考虑到学校模式和学生模型,学校与学生有很多关系:
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
只有当我尝试使用include时才会出现错误。这是正确的行为吗?如果没有,我做错了什么,我该如何解决?
使用Rails 3.0.9,REE 1.8.7。
答案 0 :(得分:8)
这有点旧,但我看到了很多问题,并没有看到任何令人满意的解决方案。添加这样的条件基本上等同于创建与两个公钥/私钥的关联。
@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 子句调用时), context是一个JoinAssociation,条件的字符串形式可以正常使用字段名而不是值。
我们发现这个解决方案让我们成功地使用了动态关联。
答案 1 :(得分:5)
我认为这是不可能实现的,因为执行proc的上下文取决于它的调用方式。我已经为您的模型制作了一个基本的应用程序,当您调用各种方法(ap is this)时会发生这种情况:
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
#<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
但是当你调用include关系时,上下文不同,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 #<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'
我认为has_many关系不能用于那种依赖于School实例的实例方法的proc。我认为将procs用作described here的唯一方法是在运行时计算一些不涉及实例方法的条件(时间条件,来自不相关模型的数据等等)。
此外,我的示例中的School.includes(:students).all
无法正常工作,因为它应该在 active_year_id
的每个实例上调用School
方法(应该先从db中检索)可以评估包含,从而消除includes
预期行为的影响。
如果active_year_id
是基于实例数据的School
类中定义的计算方法,则所有这些都有效。相反,如果active_year_id
不是School
类的方法而是字段(数据库列),则可以使用joins和scopes来实现类似于你想要实现,但它应该手工编码。
答案 2 :(得分:0)
这应该是一个评论,但没有足够的空间。
所以:
School.where(:id => 10).students
并且:
School.where(:active => true).includes(:students)
两件完全不同的事情。
第一个与School.find(10).students
相同,它返回找到的学校学生。
第二个School.where(:active => true).includes(:students)
完全不同。您正在寻找价值为active == true
并且包括students
的学校,但这对学生没有任何帮助。它只会让学校回归。
你想做什么?
答案 3 :(得分:0)
仅供参考,您可以访问当前集合,调用包含...在某些情况下这可能会有所帮助:
belongs_to :ticket, -> (t) { where(account_id: (t || self.first).account_id) }