84

I have the following two Models:

class Store < ActiveRecord::Base
    belongs_to :person
end

class Person < ActiveRecord::Base
    has_one :store
end

Here is the issue: I am trying to create a migration to create the foreign key within the people table. However, the column referring to the foreign key of Store is not named store_id as would be rails convention but is instead named foo_bar_store_id.

If I was following the rails convention I would do the migration like this:

class AddReferencesToPeople < ActiveRecord::Migration
  def change
    add_reference :people, :store, index: true
  end
end

However this will not work because the column name is not store_id but is foo_bar_store_id. So how do I specify that the foreign key name is just different, but still maintain index: true to maintain fast performance?

Neil
  • 4,578
  • 14
  • 70
  • 155
  • I think a reference, like a foreign key, should be in the model that belongs to another model. Therefore, the migration should show as 'add_reference :store, :person, index: true'. – Graham John Feb 02 '23 at 16:26

7 Answers7

153

in rails 5.x you can add a foreign key to a table with a different name like this:

class AddFooBarStoreToPeople < ActiveRecord::Migration[5.0]
  def change
    add_reference :people, :foo_bar_store, foreign_key: { to_table: :stores }
  end
end

Inside a create_table block

t.references :feature, foreign_key: {to_table: :product_features}
Shiva
  • 11,485
  • 2
  • 67
  • 84
schpet
  • 9,664
  • 6
  • 32
  • 35
  • 1
    @inye it seems like that bug's been fixed – schpet Sep 28 '17 at 18:03
  • 17
    This is one of those seemingly undocumented hacks that makes me both love and hate Rails at the same time. – peelman Jun 12 '18 at 07:19
  • 6
    This also works inside a `create_table` direction like: `t.references :feature, foreign_key: {to_table: :product_features}` – toobulkeh Jun 22 '18 at 02:24
  • Love-Hate relationships are the best, but with right quantities – ARK Aug 27 '20 at 10:23
  • When I do this in MySQL, I get ActiveRecord::MismatchedForeignKey: Column `incoming_location_id` on table `inventory_incomings` does not match column `id` on `addresses`, which has type `bigint(20)`. To resolve this issue, change the type of the `incoming_location_id` column on `inventory_incomings` to be :bigint. – hook38 Nov 08 '20 at 06:42
  • This is documented as an example near the bottom of the `add_reference` section since at least 5.0 https://api.rubyonrails.org/v5.0.0/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_reference – tgf Nov 07 '22 at 05:31
83

In Rails 4.2, you can also set up the model or migration with a custom foreign key name. In your example, the migration would be:

class AddReferencesToPeople < ActiveRecord::Migration
  def change
    add_column :people, :foo_bar_store_id, :integer, index: true
    add_foreign_key :people, :stores, column: :foo_bar_store_id
  end
end

Here is an interesting blog post on this topic. Here is the semi-cryptic section in the Rails Guides. The blog post definitely helped me.

As for associations, explicitly state the foreign key or class name like this (I think your original associations were switched as the 'belongs_to' goes in the class with the foreign key):

class Store < ActiveRecord::Base
  has_one :person, foreign_key: :foo_bar_store_id
end

class Person < ActiveRecord::Base
  belongs_to :foo_bar_store, class_name: 'Store'
end

Note that the class_name item must be a string. The foreign_key item can be either a string or symbol. This essentially allows you to access the nifty ActiveRecord shortcuts with your semantically-named associations, like so:

person = Person.first
person.foo_bar_store
# returns the instance of store equal to person's foo_bar_store_id

See more about the association options in the documentation for belongs_to and has_one.

Weston Ganger
  • 6,324
  • 4
  • 41
  • 39
Sia
  • 8,894
  • 5
  • 31
  • 50
  • 1
    Should ` add_column :people, :foo_bar_store_id, index: true` declare a type? – Chase Sep 23 '16 at 19:21
  • 1
    @alex true - it would be better to have separate up and down methods rather than just one change method. up would probably look the same as the answer's change method, and down would remove the column. – Sia Mar 02 '17 at 00:27
