Joe Van Dyk
Joe Van Dyk

Reputation: 6950

ActiveRecord has_many with custom foreign key sql expression

In ActiveRecord, a has_many relationship uses a foreign key column to load the association. So:

class Person < ActiveRecord::Base
  has_many :messages
end

class Messages < ActiveRecord::Base
  belongs_to :person
end

Person.limit(10).includes(:messages) 
# select * from persons limit 10;
# select messages.* from messages where person_id in (1, 2, 3...)

I have a case (and I've seen other people ask for this as well) where I don't want Rails to automatically append the foreign key check to the where clause. Instead, I might want something like:

class Person < ActiveRecord::Base
  has_many :messages, 
           :foreign_key => false, 
           :conditions => proc { ["person_id is null or person_id = ?", self.id] }
end

Person.limit(10).includes(:messages)
# select messages.* from messages where person_id is null or person_id in (1, 2, 3...)

How can I do this? To sum up, I don't want ActiveRecord automatically appending the foreign key in the WHERE clause, I want to be able to specify the expression it uses for the association.

I don't want to do this:

class Person < ActiveRecord::Base
  def messages
    Message.where("person_id is null or person_id = #{ self.id }")
  end
end

as that would break eager loading, as far as I know.

I also don't want to use the finder_sql option to has_many, as that would break things like person.messages.where(:id => 1) or Person.limit(10).includes(:messages => :image)

Upvotes: 3

Views: 1723

Answers (1)

Wizard of Ogz
Wizard of Ogz

Reputation: 12643

Something like this works for me on Rails 3.2.1:

class Person < ActiveRecord::Base
  has_many :messages, :finder_sql => lambda{ "SELECT * FROM messages WHERE messages.person_id=#{id} OR messages.person_id IS NULL" }
end

# person = Person.includes(:messages).first
# person.messages.loaded?  #=> true

Upvotes: 1

Related Questions