my resource_controller rails plugin

# Specify not more than this to get a restful controller
# with the actions of your choice. You can always override
# the generated actions if the default implementation
# doesn't fit your needs. Of course, should you need more
# than the standard CRUD actions, you're free to add them
# to the class body. If your resource shall be audited
# (by referencing a creator) you must have either a
# current_user or current_account instance method defined
# on your controller (restful_authentication will provide
# one). If for any reason you choose to name your
# current_user getter different from the 2 examples above,
# simply override current_user_getters in your controller
# (most probably ApplicationController) to include your
# method name, and you're all set. By default, this plugin
# assumes that you have 'will_paginate' installed and will
# paginate your index actions. If you don't want this to
# happen, pass false to the will_paginate method in your
# resource spec.
# ------------------------------------------------------
#
# class PostsController < ApplicationController
#   resource :posts do |posts|
#     posts.actions = [ :index, :show, :edit ] # all if omitted
#     posts.audit_column = :creator # :created_by if omitted
#     posts.will_paginate = false # true if omitted
#     posts.index, :include => :creator # eager load this when calling find
#     posts.show,  :include => [ :creator, :comments ]
#   end
# end

module ResourceController

  def self.included(base)
    base.extend(ClassMethods)
  end

  RESTFUL_ACTIONS = {

    :index => lambda do
      load_resources
      respond_to do |format|
        format.html # index.html.erb
        format.xml do
          render :xml => resources
        end
        format.js do
          render :json => {
            :html => render_to_string(:template => template_path_for_action(:index), :layout => false)
          }
        end
      end
    end,

    :show => lambda do
      load_resource
      respond_to do |format|
        format.html # show.html.erb
        format.xml do
          render :xml => resource
        end
        format.js do
          render :json => {
            :model => resource,
            :html => render_to_string(:template => template_path_for_action(:show), :layout => false)
          }
        end
      end
    end,

    :new => lambda do
      resource_for_new
      respond_to do |format|
        format.html # new.html.erb
        format.xml do
          render :xml => resource
        end
        format.js do
          render :json => {
            :model => resource,
            :html => render_to_string(:template => template_path_for_action(:new), :layout => false)
          }
        end
      end
    end,

    :edit => lambda do
      load_resource
      respond_to do |format|
        format.html # edit.html.erb
        format.xml do
          render :xml => resource
        end
        format.js do
          render :json => {
            :model => resource,
            :html => render_to_string(:template => template_path_for_action(:edit), :layout => false)
          }
        end
      end
    end,

    :create => lambda do
      resource_for_create
      respond_to do |format|
        resource_name = self.class.resource_spec.resource_name
        resource = instance_variable_get("@#{resource_name}")
        if resource.save
          msg = "#{resource_name.camelcase} was successfully created."
          flash[:notice] = msg
          format.html { redirect_to(resource) }
          format.xml  do
            render :xml => resource,
            :status => :created,
            :location => resource
          end
          format.js do
            render :json => {
              :model => resource,
              :html => render_to_string(:template => template_path_for_action(:show), :layout => false)
            }
          end
        else
          format.html { render :action => "new" }
          format.xml do
            render :xml => resource.errors, :status => :unprocessable_entity
          end
          format.js do
            render :json => resource.errors, :status => :unprocessable_entity
          end
        end
      end
    end,

    :update => lambda do
      load_resource
      resource_name = self.class.resource_spec.resource_name
      resource = instance_variable_get("@#{resource_name}")
      respond_to do |format|
        if resource.update_attributes(params[resource_name.to_sym])
          msg = "#{resource_name.camelcase} was successfully updated."
          flash[:notice] = msg
          format.html { redirect_to(resource) }
          format.xml  { head :ok }
          format.js do
            render :json => {
              :model => resource,
              :html => render_to_string(:template => template_path_for_action(:show), :layout => false)
            }
          end
        else
          format.html { render :action => "edit" }
          format.xml do
            render :xml => resource.errors, :status => :unprocessable_entity
          end
          format.js do
            render :json => resource.errors, :status => :unprocessable_entity
          end
        end
      end
    end,

    :destroy => lambda do
      load_resource
      resource_name = self.class.resource_spec.resource_name
      resource = instance_variable_get("@#{resource_name}")
      resource.destroy

      respond_to do |format|
        format.html { redirect_to(send("#{resource_name.pluralize}_path")) }
        format.xml  { head :ok }
        format.js do
          render :json => {
            :model => resource
          }
        end
      end
    end
  }

  RESTFUL_ACTION_NAMES = [
    :index, :show, :new, :edit, :create, :update, :destroy
  ]

  class InvalidSpec < Exception
  end

  class ResourceSpec

    attr_writer :will_paginate
    attr_reader :resource_name, :resource_class, :includes

    DEFAULT_AUDIT_COLUMN = 'created_by'

    def initialize(controller, resource_name)
      @controller = controller
      @resource_name = resource_name.to_s.underscore.singularize
      @resource_class = @resource_name.camelize.constantize
