module CanCan # Handle the load and authorization controller logic so we don't clutter up all controllers with non-interface methods. # This class is used internally, so you do not need to call methods directly on it. class ControllerResource # :nodoc: def self.add_before_filter(controller_class, method, *args) options = args.extract_options! resource_name = args.first before_filter_method = options.delete(:prepend) ? :prepend_before_filter : :before_filter controller_class.send(before_filter_method, options.slice(:only, :except)) do |controller| controller.class.cancan_resource_class.new(controller, resource_name, options.except(:only, :except)).send(method) end end def initialize(controller, *args) @controller = controller @params = controller.params @options = args.extract_options! @name = args.first raise CanCan::ImplementationRemoved, "The :nested option is no longer supported, instead use :through with separate load/authorize call." if @options[:nested] raise CanCan::ImplementationRemoved, "The :name option is no longer supported, instead pass the name as the first argument." if @options[:name] raise CanCan::ImplementationRemoved, "The :resource option has been renamed back to :class, use false if no class." if @options[:resource] end def load_and_authorize_resource load_resource authorize_resource end def load_resource unless skip?(:load) if load_instance? self.resource_instance ||= load_resource_instance elsif load_collection? self.collection_instance ||= load_collection end end end def authorize_resource unless skip?(:authorize) @controller.authorize!(authorization_action, resource_instance || resource_class_with_parent) end end def parent? @options.has_key?(:parent) ? @options[:parent] : @name && @name != name_from_controller.to_sym end def skip?(behavior) # This could probably use some refactoring options = @controller.class.cancan_skipper[behavior][@name] if options.nil? false elsif options == {} true elsif options[:except] && ![options[:except]].flatten.include?(@params[:action].to_sym) true elsif [options[:only]].flatten.include?(@params[:action].to_sym) true end end protected def load_resource_instance if !parent? && new_actions.include?(@params[:action].to_sym) build_resource elsif id_param || @options[:singleton] find_resource end end def load_instance? parent? || member_action? end def load_collection? resource_base.respond_to?(:accessible_by) && !current_ability.has_block?(authorization_action, resource_class) end def load_collection resource_base.accessible_by(current_ability, authorization_action) end def build_resource resource = resource_base.new(@params[name] || {}) resource.send("#{parent_name}=", parent_resource) if @options[:singleton] && parent_resource initial_attributes.each do |attr_name, value| resource.send("#{attr_name}=", value) end resource end def initial_attributes current_ability.attributes_for(@params[:action].to_sym, resource_class).delete_if do |key, value| @params[name] && @params[name].include?(key) end end def find_resource if @options[:singleton] && parent_resource.respond_to?(name) parent_resource.send(name) else @options[:find_by] ? resource_base.send("find_by_#{@options[:find_by]}!", id_param) : resource_base.find(id_param) end end def authorization_action parent? ? :show : @params[:action].to_sym end def id_param @params[parent? ? :"#{name}_id" : :id] end def member_action? new_actions.include?(@params[:action].to_sym) || @options[:singleton] || (@params[:id] && !collection_actions.include?(@params[:action].to_sym)) end # Returns the class used for this resource. This can be overriden by the :class option. # If +false+ is passed in it will use the resource name as a symbol in which case it should # only be used for authorization, not loading since there's no class to load through. def resource_class case @options[:class] when false then name.to_sym when nil then name.to_s.camelize.constantize when String then @options[:class].constantize else @options[:class] end end def resource_class_with_parent parent_resource ? {parent_resource => resource_class} : resource_class end def resource_instance=(instance) @controller.instance_variable_set("@#{instance_name}", instance) end def resource_instance @controller.instance_variable_get("@#{instance_name}") if load_instance? end def collection_instance=(instance) @controller.instance_variable_set("@#{instance_name.to_s.pluralize}", instance) end def collection_instance @controller.instance_variable_get("@#{instance_name.to_s.pluralize}") end # The object that methods (such as "find", "new" or "build") are called on. # If the :through option is passed it will go through an association on that instance. # If the :shallow option is passed it will use the resource_class if there's no parent # If the :singleton option is passed it won't use the association because it needs to be handled later. def resource_base if @options[:through] if parent_resource @options[:singleton] ? resource_class : parent_resource.send(@options[:through_association] || name.to_s.pluralize) elsif @options[:shallow] resource_class else raise AccessDenied # maybe this should be a record not found error instead? end else resource_class end end def parent_name @options[:through] && [@options[:through]].flatten.detect { |i| fetch_parent(i) } end # The object to load this resource through. def parent_resource parent_name && fetch_parent(parent_name) end def fetch_parent(name) if @controller.instance_variable_defined? "@#{name}" @controller.instance_variable_get("@#{name}") elsif @controller.respond_to? name @controller.send(name) end end def current_ability @controller.send(:current_ability) end def name @name || name_from_controller end def name_from_controller @params[:controller].sub("Controller", "").underscore.split('/').last.singularize end def instance_name @options[:instance_name] || name end def collection_actions [:index] + [@options[:collection]].flatten end def new_actions [:new, :create] + [@options[:new]].flatten end end end