Jens
Jens

Reputation: 1397

Rails 4 and referencing parent id in 'has_many' SQL

TL;DR: How do I use the ID of the respective parent object in a has_many SQL clause to find child objects?

Long version: I have the following example code:

class Person < AR::Base
  has_many :purchases, -> { 
    "SELECT * from purchases
      WHERE purchase.seller_id = #{id}
         OR purchase.buyer_id = #{id}"
  }

This was migrated from Rails 3 which worked and looked like

  has_many :purchases, :finder_sql => proc { #same SQL as above# }

I want to find all purchases associated with a Person object in one association, no matter whether the person was the one selling the object or buying it.

Update: I corrected the SQL, it was inside out. Sorry! Also: The association only needs to be read-only: I am never going to create records using this association, so using id twice should be OK. But I do want to be able to chain other scopes on it, e.g. @person.purchases.paid.last_5, so creating the sum of two associations in a method does not work (at least it didn't in Rails 3) since it doesn't return an AM::Relation but a simple Array.

When using this above definition in Rails 4.2, I get

> Person.first.purchases
undefined method `id' for #<Person::ActiveRecord_Relation:0x...>

The error is clear, but then how do I solve the problem?

Since this is only an example for much more complicated SQL code being used to express has_many relationships (e.g. multiple JOINS with subselects for performance), the question is:

How do I use the ID of the parent object in a has_many SQL clause?

Upvotes: 1

Views: 693

Answers (1)

dgilperez
dgilperez

Reputation: 10796

I don't think your code will work at all. You are defining an association with two foreign keys ... that'd mean that in case you want to create a new Person from a present Purchase, what foreign key is to be used, seller_id or buyer_id? That just don't make sense.

In any case, the error you are getting is clear: you are calling a variable id which is not initialized in the block context of the SQL code.

A better approach to the problem I understand from your question would be to use associations in the following way, and then define a method that gives you all the persons, both buyers and sellers that a product has. Something like this:

class Purchase < ActiveRecord::Base
  belongs_to :buyer, class_name: 'Person'
  belongs_to :seller, class_name: 'Person'

  def persons
    ids = (buyer_ids + seller_ids).uniq
    Person.where(ids: id)
  end 
end

class Person < ActiveRecord::Base
  has_many :sold_purchases, class_name: 'Purchase', foreign_key: 'buyer_id'
  has_many :buyed_purchases, class_name: 'Purchase', foreign_key: 'seller_id'
end

Im my approach, buyer_id and seller_id are purchase's attributes, not person's.

I may have not understood correctly, in that case please clarify.

Upvotes: 2

Related Questions