ActiveRecord, find by polymorphic attribute
Asked Answered
A

3

6

Having this:

class Event < ActiveRecord::Base
  belongs_to :historizable, :polymorphic => true
end

user = User.create!

I can:

Event.create!(:historizable => user)

But I can't:

Event.where(:historizable => user)
# Mysql2::Error: Unknown column 'events.historizable' in 'where clause'

I have to do this instead:

Event.where(:historizable_id => user.id, :historizable_type => user.class.name)

Update

Code that reproduces the issue: https://gist.github.com/fguillen/4732177#file-polymorphic_where_test-rb

Allhallows answered 7/2, 2013 at 14:35 Comment(2)
I know I can do user.events if I have the has_many properly configured but I'm looking for the where possibility.Allhallows
Just opened a bug in the rails project to see what they think: github.com/rails/rails/issues/9227Allhallows
A
3

This has been implemented in Rails master and will be available in Rails 4. Thanks.

@carlosantoniodasilva

Allhallows answered 8/2, 2013 at 17:15 Comment(0)
T
0

I do this:

user.events

This is a proper AR query, you can chain it with other scopes and stuff:

user.events.where(<your event conditions here>)

EDIT: AFAIK the other way around you must specify both fields (makes sense: you could have a user with id 4 and another thing with events, like a Party, also with id 4).

EDIT2: Regarding "Why does create work and where doesn't": create is more highlevel than where, because the former deals with "a complete model", while the latter manages things at the database table level.

  1. ActiveRecord's create (AFAIK) uses a combination of new + update_param somewhere down the line.
  2. update_param uses your model's xxxx= methods for assigning each individual property.
  3. In your example, historizable= is a method built by the belongs_to expression. Since the belongs_to "knows" that it's polymorphic, it can deal with the type and id.

On the other hand, when you pass a hash to the where clause, the parameters there only refer to database fields. Rails provides scopes for "higher level" access:

class Event < ActiveRecord::Base
  ...

  scope :by_historizable, lambda { |h| where(:historizable_id => h.id, :historizable_type => h.class.name) }

end

...

Event.by_historizable(user).where(<your other queries here>)

I've heard that this might change in Rails 4, and where might be more "intelligent". But I have not checked yet.

Tenuous answered 7/2, 2013 at 17:54 Comment(5)
Hola @kikito, check my comment in the question, I was aware I can use the User has_many association to obtain the same result. But in my context this is not a possibility, I'm building a gem and I'm in charge of the Event implementation but not for the historizable object implementation which is out of my code so I can't blindly trust that the has_many has been properly defined.Allhallows
About why do you need to specify both fields in the where but it works with only one in the create has not sense to me. The same magic used in one method could be used in the oposite one.Allhallows
Hola @fguillen, my first EDIT was my answer to your comment in the OP. I've added EDIT2 in response to your comments top this answer.Tenuous
Thanks for the effort in the answer, but still my concerns remain. I don't want to use the double param where because it is fragile due it is using not documented premises, like for example that the _type param is populated with Class.name, it looks like it is populated with Class.name.to_s.camelize: #8350537 (the result is the same, but could be not)Allhallows
"This has been implemented in Rails master and will be available in Rails 4. Thanks." @carlosantoniodasilvaAllhallows
H
-2

Try:

Event.joins(:historizable).where(:historizable => {:historizable_type => user})
Hake answered 7/2, 2013 at 15:1 Comment(1)
Can not eagerly load the polymorphic association :historizableAllhallows

© 2022 - 2024 — McMap. All rights reserved.