module Resourceful # This module contains various methods # that are available from actions and callbacks. # Default::Accessors and Default::URLs are the most useful to users; # the rest are mostly used internally. # # However, if you want to poke around the internals a little, # check out Default::Actions, which has the default Action definitions, # and Default::Responses.included, which defines the default response_for[link:classes/Resourceful/Builder.html#M000061] blocks. module Default # This module contains all sorts of useful methods # that allow access to the resources being worked with, # metadata about the controller and action, # and so forth. # # Many of these accessors call other accessors # and are called by the default make_resourceful actions[link:classes/Resourceful/Default/Actions.html]. # This means that overriding one method # can affect everything else. # # This can be dangerous, but it can also be very powerful. # make_resourceful is designed to take advantage of overriding, # so as long as the new methods accomplish the same purpose as the old ones, # everything will just work. # Even if you make a small mistake, # it's hard to break the controller in any unexpected ways. # # For example, suppose your controller is called TagsController, # but your model is called PhotoTag. # All you have to do is override current_model_name: # # def current_model_name # "PhotoTag" # end # # Then current_model will return the PhotoTag model, # current_object will call PhotoTag.find, # and so forth. # # Overriding current_objects and current_object is particularly useful # for providing customized model lookup logic. module Accessors # Returns an array of all the objects of the model corresponding to the controller. # For UsersController, it essentially runs User.all. # # However, there are a few important differences. # First, this method caches is results in the @current_objects instance variable. # That way, multiple calls won't run multiple queries. # # Second, this method uses the current_model accessor, # which provides a lot of flexibility # (see the documentation for current_model for details). def current_objects @current_objects ||= current_model.all end # Calls current_objects and stores # the result in an instance variable # named after the controller. # # This is called automatically by the default make_resourceful actions. # You shouldn't need to use it directly unless you're creating a new action. # # For example, in UsersController, # calling +load_objects+ sets @users = current_objects. def load_objects instance_variable_set("@#{instance_variable_name}", current_objects) end # Returns the object referenced by the id parameter # (or the newly-created object for the +new+ and +create+ actions). # For UsersController, it essentially runs User.find(params[:id]). # # However, there are a few important differences. # First, this method caches is results in the @current_objects instance variable. # That way, multiple calls won't run multiple queries. # # Second, this method uses the current_model accessor, # which provides a lot of flexibility # (see the documentation for current_model for details). # # Note that this is different for a singleton controller, # where there's only one resource per parent resource. # Then this just returns that resource. # For example, if Person has_one Hat, # then in HatsController current_object essentially runs Person.find(params[:person_id]).hat. def current_object @current_object ||= if !parent? || plural? current_model.find(params[:id]) if params[:id] else parent_object.send(instance_variable_name.singularize) end end # Calls current_object and stores # the result in an instance variable # named after the controller. # # This is called automatically by the default make_resourceful actions. # You shouldn't need to use it directly unless you're creating a new action. # # For example, in UsersController, # calling +load_object+ sets @user = current_object. def load_object instance_variable_set("@#{instance_variable_name.singularize}", current_object) end # Creates a new object of the type of the current model # with the current object's parameters. # +current_object+ then returns this object for this action # instead of looking up a new object. # # This is called automatically by the default make_resourceful actions. # You shouldn't need to use it directly unless you're creating a new action. # # Note that if a parent object exists, # the newly created object will automatically be a child of the parent object. # For example, on POST /people/4/things, # # build_object # current_object.person.id #=> 4 # def build_object @current_object = if current_model.respond_to? :build current_model.build(object_parameters) else returning(current_model.new(object_parameters)) do |obj| if singular? && parent? obj.send("#{parent_name}_id=", parent_object.id) obj.send("#{parent_name}_type=", parent_object.class.to_s) if polymorphic_parent? end end end end # The string name of the current model. # By default, this is derived from the name of the controller. def current_model_name controller_name.singularize.camelize end # An array of namespaces under which the current controller is. # For example, in Admin::Content::PagesController: # # namespaces #=> [:admin, :content] # def namespaces @namespaces ||= self.class.name.split('::').slice(0...-1).map(&:underscore).map(&:to_sym) end # The name of the instance variable that load_object and load_objects should assign to. def instance_variable_name controller_name end # The class of the current model. # Note that if a parent object exists, # this instead returns the association object. # For example, in HatsController where Person has_many :hats, # # current_model #=> Person.find(params[:person_id]).hats # # This is useful because the association object uses duck typing # to act like a model class. # It supplies a find method that's automatically scoped # to ensure that the object returned is actually a child of the parent, # and so forth. def current_model if !parent? || singular? current_model_name.constantize else parent_object.send(instance_variable_name) end end # Returns the hash passed as HTTP parameters # that defines the new (or updated) attributes # of the current object. # This is only meaningful for +create+ or +update+. def object_parameters params[current_model_name.underscore] end # Returns a list of the names of all the potential parents of the current model. # For a non-nested controller, this is []. # For example, in HatsController where Rack has_many :hats and Person has_many :hats, # # parents #=> ["rack", "person"] # # Note that the parents must be declared via Builder#belongs_to. def parent_names self.class.read_inheritable_attribute :parents end # Returns true if an appropriate parent id parameter has been supplied. # For example, in HatsController where Rack has_many :hats and Person has_many :hats, # if params[:rack_id] or params[:person_id] is given, # # parent? #=> true # # Otherwise, if both params[:rack_id] and params[:rack_id] are nil, # # parent? #=> false # # Note that parents must be declared via Builder#belongs_to. def parent? !!parent_name end # Returns whether the parent (if it exists) is polymorphic def polymorphic_parent? !!polymorphic_parent_name end # Returns the name of the current parent object if a parent id is given, or nil otherwise. # For example, in HatsController where Rack has_many :hats and Person has_many :hats, # if params[:rack_id] is given, # # parent_name #=> "rack" # # If params[:person_id] is given, # # parent_name #=> "person" # # If both params[:rack_id] and params[:rack_id] are nil, # # parent_name #=> nil # # There are several things to note about this method. # First, make_resourceful only supports single-level model nesting. # Thus, if neither params[:rack_id] nor params[:rack_id] are nil, # the return value of +parent_name+ is undefined. # # Second, don't use parent_name to check whether a parent id is given. # It's better to use the more semantic parent? method. # # Third, parent_name caches its return value in the @parent_name variable, # which you should keep in mind if you're overriding it. # However, because @parent_name == nil could mean that there is no parent # _or_ that the method hasn't been run yet, # it uses defined?(@parent_name) to do the caching # rather than @parent_name ||=. See the source code. # # Finally, note that parents must be declared via Builder#belongs_to. # # FIXME - Perhaps this logic should be moved to parent?() or another init method def parent_name return @parent_name if defined?(@parent_name) @parent_name = parent_names.find { |name| params["#{name}_id"] } if @parent_name.nil? # get any polymorphic parents through :as association inspection names = params.reject { |key, value| key.to_s[/_id$/].nil? }.keys.map { |key| key.chomp("_id") } names.each do |name| begin klass = name.camelize.constantize id = params["#{name}_id"] object = klass.find(id) if association = object.class.reflect_on_all_associations.detect { |association| association.options[:as] && parent_names.include?(association.options[:as].to_s) } @parent_name = name @polymorphic_parent_name = association.options[:as].to_s @parent_class_name = name.camelize @parent_object = object break end rescue end end else @parent_class_name = params["#{parent_name}_type"] @polymorphic_parent = !@parent_class_name.nil? end @parent_name end def polymorphic_parent_name @polymorphic_parent_name end # Returns the class name of the current parent. # For example, in HatsController where Person has_many :hats, # if params[:person_id] is given, # # parent_class_name #=> 'Person' # # Note that parents must be declared via Builder#belongs_to. def parent_class_name parent_name # to init @parent_class_name @parent_class_name ||= parent_name.nil? ? nil : parent_name.camelize end # Returns the model class of the current parent. # For example, in HatsController where Person has_many :hats, # if params[:person_id] is given, # # parent_models #=> Person # # Note that parents must be declared via Builder#belongs_to. def parent_model parent_class_name.nil? ? nil : parent_class_name.constantize end # Returns the current parent object for the current object. # For example, in HatsController where Person has_many :hats, # if params[:person_id] is given, # # parent_object #=> Person.find(params[:person_id]) # # Note that parents must be declared via Builder#belongs_to. # # Note also that the results of this method are cached # so that multiple calls don't result in multiple SQL queries. def parent_object @parent_object ||= parent_model.nil? ? nil : parent_model.find(params["#{parent_name}_id"]) end # Assigns the current parent object, as given by parent_objects, # to its proper instance variable, as given by parent_name. # # This is automatically added as a before_filter. # You shouldn't need to use it directly unless you're creating a new action. def load_parent_object instance_variable_set("@#{parent_name}", parent_object) if parent? instance_variable_set("@#{polymorphic_parent_name}", parent_object) if polymorphic_parent? end # Renders a 422 error if no parent id is given. # This is meant to be used with before_filter # to ensure that some actions are only called with a parent id. # For example: # # before_filter :ensure_parent_exists, :only => [:create, :update] # def ensure_parent_exists return true if parent? render :text => 'No parent id given', :status => 422 return false end # Returns whether or not the database update in the +create+, +update+, and +destroy+ # was completed successfully. def save_succeeded? @save_succeeded end # Declares that the current databse update was completed successfully. # Causes subsequent calls to save_succeeded? to return +true+. # # This is mostly meant to be used by the default actions, # but it can be used by user-defined actions as well. def save_succeeded! @save_succeeded = true end # Declares that the current databse update was not completed successfully. # Causes subsequent calls to save_succeeded? to return +false+. # # This is mostly meant to be used by the default actions, # but it can be used by user-defined actions as well. def save_failed! @save_succeeded = false end # Returns whether or not the current action acts upon multiple objects. # By default, the only such action is +index+. def plural_action? PLURAL_ACTIONS.include?(params[:action].to_sym) end # Returns whether or not the current action acts upon a single object. # By default, this is the case for all actions but +index+. def singular_action? !plural_action? end # Returns whether the controller is a singleton, # implying that there is only one such resource for each parent resource. # # Note that the way this is determined is based on the singularity of the controller name, # so it may yield false positives for oddly-named controllers and need to be overridden. # # TODO: maybe we can define plural? and singular? as class_methods, # so they are not visible to the world def singular? instance_variable_name.singularize == instance_variable_name end # Returns whether the controller is a normal plural controller, # implying that there are multiple resources for each parent resource. # # Note that the way this is determined is based on the singularity of the controller name, # so it may yield false negatives for oddly-named controllers. # If this is the case, the singular? method should be overridden. # # TODO: maybe we can define plural? and singular? as class_methods, # so they are not visible to the world def plural? !singular? end end end end