Courtland Caldwell
Courtland Caldwell

Reputation: 578

ActiveRecord has_many through a scoped association with arg

I've seen a number of answers to questions that address how to use scope blocks in ActiveRecord associations that include passing the object itself into the block like ...

class Patron < ActiveRecord::Base
  has_many :bars, 
           ->(patron) { baz: patron.blah },
           foreign_key: :somekey,
           primary_key: :somekey
end

class Bar < ActiveRecord::Base
  belongs_to :patron,
             ->(bar) { blah: bar.baz },
             foreign_key: :somekey,
             primary_key: :somekey
end

The usage of the explicit PK and FK here is due to the legacy relationship between the underlying tables. There are many hundreds of millions of "patrons" in the production system.

As a point of clarification re @TJR - the relationship between Patron and Bar is actual a compound foreign key on the fields :somekey and :baz (or :blah in the reverse direction). ActiveRecord's :foreign_key option doesn't allow arrays.

I've discovered that unfortunately this prevents subsequent has_many :throughs from working as expected.

class Patron < ActiveRecord::Base
  has_many :bars,
           ->(patron) { baz: patron.blah },
           foreign_key: :somekey,
           primary_key: :somekey
  has_many :drinks, through: :bars
end

Using the through association produces errors like ...

ArgumentError: wrong number of arguments (0 for 1)

The association between bar and drinks is a classic has_many with the standard foreign and primary key (bar_id, id).

I have come up with some ugly work arounds that allow me to accomplish the desired functionality but the whole thing smells terrible. So far the best of these look like

class Patron < ActiveRecord::Base
  has_many :bars, 
           ->(patron) { baz: patron.blah },
           foreign_key: :somekey,
           primary_key: :somekey
  def drinks
    bars.drinks
  end
end

I've received the existing Bar class and it consists of many hundreds of millions of records, as I previously mentioned, making a database side change difficult.

Some posts seemed to suggest a dynamic string evaluation inside the proc to avoid having to pass in the current patron object - but as mentioned in other posts, this doesn't work.

Please advise me on what I might do to get the has_many through relationship working.

Upvotes: 5

Views: 616

Answers (1)

Ilya Lavrov
Ilya Lavrov

Reputation: 2860

I just have tried this kind of associations in Rails 4.2. It works pretty well:

class Patron < ActiveRecord::Base
  has_many :bars,
           ->(patron) { where(baz: patron.blah) },
           foreign_key: :patron_id,
           primary_key: :id

  has_many :drinks, through: :bars
end

class Bar < ActiveRecord::Base
  belongs_to :patron,
             ->(bar) { where(blah: bar.baz) },
             foreign_key: :patron_id,
             primary_key: :id

  has_many :drinks
end

class Drink < ActiveRecord::Base
end

Check associations:

> p1 = Patron.first
> p1.drinks
  Drink Load (0.8ms)  SELECT "drinks".* FROM "drinks" INNER JOIN "bars" ON "drinks"."bar_id" = "bars"."id" WHERE "bars"."patron_id" = 1 AND "bars"."baz" = 1  [["patron_id", 1], ["baz", 1]]
 => #<ActiveRecord::Associations::CollectionProxy [#<Drink id: 3, name: "drink 3", bar_id: 2, created_at: "2017-04-07 03:30:06", updated_at: "2017-04-07 03:30:06">]>

Upvotes: 0

Related Questions