Module: ActiveRecord::PostgreSQLExtensions::ForeignKeyAssociations::ClassMethods

Defined in:
lib/active_record/postgresql_extensions/foreign_key_associations.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.extended(base) ⇒ Object



128
129
130
131
132
133
134
# 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

Instance Method Details

#allocate_with_foreign_keysObject

:nodoc:



136
137
138
139
# File 'lib/active_record/postgresql_extensions/foreign_key_associations.rb', line 136

def allocate_with_foreign_keys #:nodoc:
  load_foreign_key_associations if load_foreign_key_associations? && !@foreign_key_associations_loaded
  allocate_without_foreign_keys
end

#dont_load_foreign_key_associations!Object

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.



171
172
173
# 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_keysObject

Returns an Array of foreign keys in this model. See ActiveRecord::Base#foreign_keys for details.



161
162
163
# 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_associationsObject

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.



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
269
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
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# 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?Boolean

Should we load a model’s foreign key associations? Maybe we should, and maybe we shouldn’t.

Returns:

  • (Boolean)


349
350
351
352
353
354
# 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

#new_with_foreign_keys(*args) ⇒ Object

:nodoc:



141
142
143
144
145
146
# File 'lib/active_record/postgresql_extensions/foreign_key_associations.rb', line 141

def new_with_foreign_keys(*args) #:nodoc:
  load_foreign_key_associations if load_foreign_key_associations? && !@foreign_key_associations_loaded
  new_without_foreign_keys(*args) { |*block_args|
    yield(*block_args) if block_given?
  }
end

#referenced_foreign_keysObject

Returns an Array of foreign keys referencing this model. See ActiveRecord::Base#referenced_foreign_keys for details.



155
156
157
# 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

#reflections_with_foreign_keysObject

:nodoc:



148
149
150
151
# File 'lib/active_record/postgresql_extensions/foreign_key_associations.rb', line 148

def reflections_with_foreign_keys #:nodoc:
  load_foreign_key_associations if load_foreign_key_associations? && !@foreign_key_associations_loaded
  reflections_without_foreign_keys
end