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

Vij*_*Dev 8 activerecord ruby-on-rails ruby-on-rails-3

给定学校模型和学生模型,学校与学生有很多关系:

has_many :students, :conditions => proc  { "year_id=#{send(:active_year_id)}" }
Run Code Online (Sandbox Code Playgroud)

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

School.where(:active => true).includes(:students)
Run Code Online (Sandbox Code Playgroud)

当我这么做时,情况正常

School.where(:id => 10).students
Run Code Online (Sandbox Code Playgroud)

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

使用Rails 3.0.9,REE 1.8.7.

Tom*_*son 9

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

@Fabio正确地说" 执行proc的上下文根据它的调用方式而有所不同. "但我认为你可以克服"active_year_id is undefined"问题.

在示例中:

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

问题是在某些情况下,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'
    }
Run Code Online (Sandbox Code Playgroud)

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

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

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


Fab*_*bio 5

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

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

当您从学校实例调用学生关系时,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
Run Code Online (Sandbox Code Playgroud)

但是当你调用包含关系时,上下文是不同的,并且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'
Run Code Online (Sandbox Code Playgroud)

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

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

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


the*_*gah 0

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

所以:

School.where(:id => 10).students
Run Code Online (Sandbox Code Playgroud)

和:

School.where(:active => true).includes(:students)
Run Code Online (Sandbox Code Playgroud)

是两种完全不同的东西。

School.find(10).students第一个与返回找到的学校学生的情况相同。

第二个则School.where(:active => true).includes(:students)完全不同。你正在寻找具有价值active == true和包容性的学校students,但这对学生没有任何帮助。它只返回学校。

你想做什么?