Module: ActiveRecord::Associations::ClassMethods

Defined in:
lib/activerecord/lib/active_record/associations.rb

Instance Method Summary collapse

Instance Method Details

#add_cache_callbacksObject



389
390
391
392
393
394
395
396
397
398
399
400
401
# File 'lib/activerecord/lib/active_record/associations.rb', line 389

def add_cache_callbacks
  method_name = :after_save_cache_expire
  return if respond_to? method_name

  define_method(method_name) do
    return unless self[:updated_at]

    self.class.reflections.each do |name, reflection|
      cache_delete(reflection) if reflection.options[:cached]
    end
  end
  after_save method_name
end

#belongs_to(association_id, options = {}) ⇒ Object

Adds the following methods for retrieval and query for a single associated object for which this object holds an id: association is replaced with the symbol passed as the first argument, so belongs_to :author would add among others author.nil?.

  • association(force_reload = false) - Returns the associated object. nil is returned if none is found.

  • association=(associate) - Assigns the associate object, extracts the primary key, and sets it as the foreign key.

  • association.nil? - Returns true if there is no associated object.

  • build_association(attributes = {}) - Returns a new object of the associated type that has been instantiated with attributes and linked to this object through a foreign key, but has not yet been saved.

  • create_association(attributes = {}) - Returns a new object of the associated type that has been instantiated with attributes, linked to this object through a foreign key, and that has already been saved (if it passed the validation).

Example: A Post class declares belongs_to :author, which will add:

  • Post#author (similar to Author.find(author_id))

  • Post#author=(author) (similar to post.author_id = author.id)

  • Post#author? (similar to post.author == some_author)

  • Post#author.nil?

  • Post#build_author (similar to post.author = Author.new)

  • Post#create_author (similar to post.author = Author.new; post.author.save; post.author)

The declaration can also include an options hash to specialize the behavior of the association.

Options are:

  • :class_name - Specify the class name of the association. Use it only if that name can’t be inferred from the association name. So has_one :author will by default be linked to the Author class, but if the real class name is Person, you’ll have to specify it with this option.

  • :conditions - Specify the conditions that the associated object must meet in order to be included as a WHERE SQL fragment, such as authorized = 1.

  • :select - By default, this is * as in SELECT * FROM, but can be changed if, for example, you want to do a join but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.

  • :foreign_key - Specify the foreign key used for the association. By default this is guessed to be the name of the association with an “_id” suffix. So a class that defines a belongs_to :person association will use “person_id” as the default :foreign_key. Similarly, belongs_to :favorite_person, :class_name => "Person" will use a foreign key of “favorite_person_id”.

  • :dependent - If set to :destroy, the associated object is destroyed when this object is. If set to :delete, the associated object is deleted without calling its destroy method. This option should not be specified when belongs_to is used in conjunction with a has_many relationship on another class because of the potential to leave orphaned records behind.

  • :counter_cache - Caches the number of belonging objects on the associate class through the use of increment_counter and decrement_counter. The counter cache is incremented when an object of this class is created and decremented when it’s destroyed. This requires that a column named #{table_name}_count (such as comments_count for a belonging Comment class) is used on the associate class (such as a Post class). You can also specify a custom counter cache column by providing a column name instead of a true/false value to this option (e.g., :counter_cache => :my_custom_counter.) When creating a counter cache column, the database statement or migration must specify a default value of 0, failing to do this results in a counter with NULL value, which will never increment. Note: Specifying a counter cache will add it to that model’s list of readonly attributes using attr_readonly.

  • :include - Specify second-order associations that should be eager loaded when this object is loaded.

  • :polymorphic - Specify this association is a polymorphic association by passing true. Note: If you’ve enabled the counter cache, then you may want to add the counter cache attribute to the attr_readonly list in the associated classes (e.g. class Post; attr_readonly :comments_count; end).

  • :readonly - If true, the associated object is readonly through the association.

  • :validate - If false, don’t validate the associated objects when saving the parent object. false by default.

Option examples:

belongs_to :firm, :foreign_key => "client_of"
belongs_to :author, :class_name => "Person", :foreign_key => "author_id"
belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id",
           :conditions => 'discounts > #{payments_count}'
belongs_to :attachable, :polymorphic => true
belongs_to :project, :readonly => true
belongs_to :post, :counter_cache => true
belongs_to :blog, :cached => true


182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/activerecord/lib/active_record/associations.rb', line 182