12

To expand on schpet's answer, this works in a create_table Rails 5 migration directive like so:

create_table :chapter do |t|
  t.references :novel, foreign_key: {to_table: :books}
  t.timestamps
end
toobulkeh
  • 1,618
  • 1
  • 14
  • 22
  • 4
    `t.references :novel, foreign_key: {to_table: :books}` is illustrated in [Rails API documentation](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_reference) however it doesn't illustrate or mention additional options available for foreign key. Such options can be found in docs for [add_foreign_key](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_foreign_key) method – Jignesh Gohel Aug 07 '18 at 07:47
  • @JigneshGohel Thank you for linking to the documentation! I hadn't come across this before. – Allison Feb 12 '20 at 23:24
11

EDIT: For those that see the tick and don't continue reading!

While this answer achieves the goal of having an unconventional foreign key column name, with indexing, it does not add a fk constraint to the database. See the other answers for more appropriate solutions using add_foreign_key and/or 'add_reference'.

Note: ALWAYS look at the other answers, the accepted one is not always the best!

Original answer:

In your AddReferencesToPeople migration you can manually add the field and index using:

add_column :people, :foo_bar_store_id, :integer
add_index :people, :foo_bar_store_id

And then let your model know the foreign key like so:

class Person < ActiveRecord::Base
  has_one :store, foreign_key: 'foo_bar_store_id'
end
Matt
  • 13,948
  • 6
  • 44
  • 68
  • 55
    This is not a foreign key in the database. – baash05 Jun 15 '16 at 06:29
  • @baash05 Could you explain your question? – Matt Jun 15 '16 at 08:13
  • 7
    @matt I think what he is getting at, is the question is specifically asking about creating a foreign key in the table (Database level) not just at the Model level. In newer rails, 4.2 I think, a generated migration with references can add the foreign key in the appropriate table, to help ensure data integrity. Having issues with formatting, but I think the relevant code would be something like `add_foreign_key :people, :stores, column: :foo_bar_store_id` – Randy Shelford Jun 27 '16 at 21:08
  • 1
    @RandyShelford Thanks, but I don't think that explains baash05's comment either. My answer includes creating the foreign key in the database via migrate, and handling non-standard foreign keys in the model. What is my answer missing? Your suggestion of `add_foreign_key` was already covered by another answer here. – Matt Jun 28 '16 at 08:34
  • How to add foreign key in data base ?? – Thorin Aug 05 '16 at 05:11
  • 5
    @Matt to clarify what Randy / baash05 were saying, this does not create a foreign key constraint in the database. That is, `foo_bar_store_id` can be set to an invalid store id, and the model will pass all validations. Correct me if I'm wrong, but just by adding that line in the model, rails wont throw a ActiveRecord::InvalidForeignKey: PG::ForeignKeyViolation: ERROR error on saving. – CHawk Sep 09 '16 at 23:27
7
# Migration
change_table :people do |t|
  t.references :foo_bar_store, references: :store #-> foo_bar_store_id
end

# Model
# app/models/person.rb
class Person < ActiveRecord::Base
  has_one :foo_bar_store, class_name: "Store"
end
Richard Peck
  • 76,116
  • 9
  • 93
  • 147
0

Under the covers add_reference is just delegating to add_column and add_index so you just need to take care of it yourself:

add_column :people, :foo_bar_store_id, :integer
add_index :people, :foo_bar_store_id
boulder
  • 3,256
  • 15
  • 21
  • How does the Person model know foo_bar_store_id is actually a foreign key to store_id? – Neil Jan 07 '15 at 00:06
  • 4
    That doesn't belong in the migration but in the association definition in the model. As Matt has mentioned `has_one :store, foreign_key: 'foo_bar_store_id'` – boulder Jan 07 '15 at 17:29
0

foreign key with different column name

add_reference(:products, :supplier, foreign_key: { to_table: :firms })

refer the documentation Docs

Syamlal
  • 714
  • 1
  • 11
  • 20