Rails – 包括与动态条件的关联

考虑到学校模式和学生模型,学校与学生有很多关系:

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

其中active_year_id是学校模型中定义的方法,我在调用时遇到“active_year_id未定义”的错误:

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

当我这样做时,情况正常

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

只有当我尝试使用包含时才会出现错误。 这是正确的行为吗? 如果没有,我做错了什么,我该如何解决?

使用Rails 3.0.9,REE 1.8.7。

这有点旧,但我看到了很多问题,没有看到任何令人满意的解决方案。 添加这样的条件基本上等同于创建与两个公钥/私钥的关联。

@Fabio正确地说“ 执行proc的上下文取决于它的调用方式。 ”但我认为你可以克服“active_year_id未定义”的问题。

在示例中:

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

问题是在某些情况下,proc在特定的School对象的上下文中执行,有时作为ActiveRecord :: Associations :: JoinDependency :: JoinAssociation执行。 我使用稍微复杂的过程解决了这个问题,如下所示:

 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' } 

因此,当为实际学校对象计算条件时, self会响应active_year_id属性访问器,并且您可以提供哈希作为条件(它比仅用于创建关联对象的插值字符串更好地工作等)

当上下文没有将self呈现为实际的学校对象时(例如,正如您所指出的那样,当使用include子句调用时),上下文是JoinAssociation,并且条件的字符串forms使用字段名称可以正常工作而不是价值观。

我们发现这个解决方案让我们成功地使用动态关联。

我认为这是不可能实现的,因为执行proc的上下文取决于它的调用方式。 我已经用你的模型做了一个基本的应用程序,当你调用各种方法时会发生这种情况( ap是这个 ):

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

当您从学校实例调用学生关系时,proc的上下文是给定的School实例,因此它确实响应active_year_id方法

 [31] pry(main)> School.first.students School Load (0.2ms) SELECT "schools".* FROM "schools" LIMIT 1 # { :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 

但是当你调用包含关系时,上下文是不同的,并且proc作为self接收的是Student类,所以它不响应该方法,这将触发错误

 [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 # 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' 

我认为has_many关系不能用于那种依赖于School实例的实例方法的proc。 我认为这里描述的使用过程的唯一方法是在运行时计算一些不涉及实例方法的条件(时间条件,来自不相关模型的数据等等)。

此外, School.includes(:students).all在我的示例中无法工作,因为它应该在active_year_id 每个实例上调用active_year_id方法(应该在评估包含之前从db检索),从而消除includes的影响预期的行为。

如果active_year_id是基于实例数据在School类中定义的计算方法,则所有这些都是有效的。 相反,如果active_year_id不是一个方法而是一个School类的字段(db列),您可以使用连接和范围来实现类似于您想要实现的结果,但应该手动编码。

这应该是一个评论,但没有足够的空间。

所以:

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

和:

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

是两件完全不同的事情。

第一个与School.find(10).students ,它返回找到的学校学生。

Second School.where(:active => true).includes(:students)是完全不同的。 您正在寻找具有值active == true并且包括students但这对学生没有任何作用。 它只返回学校。

你想做什么?

仅供参考,您可以访问当前集合,调用包含…在某些情况下这可能会有所帮助:

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