def belongs_to(association_id, options = {})
  reflection = create_belongs_to_reflection(association_id, options)

  ivar = "@#{reflection.name}"

  if reflection.options[:polymorphic]
    association_accessor_methods(reflection, BelongsToPolymorphicAssociation)

    method_name = "polymorphic_belongs_to_before_save_for_#{reflection.name}".to_sym
    define_method(method_name) do
      association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")

      if association && association.target
        if association.new_record?
          association.save(true)
        end

        if association.updated?
          self["#{reflection.primary_key_name}"] = association.id
          self["#{reflection.options[:foreign_type]}"] = association.class.base_class.name.to_s
        end
      end
    end
    before_save method_name
  else
    association_accessor_methods(reflection, BelongsToAssociation)
    association_constructor_method(:build,  reflection, BelongsToAssociation)
    association_constructor_method(:create, reflection, BelongsToAssociation)

    method_name = "belongs_to_before_save_for_#{reflection.name}".to_sym
    define_method(method_name) do
      association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")

      if !association.nil?
        if association.new_record?
          association.save(true)
        end

        if association.updated?
          self["#{reflection.primary_key_name}"] = association.id
        end
      end
    end
    before_save method_name
  end

  # Create the callbacks to update counter cache
  if options[:counter_cache]
    cache_column = options[:counter_cache] == true ?
      "#{self.to_s.underscore.pluralize}_count" :
      options[:counter_cache]

    method_name = "belongs_to_counter_cache_after_create_for_#{reflection.name}".to_sym
    define_method(method_name) do
      association = send("#{reflection.name}")
      association.class.increment_counter("#{cache_column}", send("#{reflection.primary_key_name}")) unless association.nil?
    end
    after_create method_name

    method_name = "belongs_to_counter_cache_before_destroy_for_#{reflection.name}".to_sym
    define_method(method_name) do
      association = send("#{reflection.name}")
      association.class.decrement_counter("#{cache_column}", send("#{reflection.primary_key_name}")) unless association.nil?
    end
    before_destroy method_name

    module_eval(
      "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)"
    )
  end

  if options[:cached]
    after_save_method_name = "belongs_to_after_save_for_#{reflection.name}".to_sym
    after_destroy_method_name = "belongs_to_after_destroy_for_#{reflection.name}".to_sym
    define_method(after_save_method_name) do
      send(reflection.name).expire_cache_for(self.class.name)
    end

    alias_method after_destroy_method_name, after_save_method_name
    after_save after_save_method_name
    after_destroy after_destroy_method_name
  end

  add_single_associated_validation_callbacks(reflection.name) if options[:validate] == true

  configure_dependency_for_belongs_to(reflection)
end

#collection_accessor_methods(reflection, association_proxy_class, options, writer = true) ⇒ Object



334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/activerecord/lib/active_record/associations.rb', line 334

def collection_accessor_methods(reflection, association_proxy_class, options, writer = true)
  collection_reader_method(reflection, association_proxy_class, options)

  if writer
    define_method("#{reflection.name}=") do |new_value|
      # Loads proxy class instance (defined in collection_reader_method) if not already loaded
      association = send(reflection.name)
      association.replace(new_value)

      cache_write(reflection, association) if options[:cached]

      association
    end

    define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
      ids = (new_value || []).reject { |nid| nid.blank? }
      send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
    end
  end
end

#collection_reader_method(reflection, association_proxy_class, options) ⇒ Object



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/activerecord/lib/active_record/associations.rb', line 270

def collection_reader_method(reflection, association_proxy_class, options)
  define_method(reflection.name) do |*params|
    ivar = "@#{reflection.name}"

    force_reload = params.first unless params.empty?

    association = if options[:cached]
      cache_read(reflection)
    else
      instance_variable_get(ivar) if instance_variable_defined?(ivar)
    end

    unless association.respond_to?(:loaded?)
      association = association_proxy_class.new(self, reflection)
      if options[:cached]
        cache_write(reflection, association)
      else
        instance_variable_set(ivar, association)
      end
    end

    if force_reload
      association.reload
      cache_write(reflection, association) if options[:cached]
    end

    association
  end

  method_name = "#{reflection.name.to_s.singularize}_ids"
  define_method(method_name) do
    if options[:cached]
      cache_fetch("#{cache_key}/#{method_name}", send("calculate_#{method_name}"))
    else
      send("calculate_#{method_name}")
    end
  end
  
  define_method("calculate_#{method_name}") do
    send(reflection.name).map { |record| record.id }
  end
