Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails: macro style functions

In models and controllers, we often use Rails macros like before_validation, skip_before_filter on top of the class definition.

How is this implemented? How do I add custom ones?

Thanks!

like image 678
Grnbeagle Avatar asked May 29 '09 15:05

Grnbeagle


People also ask

What is macro style class method?

Class macros are class methods that are only used when a class is defined. They allow us to dry up shared code at across classes. In this post, we'll build a custom class macro that leverages class instance variables to define class-specific attributes.

What is macro in rails?

In this context, a macro is a piece of code that generates some other code. For example: attr_accessor :foo. generates this: def foo @foo end def foo=(val) @foo = val end.

What is a macro in Ruby?

In Ruby a macro is like a method, just some code, that instead of returning a Ruby datatype returns more Ruby code! This code will get executed along with all the other code you have written when you run your program.


2 Answers

They're just standard Ruby functions. Ruby's flexible approach to syntax makes it look better than it is. You can create your own simply by writing your method as a normal Ruby function and doing one of the following:

  1. putting it somewhere that's accessible by your controllers such as application.rb

  2. putting it in a file and requiring it in.

  3. mixing the code into a class via the Ruby include keyword.


That last option is great for model classes and the first option is really only for controllers.

An Example


An example of the first approach is shown below. In this example we add code into the ApplicationController class (in application.rb) and use it in the other controllers.

class BusinessEntitiesController < ApplicationController

    nested_within :Glossary

    private

        #  Standard controller code here ....

The nested_within provides helper functions and variables to help identify the id of the "parent" resource. In effect it parses the URL on the fly and is accessible by every one of our controllers. For example when a request comes into the controller, it is automatically parsed and the class attribute @parent_resource is set to the result of a Rails find. A side effect is that a "Not Found" response is sent back if the parent resource doesn't exist. That saves us from typing boiler plate code in every nested resource.

That all sounds pretty clever but it is just a standard Ruby function at heart ...


    def self.nested_within(resource)
        #
        #   Add a filter to the about-to-be-created method find_parent_id
        #
        before_filter :find_parent_id
    
        #
        #   Work out what the names of things
        #
        resource_name = "#{resource.to_s.tableize.singularize}"
        resource_id = "#{resource_name}_id"
        resource_path = "#{resource.to_s.tableize}_path"
    
        #
        #   Get a reference to the find method in the model layer
        #
        finder = instance_eval("#{resource}.method :find_#{resource_name}")
    
    
        #
        #   Create a new method which gets executed by the before_filter above
        #
        define_method(:find_parent_id) do
            @parent_resource = finder.call(params[resource_id])
    
            head :status => :not_found, :location => resource_path 
                    unless @parent_resource
        end
    end

The nested_within function is defined in ApplicationController (controllers/application.rb) and therefore gets pulled in automatically.

Note that nested_within gets executed inside the body of the controller class. This adds the method find_parent_id to the controller.


Summary

A combination of Ruby's flexible syntax and Rail's convention-over-configuration makes this all look more powerful (or weirder) than it actually is.

Next time you find a cool method, just stick a breakpoint in front of it and trace through it. Ahh Open Source!

Let me know if I can help further or if you want some pointers on how that nested_within code works.

Chris

like image 104
Chris McCauley Avatar answered Sep 18 '22 08:09

Chris McCauley


Chris's answer is right. But here's where you want to throw your code to write your own:

The easiest way to add Controller methods like that is to define it in ApplicationController:

class ApplicationController < ActionController::Base
  ...
  def self.acts_as_awesome
    do_awesome_things
  end
end

Then you can access it from individual controllers like so:

class AwesomeController < ApplicationController
  acts_as_awesome
end

For models, you want to reopen ActiveRecord::Base:

module ActiveRecord
  class Base
    def self.acts_as_super_awesome
      do_more_awesome_stuff
    end
  end
end

I personally would put that in a file in config/initializers so that it gets loaded once, and so that I know where to look for it always.

Then you can access it in models like so:

class MySuperAwesomeModel < ActiveRecord::Base
  acts_as_super_awesome
end
like image 40
Ian Terrell Avatar answered Sep 21 '22 08:09

Ian Terrell