Rails - включая ассоциации с динамическими условиями - PullRequest
8 голосов
/ 13 июля 2011

С учетом школьной модели и модели ученика со школой, имеющей отношение has_many к ученику:

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.

Ответы [ 4 ]

9 голосов
/ 21 марта 2013

Это немного устарело, но я часто вижу вопрос и не нашел удовлетворительных решений. Добавление такого условия по существу эквивалентно созданию ассоциации с двумя открытыми / закрытыми ключами.

@ Фабио прав, говоря: « контекст, в котором выполняется процесс, отличается в зависимости от того, как он вызывается. » Однако я думаю, что вы можете преодолеть проблему «active_year_id is undefined».

В примере:

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

проблема в том, что в некоторых ситуациях процедура выполняется в контексте определенного объекта 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 и строковая форма условия прекрасно работают, используя имена полей, а не значения.

Мы нашли, что это решение позволяет нам успешно использовать динамическую ассоциацию.

5 голосов
/ 09 мая 2012

Я думаю, что этого невозможно достичь, потому что контекст, в котором выполняется процедура, отличается в зависимости от того, как он вызывается. Я сделал базовое приложение для ваших моделей, и вот что происходит, когда вы вызываете различные методы ( ap is ):

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

Когда вы вызываете отношение учеников из школьного экземпляра, контекстом процесса является заданный экземпляр 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

Но когда вы вызываете отношение включений, контекст отличается, и то, что 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. Я думаю, что единственный способ использовать procs как , описанный здесь , - это вычислить некоторое условие во время выполнения, которое не включает методы экземпляра (временные условия, где с данными из несвязанных моделей и т. Д.).

Более того, School.includes(:students).all в моем примере не может работать, потому что он должен вызывать метод active_year_id для каждого экземпляра School (который должен быть получен из базы данных, прежде чем можно будет включить включения), и таким образом исчезает эффект includes предполагаемого поведения.

Все это действительно, если active_year_id является вычисляемым методом, определенным в классе School на основе данных экземпляра. Вместо этого, если active_year_id - это не метод, а поле (столбец db) класса School, с которым можно играть , объединяет и области для достижения результата, аналогичного тому, что Вы хотите достичь, но это должно быть закодировано вручную.

0 голосов
/ 05 июля 2016

К вашему сведению, вы можете получить доступ к текущей коллекции, к которой относится включение ... в некоторых случаях это может помочь:

belongs_to :ticket, -> (t) { where(account_id: (t || self.first).account_id) }
0 голосов
/ 13 июля 2011

Это должен быть комментарий, но места недостаточно.

Итак:

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

И:

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

Это две совершенно разные вещи.

Первый - это то же самое, что и School.find(10).students, где он возвращает найденных учащихся школ.

Второй School.where(:active => true).includes(:students) совершенно другой.Вы находите школы со значением active == true, включая students, но это никак не влияет на учащихся.Он возвращает только школы.

Что вы пытаетесь сделать?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...