end

#create_belongs_to_reflection(association_id, options) ⇒ Object



374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/activerecord/lib/active_record/associations.rb', line 374

def create_belongs_to_reflection(association_id, options)
  options.assert_valid_keys(
    :class_name, :foreign_key, :foreign_type, :remote, :select, :conditions, :include, :dependent,
    :counter_cache, :extend, :polymorphic, :readonly, :validate, :cached
  )

  reflection = create_reflection(:belongs_to, association_id, options, self)

  if options[:polymorphic]
    reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type"
  end

  reflection
end

#create_has_many_reflection(association_id, options, &extension) ⇒ Object



355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/activerecord/lib/active_record/associations.rb', line 355

def create_has_many_reflection(association_id, options, &extension)
  options.assert_valid_keys(
    :class_name, :table_name, :foreign_key, :primary_key,
    :dependent,
    :select, :conditions, :include, :order, :group, :limit, :offset,
    :as, :through, :source, :source_type,
    :uniq,
    :finder_sql, :counter_sql,
    :before_add, :after_add, :before_remove, :after_remove,
    :extend, :readonly,
    :validate, :accessible,
    :cached
  )

  options[:extend] = create_extension_modules(association_id, extension, options[:extend])

  create_reflection(:has_many, association_id, options, self)
end

#has_and_belongs_to_many(association_id, options = {}, &extension) ⇒ Object



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/activerecord/lib/active_record/associations.rb', line 313

def has_and_belongs_to_many(association_id, options = {}, &extension)
  reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)

  add_multiple_associated_validation_callbacks(reflection.name) unless options[:validate] == false
  add_multiple_associated_save_callbacks(reflection.name)
  collection_accessor_methods(reflection, HasAndBelongsToManyAssociation, options)

  # Don't use a before_destroy callback since users' before_destroy
  # callbacks will be executed after the association is wiped out.
  old_method = "destroy_without_habtm_shim_for_#{reflection.name}"
  class_eval <<-end_eval unless method_defined?(old_method)
    alias_method :#{old_method}, :destroy_without_callbacks
    def destroy_without_callbacks
      #{reflection.name}.clear
      #{old_method}
    end
  end_eval

  add_association_callbacks(reflection.name, options)
end

#has_many(association_id, options = {}, &extension) ⇒ Object

Adds the following methods for retrieval and query of collections of associated objects: collection is replaced with the symbol passed as the first argument, so has_many :clients would add among others clients.empty?.

  • collection(force_reload = false) - Returns an array of all the associated objects. An empty array is returned if none are found.

  • collection<<(object, ...) - Adds one or more objects to the collection by setting their foreign keys to the collection’s primary key.

  • collection.delete(object, ...) - Removes one or more objects from the collection by setting their foreign keys to NULL. This will also destroy the objects if they’re declared as belongs_to and dependent on this model.

  • collection=objects - Replaces the collections content by deleting and adding objects as appropriate.

  • collection_singular_ids - Returns an array of the associated objects’ ids

  • collection_singular_ids=ids - Replace the collection with the objects identified by the primary keys in ids

  • collection.clear - Removes every object from the collection. This destroys the associated objects if they are associated with :dependent => :destroy, deletes them directly from the database if :dependent => :delete_all, otherwise sets their foreign keys to NULL.

  • collection.empty? - Returns true if there are no associated objects.

  • collection.size - Returns the number of associated objects.

  • collection.find - Finds an associated object according to the same rules as Base.find.

  • collection.build(attributes = {}, ...) - Returns one or more new objects of the collection type that have been instantiated with attributes and linked to this object through a foreign key, but have not yet been saved. Note: This only works if an associated object already exists, not if it’s nil!

  • collection.create(attributes = {}) - Returns a new object of the collection type that has been instantiated with attributes, linked to this object through a foreign key, and that has already been saved (if it passed the validation). Note: This only works if an associated object already exists, not if it’s nil!

Example: A Firm class declares has_many :clients, which will add:

  • Firm#clients (similar to Clients.find :all, :conditions => "firm_id = #{id}")

  • Firm#clients<<

  • Firm#clients.delete

  • Firm#clients=

  • Firm#client_ids

  • Firm#client_ids=

  • Firm#clients.clear

  • Firm#clients.empty? (similar to firm.clients.size == 0)

  • Firm#clients.size (similar to Client.count "firm_id = #{id}")

  • Firm#clients.find (similar to Client.find(id, :conditions => "firm_id = #{id}"))

  • Firm#clients.build (similar to Client.new("firm_id" => id))

  • Firm#clients.create (similar to c = Client.new("firm_id" => id); c.save; c)

