Module: Resourceful::Default::Accessors

Included in:
Base
Defined in:
lib/resourceful/default/accessors.rb

Overview

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. 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.

Instance Method Summary collapse

Instance Method Details

#build_objectObject

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


124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/resourceful/default/accessors.rb', line 124

def build_object
  @current_object = if current_model.respond_to? :build
    current_model.build(object_parameters)
  else
    current_model.new(object_parameters).tap 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

#current_modelObject

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.



173
174
175
176
177
178
179
# File 'lib/resourceful/default/accessors.rb', line 173

def current_model
  if !parent? || singular?
    namespaced_model_name.constantize
  else
    parent_object.send(instance_variable_name)
  end
end

#current_model_nameObject

The string name of the current model. By default, this is derived from the name of the controller.



143
144
145
# File 'lib/resourceful/default/accessors.rb', line 143

def current_model_name
  controller_name.singularize.camelize
end

#current_objectObject

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.



87
88
89
90
91
92
93
# File 'lib/resourceful/default/accessors.rb', line 87

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

#current_objectsObject

Returns an array of all the objects of the model corresponding to the controller. For UsersController, it essentially runs User.find(: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).



53
54
55
# File 'lib/resourceful/default/accessors.rb', line 53

def current_objects
  @current_objects ||= current_model.find(:all)
end

#ensure_parent_existsObject

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]


350
351
352
353
354
# File 'lib/resourceful/default/accessors.rb', line 350

def ensure_parent_exists
  return true if parent?
  render :text => 'No parent id given', :status => 422
  return false
end

#instance_variable_nameObject

The name of the instance variable that load_object and load_objects should assign to.



157
158
159
# File 'lib/resourceful/default/accessors.rb', line 157

def instance_variable_name
  controller_name
end

#load_objectObject

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.



105
106
107
# File 'lib/resourceful/default/accessors.rb', line 105

def load_object
  instance_variable_set("@#{instance_variable_name.singularize}", current_object)
end

#load_objectsObject

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.



66
67
68
# File 'lib/resourceful/default/accessors.rb', line 66

def load_objects
  instance_variable_set("@#{instance_variable_name}", current_objects)
end

#load_parent_objectObject

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.



338
339
340
341
# File 'lib/resourceful/default/accessors.rb', line 338

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

#namespaced_model_nameObject



137
138
139
# File 'lib/resourceful/default/accessors.rb', line 137

def namespaced_model_name
  [self.class.model_namespace, current_model_name].compact.join('::')
end

#namespacesObject

An array of namespaces under which the current controller is. For example, in Admin::Content::PagesController:

namespaces #=> [:admin, :content]


152
153
154
# File 'lib/resourceful/default/accessors.rb', line 152

def namespaces
  @namespaces ||= self.class.name.split('::').slice(0...-1).map(&:underscore).map(&:to_sym)
end

#object_parametersObject

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.



185
186
187
# File 'lib/resourceful/default/accessors.rb', line 185

def object_parameters
  params[namespaced_model_name.underscore.tr('/', '_')]
end

#parent?Boolean

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.

Returns:

  • (Boolean)


211
212
213
# File 'lib/resourceful/default/accessors.rb', line 211

def parent?
  !!parent_name
end

#parent_class_nameObject

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.



303
304
305
306
# File 'lib/resourceful/default/accessors.rb', line 303

def parent_class_name
  parent_name # to init @parent_class_name
  @parent_class_name ||= parent_name.nil? ? nil : parent_name.camelize
end

#parent_modelObject

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.



315
316
317
# File 'lib/resourceful/default/accessors.rb', line 315

def parent_model
  parent_class_name.nil? ? nil : parent_class_name.constantize
end

#parent_nameObject

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[:person_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



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'lib/resourceful/default/accessors.rb', line 263

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.keys.inject({}) do |hsh, key|
      hsh[key] = key.chomp("_id") if key.to_s =~ /_id$/
      hsh
    end
    names.each do |key, name|
      begin
        klass = name.camelize.constantize
        if association = klass.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 = klass.find(params[key])
          break
        end
      rescue
      end
    end
  else
    @parent_class_name = params["#{parent_name}_type"]
    @polymorphic_parent = !@parent_class_name.nil? # NEVER USED
  end
  @parent_name
end

#parent_namesObject

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.



196
197
198
# File 'lib/resourceful/default/accessors.rb', line 196

def parent_names
  self.class.parents
end

#parent_objectObject

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.



329
330
331
# File 'lib/resourceful/default/accessors.rb', line 329

def parent_object
  @parent_object ||= parent_model.nil? ? nil : parent_model.find(params["#{parent_name}_id"])
end

#plural?Boolean

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

Returns:

  • (Boolean)


413
414
415
# File 'lib/resourceful/default/accessors.rb', line 413

def plural?
  !singular?
end

#plural_action?Boolean

Returns whether or not the current action acts upon multiple objects. By default, the only such action is index.

Returns:

  • (Boolean)


382
383
384
# File 'lib/resourceful/default/accessors.rb', line 382

def plural_action?
  PLURAL_ACTIONS.include?(params[:action].to_sym)
end

#polymorphic_parent?Boolean

Returns whether the parent (if it exists) is polymorphic

Returns:

  • (Boolean)


224
225
226
# File 'lib/resourceful/default/accessors.rb', line 224

def polymorphic_parent?
  !!polymorphic_parent_name
end

#polymorphic_parent_nameObject



292
293
294
# File 'lib/resourceful/default/accessors.rb', line 292

def polymorphic_parent_name
  @polymorphic_parent_name
end

#save_failed!Object

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.



376
377
378
# File 'lib/resourceful/default/accessors.rb', line 376

def save_failed!
  @save_succeeded = false
end

#save_succeeded!Object

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.



367
368
369
# File 'lib/resourceful/default/accessors.rb', line 367

def save_succeeded!
  @save_succeeded = true
end

#save_succeeded?Boolean

Returns whether or not the database update in the create, update, and destroy was completed successfully.

Returns:

  • (Boolean)


358
359
360
# File 'lib/resourceful/default/accessors.rb', line 358

def save_succeeded?
  @save_succeeded
end

#shallow?Boolean

Returns true if no parent id parameter can be found and a belongs_to relationship on this controller was declared with a parent for shallow routing.

Returns:

  • (Boolean)


218
219
220
221
# File 'lib/resourceful/default/accessors.rb', line 218

def shallow?
  self.class.shallow_parent &&
    (parent_name.nil? || parent_name == self.class.shallow_parent)
end

#singular?Boolean

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

Returns:

  • (Boolean)


400
401
402
# File 'lib/resourceful/default/accessors.rb', line 400

def singular?
  instance_variable_name.singularize == instance_variable_name
end

#singular_action?Boolean

Returns whether or not the current action acts upon a single object. By default, this is the case for all actions but index.

Returns:

  • (Boolean)


388
389
390
# File 'lib/resourceful/default/accessors.rb', line 388

def singular_action?
  !plural_action?
end