module ActiveRecord::PostgreSQLExtensions::ForeignKeyAssociations::ClassMethods

Public Class Methods

extended(base) click to toggle source
# 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

dont_load_foreign_key_associations!() click to toggle source

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
foreign_keys() click to toggle source

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
load_foreign_key_associations() click to toggle source

Creates foreign key associations for the model. This is essentially a three-step process:

  1. Find any tables that reference this model via foreign keys and create the associations accordingly.

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

  3. 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
load_foreign_key_associations?() click to toggle source

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
referenced_foreign_keys() click to toggle source

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