module PowerTools module ActionController module Resources COLLECTION_ACTIONS = [:index] READ_ACTIONS = [:show, :new, :edit, :delete] WRITE_ACTIONS = [:create, :update, :destroy] ALL_ACTIONS = COLLECTION_ACTIONS + READ_ACTIONS + WRITE_ACTIONS SINGLETON_ACTIONS = READ_ACTIONS + WRITE_ACTIONS module ClassMethods def resource(name, options={}) write_inheritable_attribute(:resource_name, name) write_inheritable_attribute(:resource_options, options) include InstanceMethods set_filters exclude_actions end def resource_is_singleton? resource_options[:singleton] end def resource_name read_inheritable_attribute(:resource_name) end def resource_options read_inheritable_attribute(:resource_options) end protected def set_filters before_filter :find_resources, :only => :index unless resource_is_singleton? before_filter :find_resource, :only => [ :show, :edit, :update, :delete, :destroy ] end def exclude_actions if resource_options[:only] actions = resource_is_singleton? ? SINGLETON_ACTIONS : ALL_ACTIONS hide_action *(actions - resource_options[:only].to_a) elsif resource_options[:except] hide_action *resource_options[:except].to_a end end end module InstanceMethods def index show_resources end def show show_resource end def new self.resource = build_resource resource_params end def edit end def delete end def create self.resource = build_resource resource_params if @resource.save create_successful else create_failed end end def update @resource.update_attributes resource_params if @resource.save update_successful else update_failed end end def destroy @resource.destroy after_destroyed end protected def method_or_proc_or_val(thing) case thing when Symbol send(thing) when Proc thing.call(self) else thing end end def resource_options self.class.resource_options end def resource_name self.class.resource_name end def resource=(val) @resource = val instance_variable_set("@#{resource_name}", @resource) end def resources=(val) @resources = val instance_variable_set("@#{resource_name.to_s.pluralize}", @resources) end def resource_params params[self.class.resource_name] end def resource_scope assoc = self.class.resource_is_singleton? ? resource_name : resource_name.to_s.pluralize @scope ||= resource_options[:belongs_to] ? instance_variable_get("@#{resource_options[:belongs_to]}").send(assoc) : resource_name.to_s.camelize.constantize end def resource_id params[:id] end def find_resources unless pagination_options = resource_options[:paginate] self.resources = resource_scope.find(:all, method_or_proc_or_val(resource_options[:find_all_options] || {})) else self.resources = resource_scope.paginate(method_or_proc_or_val(resource_options[:find_all_options] || {}).merge( :per_page => method_or_proc_or_val(pagination_options[:per_page]) || 30, :page => method_or_proc_or_val(pagination_options[:page]) || params[:page] )) end end def find_resource if finder = resource_options[:singleton] self.resource = method_or_proc_or_val(finder) else self.resource = find_or_404(resource_scope, resource_id, resource_options[:find_with], resource_options[:find_options] || {}) end end def build_resource(params) if resource_scope.respond_to?(:build) resource_scope.build params else resource_scope.new params end end def resource_url if belongs_to = resource_options[:belongs_to] send("#{belongs_to}_#{resource_name}_url", instance_variable_get("@#{belongs_to}"), @resource) else send("#{resource_name}_url", @resource) end end def edit_resource_url if belongs_to = resource_options[:belongs_to] send("edit_#{belongs_to}_#{resource_name}_url", instance_variable_get("@#{belongs_to}"), @resource) else send("edit_#{resource_name}_url", @resource) end end def resources_url if belongs_to = resource_options[:belongs_to] send("#{belongs_to}_#{resource_name.to_s.pluralize}_url", instance_variable_get("@#{belongs_to}")) else send("#{resource_name.to_s.pluralize}_url") end end def provide(resource) if formats = resource_options[:provides] formats = formats.to_a respond_to do |wants| formats.each do |format| wants.send(format) { template_or_to_res(resource, format) } end end end end def show_resources provide @resources end def show_resource provide @resource end def create_successful resource_update_success(@resource) do redirect_to after_save_redirect end end def create_failed resource_update_fail(@resource) do render :action => :new end end def update_successful resource_update_success(@resource) do redirect_to after_save_redirect end end def update_failed resource_update_fail(@resource) do render :action => :edit end end def after_destroyed resource_update_success(@resource) do if self.class.resource_is_singleton? redirect_to '/' else redirect_to resources_url end end end def after_save_redirect redirect = resource_options[:after_save_redirect_to] || :show case redirect when :index resources_url when :show resource_url when :edit edit_resource_url else '/' end end def resource_update_success(resource, &block) resource_update_response(resource, '202 Accepted', &block) end def resource_update_fail(resource, &block) resource_update_response(resource && resource.errors, '422 Resource Invalid', &block) end def resource_update_response(resource, status, &block) formats = resource_options[:provides] || [:html] respond_to do |wants| wants.html &block if formats.delete(:html) formats.each do |format| wants.send(format) { to_res(resource, format, status) } end end end def template_or_to_res(resource, format, status='200 OK') unless resource_template_exists?(format) to_res(resource, format, status) end end def to_res(resource, format, status) output = resource.send("to_#{format}", format_options) if format == :json && (callback = params[:callback]) response.headers['Content-Type'] = 'text/javascript' render :text => "#{callback}(#{output});" else render format => output, :status => status end end def format_options options = resource_options[:format_options] or return {} if options.is_a?(Hash) method_or_proc_or_val(action_name == 'index' ? options[:collection] : options[:member]) || {} else method_or_proc_or_val(resource_options[:format_options]) || {} end end def resource_template_exists?(format) @template.send(:template_exists?, default_template_name, "#{format}.erb") end end module Routing def resources_with_delete(*entities, &block) entities, options = entities_and_options_with_delete(entities) entities.each { |entity| map_resource(entity, options.dup, &block) } end def resource_with_delete(*entities, &block) entities, options = entities_and_options_with_delete(entities) entities.each { |entity| map_singleton_resource(entity, options.dup, &block) } end private def entities_and_options_with_delete(entities) options = entities.extract_options! options[:member] ||= {} options[:member][:delete] = :get [entities, options] end end def self.included(base) base.extend(ClassMethods) ::ActionController::Routing::RouteSet::Mapper.class_eval do include PowerTools::ActionController::Resources::Routing end end end end end