my resource_controller rails plugin
November 20th, 2007 • Uncategorized
# 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
Good one !!
It help me a lot !!