Given a school model and a student model with the school having a has_many relation to student:
has_many :students, :conditions => proc { "year_id=#{send(:active_year_id)}" }
where active_year_id is a method defined in the school model, I'm encountering an error that "active_year_id is undefined" when calling:
School.where(:active => true).includes(:students)
The condition works fine when I do, say,
School.where(:id => 10).students
Only when I try to use includes do I get that error. Is that the right behavior. If not, what am I doing wrong and how do I fix ?
Using Rails 3.0.9, REE 1.8.7.
This is a bit old, but I see the question a lot and have not seen any satisfactory solutions. Adding a condition like this is essentially equivalent to creating an association with two public/private keys.
@Fabio is right in saying "the context in where the proc is executed is different depending of how it's called." However I think you can overcome the "active_year_id is undefined" problem.
In the example:
class School < ActiveRecord::Base
has_many :students, :conditions => proc { "year_id=#{send(:active_year_id)}" }
...
the issue is that in some situations, the proc is executed in the context of a particular School object and sometimes as an ActiveRecord::Associations::JoinDependency::JoinAssociation. I solved this using a slightly more complicated proc, like this:
class School < ActiveRecord::Base
has_many :students, :conditions => proc {
self.respond_to?(:active_year_id) ?
{year_id: self.active_year_id} :
'students.year_id = schools.active_year_id'
}
So, when the condition is calculated for an actual school object, self responds to the active_year_id attribute accessor and you can provide a hash as the condition (which works more nicely than just an interpolated string for creating associated objects, etc.)
When the context does not present self as an actual school object, (which, as you noted, occurs when called using an include clause, for example,) the context is a JoinAssociation and the string form of the condition works just fine using field names rather than values.
We found this solution let us use the dynamic association successfully.
I think this is not possible to achieve because the context in where the proc is executed is different depending of how it's called. I've made a basic app with your models and this is what happens when you call the various methods (ap is this):
class School < ActiveRecord::Base
has_many :students, :conditions => proc { ap self; "year_id=#{send(:active_year_id)}" }
end
When you call the students relation from a school instance the context of the proc is the given School
instance so it does respond to the active_year_id
method
[31] pry(main)> School.first.students
School Load (0.2ms) SELECT "schools".* FROM "schools" LIMIT 1
#<School:0x007fcc492a7e58> {
:id => 1,
:name => "My school",
:active_year_id => 1,
:year_id => 1,
:created_at => Tue, 08 May 2012 20:15:19 UTC +00:00,
:updated_at => Tue, 08 May 2012 20:15:19 UTC +00:00
}
Student Load (0.2ms) SELECT "students".* FROM "students" WHERE "students"."school_id" = 1 AND (year_id=1)
+----+----------------+-----------+---------+-------------------------+-------------------------+
| id | name | school_id | year_id | created_at | updated_at |
+----+----------------+-----------+---------+-------------------------+-------------------------+
| 1 | My student | 1 | 1 | 2012-05-08 20:16:21 UTC | 2012-05-08 20:16:21 UTC |
| 2 | Second student | 1 | 1 | 2012-05-08 20:18:35 UTC | 2012-05-08 20:18:35 UTC |
+----+----------------+-----------+---------+-------------------------+-------------------------+
2 rows in set
But when you call the includes relation the context is different and what the proc receive as self is Student
class, so it doesn't respond to that method and this will trigger the error
[32] pry(main)> School.includes(:students).all
School Load (0.3ms) SELECT "schools".* FROM "schools"
class Student < ActiveRecord::Base {
:id => :integer,
:name => :string,
:school_id => :integer,
:year_id => :integer,
:created_at => :datetime,
:updated_at => :datetime
}
NoMethodError: undefined method `active_year_id' for #<Class:0x007fcc4a6a3420>
from /Users/fabio/.rvm/gems/ruby-1.9.3-p194/gems/activerecord-3.2.3/lib/active_record/dynamic_matchers.rb:50:in `method_missing'
I think that a has_many relation can't be used with that kind of proc which relies on an instance method of a School instance. I think that the only way to use procs as described here is to compute some condition at runtime which doesn't involve instance methods (time conditions, where with data from unrelated models and so on).
Moreover the School.includes(:students).all
in my example cannot work because it should call the active_year_id
method on every instance of School
(which should be retrieved from db before the includes could be evaluated) and thus vanishing the effect of the includes
intended behavior.
All of this is valid if the active_year_id
is a computed method defined in School
class based on instance data. Instead if the active_year_id
is not a method but a field (a db column) of the School
class you can play with joins and scopes to achieve a result similar to what you want to achieve but it should be coded by hand.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With