ActiveRecord создает экземпляр неправильного класса через область, нацеленную на класс STI.

Я хотел бы иметь возможность вызывать метод build в области, которая нацелена на определенный класс модели через его тип STI, и заставить ActiveRecord построить экземпляр правильного класса.

class LineItem < ActiveRecord::Base
  scope :discount, where(type: 'DiscountLineItem')
end

class DiscountLineItem < LineItem; end

> LineItem.discount.build # Expect an instance of DiscountLineItem here
=> #<LineItem ...>

Здесь я ожидал экземпляр DiscountLineItem, а не экземпляр LineItem.


person steveluscher    schedule 09.07.2012    source источник


Ответы (2)


Несмотря на то, что ActiveRecord не создает экземпляр объекта как правильный класс, он правильно устанавливает тип. В основном у вас есть два способа обойти это:

1) Создайте объект, а затем перезагрузите его из базы данных:

item = LineItem.discount.create(attrs...)
item = LineItem.find(item.id)

2) Используйте класс STI и создайте объект непосредственно из него:

DiscountLineItem.build

Со всеми возможностями ActiveRecord это кажется бессмысленным ограничением, и изменить его не так уж сложно. Теперь вы меня заинтересовали :)

Обновление:

Это было недавно добавлено в Rails 4.0 со следующим сообщением фиксации:

Позволяет выполнять BaseClass.new(:type => "SubClass"), а также parent.children.build(:type => "SubClass") или parent.build_child для инициализации подкласса STI. Гарантирует, что имя класса является допустимым классом и находится в предках суперкласса, ожидаемого ассоциацией.

person Peter Brown    schedule 09.07.2012
comment
Возможно, пришло время перенести это обсуждение в трекер Rails. Я напишу неудачный тестовый пример и опубликую его сегодня. - person steveluscher; 09.07.2012
comment
Вчера вечером я написал исправление и собирался опубликовать его в группе rails core google для обратной связи. Я опубликую это здесь как обновление позже этим вечером, когда у меня будет шанс. - person Peter Brown; 09.07.2012
comment
@steveluscher Вот патч для ActiveRecord, который вы можете попробовать: gist.github.com/5cad22a11f011052d8f6 - person Peter Brown; 10.07.2012
comment
Вы хотите отправить это на github.com/rails/rails/issues/7021? @бирлингтон? Я записал этот неудачный тест и сослался на него. - person steveluscher; 10.07.2012
comment
@steveluscher Похоже, проблема закрыта :/. Если это ожидаемое поведение, будет трудно убедить людей изменить его. Вы можете использовать один из вариантов, которые я предложил, или не стесняйтесь взять патч, который я написал, и использовать его в своем приложении или создать с его помощью гем. Кроме того, он был основан на Rails 3.2.6, поэтому я не знаю, будет ли он работать в 4.0. - person Peter Brown; 10.07.2012
comment
@steveluscher Хорошие новости, похоже, что чей-то запрос на включение был принят для этого. Я обновил свой ответ для всех, кто наткнется на это в будущем. - person Peter Brown; 30.11.2012
comment
Прекрасный. Спасибо @Beerlington. - person steveluscher; 03.12.2012

Забудьте о build на мгновение. Если у вас есть LineItem l и вы делаете l.discount, вы получите LineItem экземпляров, а не DiscountLineItem экземпляров. Если вы хотите получить DiscountLineItem экземпляров, я предлагаю преобразовать область действия в метод

def self.discount
  where(type: 'DiscountLineItem').map { |l| l.becomes(l.type.constantize) }
end

Теперь вы получите коллекцию из DiscountLineItem экземпляров.

person deefour    schedule 09.07.2012
comment
Это должно быть def self.discount - person Peter Brown; 09.07.2012
comment
Мне нравится эта концепция, но использование карты убьет вашу способность связывать любые другие области или методы, поскольку вы конвертируете объект ActiveRecord::Relation в массив. Должен быть способ сделать это чисто :/ - person Peter Brown; 09.07.2012
comment
Да, мне просто нужно прочитать документы по преобразованию его обратно в отношение. - person deefour; 09.07.2012
comment
Ох. Хотя эта техника завершает жизнь ActiveRecord::Relation, лишая меня возможности создавать цепочки, спасибо за указание на метод becomes. - person steveluscher; 09.07.2012
comment
Да, это один из тех методов, которые вы много раз искали в своей полиморфной работе, но не знали, что искать, чтобы найти. Я больше смотрю на альтернативное решение, чтобы сохранить отношения, но чувствую (с моими текущими знаниями о рельсах), что это закончится довольно хакерским. - person deefour; 09.07.2012