Module: ActiveRecord::PostgreSQLExtensions::ForeignKeyAssociations

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

Overview

The ForeignKeyAssociations module attempts to automatically create associations based on your database schema by looking at foreign key relationships. It can be enabled by setting the enable_foreign_key_associations configuration option on ActiveRecord::Base to true.

The ForeignKeyAssociations isn’t a replacement for hand-coded associations, as it specifically won’t override any associations you create in your models, but can serve to keep your models a little more up-to-date by using the database itself as a means to creating associations.

Foreign key associations are formed by looking at various system tables in your database and attempting to make sane decisions based on how foreign key relationships and indexes are created. We basically go by the following rules:

  • a foreign key reference will create a belongs_to on the model doing the referencing as well as either a has_one or has_many association on the referenced table. If there is a UNIQUE index on the foreign key column, we use a has_one association; otherwise, we use a has_many.

  • “has_many :through” associations are found using multi-column UNIQUE indexes and existing associations which are either found during the first stages of our process or are pre-existing.

Using PostgreSQL as an example:

CREATE TABLE "foos" (
  id serial NOT NULL PRIMARY KEY
);

CREATE TABLE "bars" (
  id serial NOT NULL PRIMARY KEY,
  foo_id integer NOT NULL REFERENCES "foo"("id")
);

In this case, we will attempt to create the following associations:

# Foo model:
has_many :bars

# Bar model:
belongs_to :foo

If we were to add a UNIQUE index on the foo_id column in bars, we would get a has_one assocation in the foos model:

CREATE TABLE "bars" (
  id serial NOT NULL PRIMARY KEY,
  foo_id integer NOT NULL REFERENCES "foo"("id") UNIQUE
);
# or ALTER TABLE or CREATE UNIQUE INDEX, whatever

Produces the following associations:

# Foo model:
has_one :bars

# Bar model:
belongs_to :foo

We also attempt to do “has_many :through” associations by looking for things like UNIQUE indexes on multiple columns, previously existing associations and model names. For instance, given the following schema:

CREATE TABLE "foos" (
  id serial NOT NULL PRIMARY KEY
);

CREATE TABLE "bars" (
  id serial NOT NULL PRIMARY KEY
);

CREATE TABLE "foo_bars" (
  id serial NOT NULL PRIMARY KEY,
  foo_id integer NOT NULL REFERENCES "foos"("id"),
  bar_id integer NOT NULL REFERENCES "bars"("id"),
  UNIQUE ("foo_id", "bar_id")
);

Would create the following associations:

# FooBar model:
belongs_to :foo
belongs_to :bar

# Foo model:
has_many :foo_bars
has_many :bars, :through => :foo_bars

# Bar model:
has_many :foo_bars
has_many :foos, :through => :foo_bars

The rules for association creation through foreign keys are fairly lax, i.e. you don’t need to name your keys “something_id” as Rails generally demands by default. About the only thing that would really help us find foreign key associations is the naming used by your models: if they don’t match up with the Rails conventions for model-to-table mapping (pluralization, underscores, etc.), we can get confused and may miss some associations. The associations will eventually be created once all of your models are loaded, but as of Rails 2.0 we can’t guarantee when and if all of your models will load before we try to find our foreign keys, so bear that in mind when using this plugin. The only time this really comes up is when you’re using set_table_name in a model to override the Rails conventions and we can’t figure that out during our foreign key hunt.

Note that this plugin will never try to override existing associations. If you have an existing association with the same name as one that we are trying to create (or for that matter, a method with the same name) then we will just silently and happily skip that association.

Portions of this plugin were inspired by the RedHill on Rails plugins available at www.redhillonrails.org/. The idea is basically the same in both cases, although our implementations are rather different both in terms of structure and functionality, as this plugin is more specific to our particular needs.

Defined Under Namespace

Modules: ClassMethods

Class Method Summary collapse

Class Method Details

.included(base) ⇒ Object



123
124
125
# File 'lib/active_record/postgresql_extensions/foreign_key_associations.rb', line 123

def self.included(base)
  base.extend(ClassMethods)
end