rails, rest, and a bit of metaprogramming

Meta-programming invokes a high level of power and control over the programming language. Some, such as Wikipedia defines it as:
Pretty much it's about doing it on the fly, while the program is executing! This is all over the place in Rails. It is how it compute all the macros... such as associations, callbacks, filters, etc... I'm obviously not going to cover everything about metaprogramming on this article. There are already many books out there that cover this topic in greater detail. What I'm wrinting, is about one of the features that excites me the most... Modules, include and extend.
Modules
Like classes, modules are bundles of methods and constants.
Unlike classes, modules don’t have instances; instead, you specify that you want the functionality of a particular module to be added to the functionality of a class, or of a specific object.
Modules let you put your code in your own namespace so it doesn’t interfere with anyone else’s. Plus, it allows you have a number of options for mixing your functionality into other code.
Module methods can become class methods or instance methods depending on the context into which they are mixed. This is very useful and is much better than plain old inheritance... However, you can use that, too!
include
module Foo def bar baz end end class Qux include Foo end u = Qux.new u.bar # => baz Qux.bar # => NoMethodErr
Including a module in a class definition mixes the module’s instance methods into those of the class. They become available as instance methods of the class. Although, very important you cannot override existing methods in the class this way; you must use aliases instead.
Mixed-in modules behave rather like superclasses.
A module mixed into a class may create instance variables in objects of that class. If you do this, you must avoid using variable names that may conflict with existing variables in the target class.
extend
Extend makes the methods of a module available in the receiver class. If you send :extend to an object, the methods of the module being extended are made available as instance methods on the object. On the other hand if you send :extend within a class definition, the module’s methods become class methods.
Object#extend.
module Foo def bar baz end end class Qux end u = Qux.new u.bar # => NoMethodError because Foo not # yet mixed into Qux u.extend(Foo) # u is an object u.bar # => baz because bar is now available # as an instance method on u Qux.bar # => NoMethodError
class << obj include Mod end module Foo def bar baz end end class Quux extend Foo end Quux.bar # => baz because bar is now available e = Quux.new # as a class method on Foo e.bar # => NoMethodError
By the same token:
v = Qux.new v.bar # => NoMethodError class << v include Foo end v.bar # baz
Class#extend.
module Foo def bar baz end end class Quux extend Foo end Quux.bar # => baz because bar is now available # as a class method on Everything e = Quux.new e.bar # => NoMethodError
Sorry, I went a bit crazy with the metasyntactic Variables ;)
Calling extend is equivalent to calling self.extend; in a class definition self is the class, so the module’s methods are added to the class.
Okay, lets use this!
Thanks to this logic, creating plugins and adding functionality to our Rails apps is very simple. Here I created a module which DRYs out a whole lot our controllers.
Just drop the following module in your rails lib directory: ( Gist )
module RestControllerMethods def self.included(base) base.send :before_filter, :build_obj, :only => [ :new, :create ] base.send :before_filter, :load_obj, :only => [ :show, :edit, :update, :destroy ] end def index self.instance_variable_set('@' + self.controller_name, scoper.find(:all)) end def create if @obj.save flash[:notice] = "The #{cname.humanize.downcase} has been created." redirect_to redirect_url else render :action => 'new' end end def update if @obj.update_attributes(params[cname]) flash[:notice] = "The #{cname.humanize.downcase} has been updated." redirect_to redirect_url else render :action => 'edit' end end def destroy @result = @obj.destroy respond_to do |f| f.html do if @result flash[:notice] = "The #{cname.humanize.downcase} has been deleted." redirect_to redirect_url else render :action => 'show' end end f.js do render :update do |page| if @result page.remove "#{@cname}_#{@obj.id}" else page.alert "Errors deleting #{@obj.class.to_s.downcase}: #{@obj.errors.full_messages.to_sentence}" end end end end end protected def cname; @cname ||= controller_name.singularize end def set_obj; @obj ||= self.instance_variable_get('@' + cname) end def load_obj; @obj = self.instance_variable_set('@' + cname, scoper.find(params[:id])) end def scoper; Object.const_get(cname.classify) end def redirect_url; { :action => 'index' } end def build_obj @obj = self.instance_variable_set('@' + cname, scoper.is_a?(Class) ? scoper.new(params[cname]) : scoper.build(params[cname])) end end
Now just include the module in any of your controllers and you would not have to declair any of the REST actions... They already have been included thanks to our module!
class SurveysController < ApplicationController include RestControllerMethods end
Also, you can extend the actions functionality so they can have more complex functionality. Here I'm extending the "new" action so it will accept nested attributes, defined in the Survey model...
This code is from Railscasts Episode #197: Nested Model Form Part 2. I'm only adding the RestControllerMethods module functionality.
class Survey < ActiveRecord::Base has_many :questions, :dependent => :destroy accepts_nested_attributes_for :questions, :reject_if => λ { |a| a[:content].blank? }, :allow_destroy => true end
class SurveysController < ApplicationController include RestControllerMethods def new 3.times do question = @survey.questions.build 4.times { question.answers.build } end end end

This is just as a proof of concept... if you really want this sort of functionality in your controllers you should use Jose Valim inherited_resources plugin which has much more functionality plus properly tested code!
The source code for this project can be found on my github page.