#      rescue
#        raise InvalidSpec, "from ResourceSpec#initialize"
    end

    def method_missing(meth, *args, &block) #:nodoc:
      if actions.include?(meth.to_sym)
        (@includes ||= {})[meth.to_sym] = args.extract_options![:include]
      else
        msg = "No public action '#{meth}' is defined for #{@controller}"
        raise InvalidSpec, msg
      end
    end

    # check if controller respond_to? current_account
    # or current_user cannot be performed definitely,
    # because it most probably is defined at a later stage
    # maybe have a look at the possibility to specify the
    # load order of plugins (the current_user function is
    # most probably supplied by restful_authentication or
    # the likes
#      def valid?
#        if @audit_column
#          @resource_class.columns_hash.include?(@audit_column)
#        else
#          true
#        end
#      rescue
#        false # swallow here but raise when called on creation
#      end

    def will_paginate?
      @will_paginate.nil? ? true : @will_paginate == true
    end

    def audit_column
      @audit_column ||= DEFAULT_AUDIT_COLUMN
    end

    def needs_audit_column?
      @resource_class.columns_hash.include?(audit_column)
    end

    def has_includes?(action)
      not @includes[action.to_sym].nil?
    end

    def audit_column=(column_name)
      @audit_column = column_name.to_s # always store as String
    end

    def actions=(value)
      @actions = case value
        when Symbol then [ value ]
        when String then [ value.to_sym ]
        when Array  then value.map(&:to_sym)
        else raise InvalidSpec, "from actions="
      end
    end

    def actions
      @actions ||= RESTFUL_ACTION_NAMES
    end

    def includes
      @includes ? @includes.dup : nil # prevent modifying the original
    end

  end

  module InstanceMethods

    protected

    def resource_spec
      self.class.resource_spec
    end

    def template_path_for_action(action)
      "#{resource_spec.resource_name.pluralize}/#{action.to_s}.html.erb"
    end

    def load_resources
      includes= resource_spec.includes[action_name.to_sym] rescue nil
      if resource_spec.will_paginate?
        options = { :page => params[:page] }
        options.merge!(:include => includes) if includes
        v = resource_spec.resource_class.paginate(options)
      else
        args = [ :all ]
        args << { :include => includes } if includes
        v = resource_spec.resource_class.find(*args)
      end
      instance_variable_set("@#{resource_spec.resource_name.pluralize}", v)
    end

    def load_resource
      includes= resource_spec.includes[action_name.to_sym] rescue nil
      args = [ params[:id] ]
      args << { :include => includes } if includes
      v = resource_spec.resource_class.find(*args)
      instance_variable_set("@#{resource_spec.resource_name}", v)
    end

    def resource
      ivar = resource_spec.resource_name
      instance_variable_get("@#{ivar}") || load_resource
    end

    def resources
      ivar = resource_spec.resource_name.pluralize
      instance_variable_get("@#{ivar}") || load_resources
    end

    def resource_for_new
      v = resource_spec.resource_class.new
      instance_variable_set("@#{resource_spec.resource_name}", v)
    end

    def resource_for_create
      attributes = params[resource_spec.resource_name.to_sym]
      if resource_spec.needs_audit_column?
        if current_user_getter_available?
          attributes.merge! resource_spec.audit_column => get_current_user.id
        else
          msg = <<-EOS
            Your controller does not respond to one of the following methods:
            #{current_user_getters.join(',')}
          EOS
          raise InvalidSpec, msg
        end
      end
      logger.debug attributes.inspect
      v = resource_spec.resource_class.new(attributes)
      instance_variable_set("@#{resource_spec.resource_name}", v)
    end

    # override if your getter is not in this list
    def current_user_getters
      [ :current_user, :current_account ]
    end

    private

    def current_user_getter_available?
      current_user_getters.any? { |m| respond_to?(m) }
    end

    # safe if current_user_getter_available?
    def get_current_user
      send current_user_getters.find { |m| respond_to?(m) }
    end

  end

  module ClassMethods

    attr_reader :resource_spec

    protected

    def resource(name)
      @resource_spec = ResourceSpec.new(self, name)
      yield @resource_spec if block_given?
#        msg = "Couldn't find #{@resource_spec.audit_column} in table"
#        raise InvalidSpec, msg unless @resource_spec.valid?

      # modify controller only if a resource was specified
      # and add only the actions that were specified in resource_spec
      include InstanceMethods
      @resource_spec.actions.each do |action|
        define_method action, &RESTFUL_ACTIONS[action]
      end
    end

  end

end


One Response (Add Your Comment)

  1. Good one !!
    It help me a lot !!

Leave a Reply

Formatting: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>