Rails - Including associations with dynamic conditions

Given the school model and the student model with the school related has_many to the student:

has_many :students, :conditions => proc { "year_id=#{send(:active_year_id)}" } 

where active_year_id is the method defined in the school model, I encounter the error that "active_year_id is undefined" when called:

 School.where(:active => true).includes(:students) 

The condition works fine when I, say,

 School.where(:id => 10).students 

Only when I try to use, I get this error. This is the correct behavior. If not, what am I doing wrong and how can I fix it?

Using Rails 3.0.9, REE 1.8.7.

+7
source share
4 answers

This is a bit outdated, but I often see this question and do not see satisfactory solutions. Adding this condition is essentially equivalent to creating a relationship with two public / private keys.

@Fabio is right in saying: "the context in which proc is executed is different from what it is 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 problem is that in some situations, proc is executed in the context of a particular School object, and sometimes as ActiveRecord :: Associations :: JoinDependency :: JoinAssociation. I solved this using a slightly more complex process, for example:

 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 the actual school object, self responds to the active_year_id attribute, and you can provide the hash as a condition (which works more nicely than just an interpolated string to create related objects, etc.)

If the context does not represent self as the actual school object (which, as you noted, occurs when called using the include clause, for example), context is JoinAssociation, and the string form of the condition works just fine, using field names, not values.

We found this solution, we are successfully using dynamic association.

+7
source

I think this is not possible to achieve, because the context in which proc is executed differs depending on what it is called. I made a basic application with your models, and this is what happens when you call various methods ( ap this ):

 class School < ActiveRecord::Base has_many :students, :conditions => proc { ap self; "year_id=#{send(:active_year_id)}" } end 

When you invoke student attitudes from a school instance, the proc context is that instance of School , so it responds 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 include relation, the context is different and what proc gets as self is a Student class, so it does not respond to this method, and this will throw an 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 the has_many relationship cannot be used with a kind of proc that relies on the instance method of the School instance. I think the only way to use procs as described here is to calculate some condition at runtime that does not include instance methods (temporary conditions, where with data from unrelated models, etc.).

In addition, School.includes(:students).all in my example cannot work, because it must call the active_year_id method for each instance of School (which must be extracted from db before inclusions can be evaluated) and thus , the effect of the intended behavior includes .

All this is true if active_year_id is a computed method defined in the School class based on instance data. Instead, if active_year_id not a method, but a field (db column) of the School class, you can play with join and scopes to achieve a result similar to what you want to achieve, but you need to code it manually.

+3
source

This should be a comment, but not enough space.

So:

 School.where(:id => 10).students 

AND:

 School.where(:active => true).includes(:students) 

These are two completely different things.

The first is the same as School.find(10).students , where it returns the found students to schools.

The second School.where(:active => true).includes(:students) completely different. You find schools with the value active == true and including students , but it does nothing with students. He only returns to school.

What are you trying to do?

0
source

FYI, you can access the current collection that include calls ... this may help in some cases:

 belongs_to :ticket, -> (t) { where(account_id: (t || self.first).account_id) } 
0
source

All Articles