The declaration can also include an options hash to specialize the behavior of the association.

Options are:

  • :class_name - Specify the class name of the association. Use it only if that name can’t be inferred from the association name. So has_many :products will by default be linked to the Product class, but if the real class name is SpecialProduct, you’ll have to specify it with this option.

  • :conditions - Specify the conditions that the associated objects must meet in order to be included as a WHERE SQL fragment, such as price > 5 AND name LIKE 'B%'. Record creations from the association are scoped if a hash is used. has_many :posts, :conditions => {:published => true} will create published posts with @blog.posts.create or @blog.posts.build.

  • :order - Specify the order in which the associated objects are returned as an ORDER BY SQL fragment, such as last_name, first_name DESC.

  • :foreign_key - Specify the foreign key used for the association. By default this is guessed to be the name of this class in lower-case and “_id” suffixed. So a Person class that makes a has_many association will use “person_id” as the default :foreign_key.

  • :dependent - If set to :destroy all the associated objects are destroyed alongside this object by calling their destroy method. If set to :delete_all all associated objects are deleted without calling their destroy method. If set to :nullify all associated objects’ foreign keys are set to NULL without calling their save callbacks. Warning: This option is ignored when also using the :through option.

  • :finder_sql - Specify a complete SQL statement to fetch the association. This is a good way to go for complex associations that depend on multiple tables. Note: When this option is used, find_in_collection is not added.

  • :counter_sql - Specify a complete SQL statement to fetch the size of the association. If :finder_sql is specified but not :counter_sql, :counter_sql will be generated by replacing SELECT ... FROM with SELECT COUNT(*) FROM.

  • :extend - Specify a named module for extending the proxy. See “Association extensions”.

  • :include - Specify second-order associations that should be eager loaded when the collection is loaded.

  • :group - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.

  • :limit - An integer determining the limit on the number of rows that should be returned.

  • :offset - An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.

  • :select - By default, this is * as in SELECT * FROM, but can be changed if you, for example, want to do a join but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will rise an error.

  • :as - Specifies a polymorphic interface (See belongs_to).

  • :through - Specifies a Join Model through which to perform the query. Options for :class_name and :foreign_key are ignored, as the association uses the source reflection. You can only use a :through query through a belongs_to or has_many association on the join model.

  • :source - Specifies the source association name used by has_many :through queries. Only use it if the name cannot be inferred from the association. has_many :subscribers, :through => :subscriptions will look for either :subscribers or :subscriber on Subscription, unless a :source is given.

  • :source_type - Specifies type of the source association used by has_many :through queries where the source association is a polymorphic belongs_to.

  • :uniq - If true, duplicates will be omitted from the collection. Useful in conjunction with :through.

  • :readonly - If true, all the associated objects are readonly through the association.

  • :cached - If true, all the associated objects will be cached.

Option examples:

has_many :comments, :order => "posted_on"
has_many :comments, :include => :author
has_many :people, :class_name => "Person", :conditions => "deleted = 0", :order => "name"
has_many :tracks, :order => "position", :dependent => :destroy
has_many :comments, :dependent => :nullify
has_many :tags, :as => :taggable
has_many :reports, :readonly => true
has_many :posts, :cached => true
has_many :subscribers, :through => :subscriptions, :source => :user
has_many :subscribers, :class_name => "Person", :finder_sql =>
    'SELECT DISTINCT people.* ' +
    'FROM people p, post_subscriptions ps ' +
    'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' +
    'ORDER BY p.first_name'


105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/activerecord/lib/active_record/associations.rb', line 105

def has_many(association_id, options = {}, &extension)
  reflection = create_has_many_reflection(association_id, options, &extension)

  configure_dependency_for_has_many(reflection)

  add_multiple_associated_save_callbacks(reflection.name)
  add_association_callbacks(reflection.name, reflection.options)

  if options[:through]
    collection_accessor_methods(reflection, HasManyThroughAssociation, options)
  else
    collection_accessor_methods(reflection, HasManyAssociation, options)
  end

  add_cache_callbacks if options[:cached]
end