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

时间:2011-07-13 19:33:32

标签: ruby-on-rails ruby-on-rails-3 activerecord

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

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。

4 个答案:

答案 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类的方法而是字段(数据库列),则可以使用joinsscopes来实现类似于你想要实现,但它应该手工编码。

答案 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) }