Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to force a has_many association using a different column as foreign key

Stuck in a - at first sight - simple problem in RoR. I am sure that is easy, but none answer here in SO helped me too much.

I have two ActiveRecord models: Foo has many Bars:

class Foo < ApplicationRecord
    has_many :bars
end

class Bar < ApplicationRecord
  belongs_to :foo
end

That works like a charm. But I would like to use another field of Foo as foreign_key. The default is foo_id I would like to use custom_id as my foreign key. So I tried this (as many solutions over the web suggested):

class Foo < ApplicationRecord
    has_many :bars, :foreign_key => 'custom_id', :class_name => 'Bars'
end

class Bars < ApplicationRecord
  belongs_to :foo, :class_name => 'Foo'
end

But that doesn't work. i.e. ActiveRecord keeps binding Foo to Bars using foo_id.

Note: Including a self.primary_key='custom_id' in Foo would partially work. but I don't think that is a good idea. I want to keep foo_id as the primary key

UPDATE:

Given the feedback -Thank you guys-, I uploaded that example here https://github.com/montenegrodr/temporary_repository_ror :

  • Bar model
  • Foo model
  • ** This test is expected to PASS but it FAILS.**

UPDATE #2:

The answers does not satisfy the question above. Why the test is failing, my assumption is that it shouldn't fail.

UPDATE #3:

There're a couple of new answers I still need to assess. Will do that within 24 hours. Thanks.

UPDATE #4:

Thank you guys for all the answers. But none of them satisfied the criteria. I need to have that test passing. If it is not possible, can someone explain why? Is it a rails constraint?

like image 872
Montenegrodr Avatar asked Oct 25 '18 16:10

Montenegrodr


Video Answer


1 Answers

You need to specify a different primary key for the relationship if you wish to achieve what you are looking to do.

To clarify, this is not the same as changing the primary_key of the model. This way is only changing the primary key used by the relationship. Please see the bottom of this post for examples.

I changed the keys from both using custom_id and changed one to foo_id. This way you have a better idea of what is going on between the models. You can use both custom_id if you wish, but I would suggest keeping the rails norm of foo_id for the belongs_to association.

If you want to use both of custom_id you'll have to add some specific foreign_keys


Here are the models:

Foo

class Foo < ApplicationRecord
  has_many :bars,
           primary_key: :custom_id, 
           foreign_key: :foo_id
end

Bar

class Bar < ApplicationRecord
  belongs_to :foo, 
             primary_key: :custom_id
end

The Migrations

CreateFoos

class CreateFoos < ActiveRecord::Migration[5.2]
  def change
    create_table :foos do |t|

      t.integer :custom_id, index: {unique: true}
      t.timestamps
    end
  end
end

CreateBars

class CreateBars < ActiveRecord::Migration[5.2]
  def change
    create_table :bars do |t|

      t.integer :foo_id, index: true
      t.timestamps
    end
  end
end

Here is the updated Test that should now be passing:

Test

require 'test_helper'

class BarTest < ActiveSupport::TestCase
  test "the truth" do
    foo = Foo.new(id: 1, custom_id: 100)
    bar = Bar.new(foo: foo)

    assert bar.foo_id == foo.custom_id
    # bar.foo_id    = 100
    # foo.custom_id = 100
  end
end

Examples

Foo.find(1) #<Foo id: 1, custom_id: 100>
Bar.first #<Bar id: 1, foo_id: 100>

Bar.first.foo = #<Foo id: 1, custom_id: 100>
Bar.first.foo == Foo.find(1) # true

As you can see, this method does not change the primary key of Foo itself. It changes the primary key the relationship between Foo and Bar uses. Bar is realated to foo via custom_id: 100, but foo is still found with it's id: 1 key, not its custom_id key.

like image 152
Kyle Ratliff Avatar answered Oct 21 '22 01:10

Kyle Ratliff