Class: ActiveRecord::Associations::HasManyThroughAssociation
Relationships & Source Files | |
Super Chains via Extension / Inclusion / Inheritance | |
Class Chain:
|
|
Instance Chain:
|
|
Inherits: |
ActiveRecord::Associations::HasManyAssociation
|
Defined in: | activerecord/lib/active_record/associations/has_many_through_association.rb |
Overview
Active Record Has Many Through Association
Class Method Summary
Instance Attribute Summary
- #target_reflection_has_associated_record? ⇒ Boolean readonly private
- #through_scope readonly private
ThroughAssociation
- Included
ForeignAssociation
- Included
CollectionAssociation
- Inherited
#collection?, | |
#empty? | Returns true if the collection is empty. |
#find_from_target?, #nested_attributes_target, #null_scope?, #target= |
Association
- Inherited
#collection? | Whether the association represent a single record or a collection of records. |
#disable_joins, | |
#loaded? | Has the target been already loaded? |
#options, #owner, #reflection, | |
#stale_target? | The target is stale if the target no longer points to the record(s) that the relevant foreign_key(s) refers to. |
#target, | |
#target= | Sets the target of this association to |
#find_target?, | |
#foreign_key_present? | Returns true if there is a foreign key present on the owner which references the target. |
#violates_strict_loading? |
Instance Method Summary
- #concat(*records)
- #insert_record(record, validate = true, raise = false)
- #build_record(attributes) private
-
#build_through_record(record)
private
The through record (built with build_record) is temporarily cached so that it may be reused if insert_record is subsequently called.
- #concat_records(records) private
- #delete_or_nullify_all_records(method) private
- #delete_records(records, method) private
- #delete_through_records(records) private
- #difference(a, b) private
- #distribution(array) private
- #find_target(async: false) private
- #intersection(a, b) private
-
#invertible_for?(record) ⇒ Boolean
private
NOTE - not sure that we can actually cope with inverses here.
- #mark_occurrence(distribution, record) private
- #remove_records(existing_records, records, method) private
- #save_through_record(record) private
- #through_records_for(record) private
- #through_scope_attributes private
- #update_through_counter?(method) ⇒ Boolean private
ThroughAssociation
- Included
#build_record, | |
#construct_join_attributes | Construct attributes for |
#ensure_mutable, #ensure_not_nested, | |
#stale_state | Note: this does not capture all cases, for example it would be impractical to try to properly support stale-checking for nested associations. |
#target_scope | We merge in these scopes for two reasons: |
#through_association, #through_reflection, #transaction |
HasManyAssociation
- Inherited
#handle_dependency, #insert_record, #_create_record, #concat_records, | |
#count_records | Returns the number of records in this collection. |
#delete_count, #delete_or_nullify_all_records, | |
#delete_records | Deletes the records according to the |
#difference, #intersection, #update_counter, #update_counter_if_success, #update_counter_in_memory |
ForeignAssociation
- Included
#nullified_owner_attributes, | |
#set_owner_attributes | Sets the owner attributes on the given record. |
CollectionAssociation
- Inherited
#add_to_target, #build, | |
#concat | Add |
#delete | Removes |
#delete_all | Removes all records from the association without calling callbacks on the associated records. |
#destroy | Deletes the |
#destroy_all | Destroy all the records from this association. |
#find, | |
#ids_reader | Implements the ids reader method, e.g. |
#ids_writer | Implements the ids writer method, e.g. |
#include?, #load_target, | |
#reader | Implements the reader method, e.g. |
#replace | Replace this collection with |
#reset, #scope, | |
#size | Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn’t been loaded, and calling |
#writer | Implements the writer method, e.g. |
#_create_record, #callback, #callbacks_for, #concat_records, #delete_or_destroy, | |
#delete_records | Delete the given records from the association, using one of the methods |
#find_by_scan | If the |
#include_in_memory?, | |
#insert_record | Do the relevant stuff to insert the given record into the association collection. |
#merge_target_lists | We have some records loaded from the database (persisted) and some that are in-memory (memory). |
#remove_records, #replace_common_records_in_memory, #replace_on_target, #replace_records, #transaction |
Association
- Inherited
#async_load_target, #create, #create!, #extensions, #initialize_attributes, #inversed_from, #inversed_from_queries, | |
#klass | Returns the class of the target. |
#load_target | Loads the target if needed and returns it. |
#loaded! | Asserts the target has been loaded setting the loaded flag to |
#marshal_dump | We can’t dump @reflection and @through_reflection since it contains the scope proc. |
#marshal_load, | |
#reload | Reloads the target and returns |
#remove_inverse_instance | Remove the inverse association, if possible. |
#reset | Resets the loaded flag to |
#reset_negative_cache, #reset_scope, #scope, | |
#set_inverse_instance | Set the inverse association, if possible. |
#set_inverse_instance_from_queries, #set_strict_loading, | |
#association_scope | The scope for this association. |
#build_record, #enqueue_destroy_association, | |
#ensure_klass_exists! | Reader and writer methods call this so that consistent errors are presented when the association target class does not exist. |
#find_target, | |
#foreign_key_for? | Returns true if record contains the foreign_key. |
#inversable?, #inverse_association_for, | |
#inverse_reflection_for | Can be redefined by subclasses, notably polymorphic belongs_to The record parameter is necessary to support polymorphic inverses as we must check for the association in the specific class of the record. |
#invertible_for? | Returns true if inverse association on the given record needs to be set. |
#matches_foreign_key?, | |
#raise_on_type_mismatch! | Raises |
#scope_for_create, | |
#skip_statement_cache? | Returns true if statement cache should be skipped on the association reader. |
#skip_strict_loading, | |
#stale_state | This should be implemented to return the values of the relevant key(s) on the owner, so that when stale_state is different from the value stored on the last find_target, the target is stale. |
#target_scope | Can be overridden (i.e. in |
Constructor Details
.new(owner, reflection) ⇒ HasManyThroughAssociation
# File 'activerecord/lib/active_record/associations/has_many_through_association.rb', line 9
def initialize(owner, reflection) super @through_records = {}.compare_by_identity end
Instance Attribute Details
#target_reflection_has_associated_record? ⇒ Boolean
(readonly, private)
[ GitHub ]
# File 'activerecord/lib/active_record/associations/has_many_through_association.rb', line 121
def target_reflection_has_associated_record? !(through_reflection.belongs_to? && Array(through_reflection.foreign_key).all? { |foreign_key_column| owner[foreign_key_column].blank? }) end
#through_scope (readonly, private)
[ GitHub ]# File 'activerecord/lib/active_record/associations/has_many_through_association.rb', line 69
attr_reader :through_scope
Instance Method Details
#build_record(attributes) (private)
[ GitHub ]# File 'activerecord/lib/active_record/associations/has_many_through_association.rb', line 90
def build_record(attributes) ensure_not_nested @through_scope = scope record = super inverse = if source_reflection.polymorphic? source_reflection.polymorphic_inverse_of(record.class) else source_reflection.inverse_of end if inverse if inverse.collection? record.send(inverse.name) << build_through_record(record) elsif inverse.has_one? record.send("#{inverse.name}=", build_through_record(record)) end end record ensure @through_scope = nil end
#build_through_record(record) (private)
The through record (built with build_record) is temporarily cached so that it may be reused if insert_record is subsequently called.
However, after insert_record has been called, the cache is cleared in order to allow multiple instances of the same record in an association.
# File 'activerecord/lib/active_record/associations/has_many_through_association.rb', line 56
def build_through_record(record) @through_records[record] ||= begin ensure_mutable attributes = through_scope_attributes attributes[source_reflection.name] = record through_association.build(attributes).tap do |new_record| new_record.send("#{source_reflection.foreign_type}=", [:source_type]) if [:source_type] end end end
#concat(*records)
[ GitHub ]# File 'activerecord/lib/active_record/associations/has_many_through_association.rb', line 14
def concat(*records) unless owner.new_record? records.flatten.each do |record| raise_on_type_mismatch!(record) end end super end
#concat_records(records) (private)
[ GitHub ]# File 'activerecord/lib/active_record/associations/has_many_through_association.rb', line 37
def concat_records(records) ensure_not_nested records = super(records, true) if owner.new_record? && records records.flatten.each do |record| build_through_record(record) end end records end
#delete_or_nullify_all_records(method) (private)
[ GitHub ]# File 'activerecord/lib/active_record/associations/has_many_through_association.rb', line 136
def delete_or_nullify_all_records(method) delete_records(load_target, method) end
#delete_records(records, method) (private)
[ GitHub ]# File 'activerecord/lib/active_record/associations/has_many_through_association.rb', line 140
def delete_records(records, method) ensure_not_nested scope = through_association.scope scope.where! construct_join_attributes(*records) scope = scope.where(through_scope_attributes) case method when :destroy if scope.model.primary_key count = scope.destroy_all.count(&:destroyed?) else scope.each(&:_run_destroy_callbacks) count = scope.delete_all end when :nullify count = scope.update_all(source_reflection.foreign_key => nil) else count = scope.delete_all end delete_through_records(records) if source_reflection. [:counter_cache] && method != :destroy counter = source_reflection.counter_cache_column klass.decrement_counter counter, records.map(&:id) end if through_reflection.collection? && update_through_counter?(method) update_counter(-count, through_reflection) else update_counter(-count) end count end
#delete_through_records(records) (private)
[ GitHub ]# File 'activerecord/lib/active_record/associations/has_many_through_association.rb', line 209
def delete_through_records(records) records.each do |record| through_records = through_records_for(record) if through_reflection.collection? through_records.each { |r| through_association.target.delete(r) } else if through_records.include?(through_association.target) through_association.target = nil end end @through_records.delete(record) end end
#difference(a, b) (private)
[ GitHub ]# File 'activerecord/lib/active_record/associations/has_many_through_association.rb', line 177
def difference(a, b) distribution = distribution(b) a.reject { |record| mark_occurrence(distribution, record) } end
#distribution(array) (private)
[ GitHub ]#find_target(async: false) (private)
# File 'activerecord/lib/active_record/associations/has_many_through_association.rb', line 225
def find_target(async: false) raise NotImplementedError, "No async loading for HasManyThroughAssociation yet" if async return [] unless target_reflection_has_associated_record? return scope.to_a if disable_joins super end
#insert_record(record, validate = true, raise = false)
[ GitHub ]# File 'activerecord/lib/active_record/associations/has_many_through_association.rb', line 24
def insert_record(record, validate = true, raise = false) ensure_not_nested if record.new_record? || record.has_changes_to_save? return unless super end save_through_record(record) record end
#intersection(a, b) (private)
[ GitHub ]# File 'activerecord/lib/active_record/associations/has_many_through_association.rb', line 183
def intersection(a, b) distribution = distribution(b) a.select { |record| mark_occurrence(distribution, record) } end
#invertible_for?(record) ⇒ Boolean
(private)
NOTE - not sure that we can actually cope with inverses here
# File 'activerecord/lib/active_record/associations/has_many_through_association.rb', line 233
def invertible_for?(record) false end
#mark_occurrence(distribution, record) (private)
[ GitHub ]# File 'activerecord/lib/active_record/associations/has_many_through_association.rb', line 189
def mark_occurrence(distribution, record) distribution[record] > 0 && distribution[record] -= 1 end
#remove_records(existing_records, records, method) (private)
[ GitHub ]# File 'activerecord/lib/active_record/associations/has_many_through_association.rb', line 116
def remove_records(existing_records, records, method) super delete_through_records(records) end
#save_through_record(record) (private)
[ GitHub ]# File 'activerecord/lib/active_record/associations/has_many_through_association.rb', line 81
def save_through_record(record) association = build_through_record(record) if association.changed? association.save! end ensure @through_records.delete(record) end
#through_records_for(record) (private)
[ GitHub ]# File 'activerecord/lib/active_record/associations/has_many_through_association.rb', line 199
def through_records_for(record) attributes = construct_join_attributes(record) candidates = Array.wrap(through_association.target) candidates.find_all do |c| attributes.all? do |key, value| c.public_send(key) == value end end end
#through_scope_attributes (private)
[ GitHub ]# File 'activerecord/lib/active_record/associations/has_many_through_association.rb', line 71
def through_scope_attributes scope = through_scope || self.scope attributes = scope.where_values_hash(through_association.reflection.klass.table_name) except_keys = [ *Array(through_association.reflection.foreign_key), through_association.reflection.klass.inheritance_column ] attributes.except!(*except_keys) end
#update_through_counter?(method) ⇒ Boolean
(private)
# File 'activerecord/lib/active_record/associations/has_many_through_association.rb', line 125
def update_through_counter?(method) case method when :destroy !through_reflection.inverse_updates_counter_cache? when :nullify false else true end end