module ActiveRecord::PostgreSQLExtensions::ForeignKeyAssociations::ClassMethods
Public Class Methods
# File lib/active_record/postgresql_extensions/foreign_key_associations.rb, line 128 def self.extended(base) class << base alias_method_chain :allocate, :foreign_keys alias_method_chain :new, :foreign_keys alias_method_chain :reflections, :foreign_keys end end
Public Instance Methods
Allows you to selectively disable foreign key association loading when the ActiveRecord
setting enable_foreign_key_associations is enabled. This works on a per-model basis, and prevents any foreign key associations from being created on this model. This applies to both foreign keys that reference this model as well as foreign keys within the model itself.
# File lib/active_record/postgresql_extensions/foreign_key_associations.rb, line 171 def dont_load_foreign_key_associations! @load_foreign_key_associations = false end
Returns an Array of foreign keys in this model. See ActiveRecord::Base#foreign_keys for details.
# File lib/active_record/postgresql_extensions/foreign_key_associations.rb, line 161 def foreign_keys @foreign_keys ||= connection.foreign_keys(table_name, "#{name} Foreign Keys") end
Creates foreign key associations for the model. This is essentially a three-step process:
-
Find any tables that reference this model via foreign keys and create the associations accordingly.
-
Find any foreign keys in this model and create the associations accordingly. This process creates both belongs_to associations on this model to the referenced models as well as has_many/has_one associations on the referenced models themselves. To determine whether the association is a has_many or a has_one, we take a look at UNIQUE indexes created on the table column. In cases where the index is UNIQUE, we create a has_one association; in all others, we create a has_many association.
-
Look at the model itself and try to determine whether or not we have a “has_many :through” association. We make this determination by looking to see if there are two foreign keys with the following conditions:
-
the model has an index with exactly two columns in it and the index itself is UNIQUE;
-
we’ve already created a belongs_to association with each column and the column names match the columns in the UNIQUE index; and
-
the model name is either “FirstModelSecondModel” or “SecondModelFirstModel”.
If these criteria match, then the “has_many :through” associations are created on both of the referenced models.
-
In all cases, we respect any dont_load_foreign_key_associations! settings on individual models as well as take into account existing associations with the same names as the ones we’re going to try to create. In other words, if you already have an association called :listings on a model and we find a foreign key that matches, we won’t blow away your existing association and instead just continue along merrily.
# File lib/active_record/postgresql_extensions/foreign_key_associations.rb, line 210 def load_foreign_key_associations return if @foreign_key_associations_loaded @foreign_key_associations_loaded = true indexes = connection.indexes(table_name, "#{name} Indexes") # This does the associations for the tables that reference # columns in this table. referenced_foreign_keys.each do |fk| begin referencing_class = compute_type(fk[:table].classify) referencing_class.load_foreign_key_associations if referencing_class.load_foreign_key_associations? rescue NameError # Do nothing. We won't bother creating associations # if the model class itself doesn't exist. end end # This does the foreign key associations for this model. foreign_keys.each do |fk| belongs_to_association_id = fk[:table].singularize.to_sym begin references_class_name = fk[:table].classify references_class = compute_type(references_class_name) unless method_defined?(belongs_to_association_id) belongs_to( belongs_to_association_id, :class_name => references_class_name, :foreign_key => fk[:column] ) end # If we have a unique index for this column, we'll # create a has_one association; otherwise, it's a # has_many. if indexes.detect { |i| i.columns.length == 1 && i.unique && i.columns.include?(fk[:column]) } has_association_id = self.name.demodulize.underscore.to_sym unless references_class.method_defined?(has_association_id) references_class.has_one( has_association_id, { :class_name => name, :foreign_key => fk[:column] } ) end else has_association_id = self.name.demodulize.underscore.pluralize.to_sym unless references_class.method_defined?(has_association_id) references_class.has_many( has_association_id, { :class_name => name, :foreign_key => fk[:column] } ) end end rescue NameError # Do nothing. NOTHING! We don't want to create # associations on non-existent classes. end end # If we have an index that contains exactly two columns and # it's a UNIQUE index, then we might have a # "has_many :through" association, so let's look for it now. if through = indexes.detect { |i| i.columns.length == 2 && i.unique } catch :not_a_has_many_through do hmt_associations = [] # This will loop through the columns in the UNIQUE # index and see if they're both foreign keys # referencing other tables. through.columns.each do |c| if foreign_keys.detect { |fk| fk[1] == c }.blank? throw(:not_a_has_many_through) end # Check that both columns have belongs_to # associations. unless hmt_association = reflections.detect { |r, v| v.macro == :belongs_to && v.primary_key_name == c } throw(:not_a_has_many_through) end hmt_associations << hmt_association end hmt_first = hmt_associations.first hmt_second = hmt_associations.last hmt_first_association_id = hmt_second.first.to_s.pluralize.to_sym hmt_second_association_id = hmt_first.first.to_s.pluralize.to_sym hmt_first_class = hmt_first.last.name.constantize hmt_second_class = hmt_second.last.name.constantize # Check to see if this model is named # "FirstModelSecondModel" or "SecondModelFirstModel". if strict_foreign_key_has_many_throughs unless [ "#{hmt_first_class}#{hmt_second_class}", "#{hmt_second_class}#{hmt_first_class}" ].include?(self.name) throw(:not_a_has_many_through) end end # If we haven't thrown up, we can create the # associations, assuming they don't already exist and # we're allowed to. through_association_id = self.name.demodulize.underscore.pluralize.to_sym if hmt_first_class.load_foreign_key_associations? unless hmt_first_class.method_defined?(hmt_first_association_id) hmt_first_class.has_many( hmt_first_association_id, :through => through_association_id ) end end if hmt_second_class.load_foreign_key_associations? unless hmt_second_class.method_defined?(hmt_second_association_id) hmt_second_class.has_many( hmt_second_association_id, :through => through_association_id ) end end end end end
Should we load a model’s foreign key associations? Maybe we should, and maybe we shouldn’t.
# File lib/active_record/postgresql_extensions/foreign_key_associations.rb, line 349 def load_foreign_key_associations? ActiveRecord::Base.enable_foreign_key_associations && !abstract_class? && ( @load_foreign_key_associations.nil? || @load_foreign_key_associations ) end
Returns an Array of foreign keys referencing this model. See ActiveRecord::Base#referenced_foreign_keys for details.
# File lib/active_record/postgresql_extensions/foreign_key_associations.rb, line 155 def referenced_foreign_keys @referenced_foreign_keys ||= connection.referenced_foreign_keys(table_name, "#{name} Referenced Foreign Keys") end