#ruby-on-rails-3 #activerecord #activemodel
#ruby-on-rails-3 #activerecord #activemodel
Вопрос:
У меня есть объект, который наследуется от ActiveRecord, но у него есть атрибут, который не сохраняется в базе данных, например:
class Foo < ActiveRecord::Base
attr_accessor :bar
end
Я хотел бы иметь возможность отслеживать изменения в ‘bar’ с помощью таких методов, как ‘bar_changed?’, как предусмотрено ActiveModel Dirty. Проблема в том, что когда я пытаюсь реализовать Dirty для этого объекта, как описано в документах, я получаю сообщение об ошибке, поскольку и ActiveRecord, и ActiveModel определены define_attribute_methods
, но с разным количеством параметров, поэтому я получаю сообщение об ошибке при попытке вызвать define_attribute_methods [:bar]
.
Я пробовал сглаживание define_attribute_methods
перед включением ActiveModel::Dirty
, но безуспешно: я получаю ошибку не определенного метода.
Есть идеи о том, как с этим справиться? Конечно, я мог бы написать требуемые методы вручную, но мне было интересно, возможно ли это сделать с использованием модулей Rails, расширив функциональность ActiveModel до атрибутов, не обрабатываемых ActiveRecord.
Ответ №1:
Я использую attribute_will_change!
метод, и, похоже, все работает нормально.
Это частный метод, определенный в active_model/dirty.rb
, но ActiveRecord смешивает его во всех моделях.
Это то, что я в конечном итоге реализовал в своем классе модели:
def bar
@bar ||= init_bar
end
def bar=(value)
attribute_will_change!('bar') if bar != value
@bar = value
end
def bar_changed?
changed.include?('bar')
end
init_bar
Метод используется только для инициализации атрибута. Вам это может понадобиться, а может и не понадобиться.
Мне не нужно было указывать какой-либо другой метод (например, define_attribute_methods
) или включать какие-либо модули. Вам действительно придется переопределить некоторые методы самостоятельно, но, по крайней мере, поведение будет в основном соответствовать ActiveModel .
Я признаю, что я еще не тестировал его тщательно, но пока я не столкнулся ни с какими проблемами.
Комментарии:
1. Как я упоминал в ответе,
init_bar
метод используется только для инициализации атрибута. Если вам нужна какая-то логика инициализации, вам придется определить ее, в противном случае вы можете просто проигнорировать ее.2. О, это метод, который я должен определить… это не встроенный метод, верно?
3. Правильно. Вы должны определить его, если вам нужен инициализатор.
Ответ №2:
ActiveRecord
имеет #attribute
метод (исходный код), который после вызова из вашего класса позволит ActiveModel::Dirty
создавать такие методы, как bar_was
, bar_changed?
и многие другие.
Таким образом, вам пришлось бы вызывать attribute :bar
внутри любого класса, который расширяется от ActiveRecord
(или ApplicationRecord
для самых последних версий Rails), чтобы создать эти вспомогательные методы после bar
.
Редактировать: Обратите внимание, что этот подход не следует смешивать с attr_accessor :bar
Редактировать 2: Еще одно замечание заключается в том, что несуществующие атрибуты, определенные с помощью attribute
(например attribute :bar, :string
), будут удалены при сохранении. Если вам нужно, чтобы атрибуты зависали после сохранения (как я сделал), вы действительно можете (осторожно) смешать с attr_reader
, вот так:
attr_reader :bar
attribute :bar, :string
def bar=(val)
super
@bar = val
end
Комментарии:
1. Это казалось многообещающим, однако у меня не сработало. Я не верю, что
attribute
метод определяет методы-модификаторы, которые реализуютActiveModel::Dirty
интерфейс2. Я беру все это обратно. Пока вы только добавляете
attribute :bar, :string
(не смешивайте сattr_accessor :bar
или определяете пользовательский установщик), это отлично работает и было бы моим предпочтительным подходом… когда SO разблокирует мой голос, я проголосую за3. на самом деле я понял, что с ним возможно (и для меня было необходимо) смешивать
attr_accessor
, потому что не сохраненныйattribute :bar
удаляется при сохранении, а мне это было нужно при обратном вызове…. Думаю, я отредактирую еще раз 😆4. Или я не уверен — заслуживает ли это последующего ответа?
5. Я думаю, что выбранный вами путь (т. Е. Редактирование ответа путем добавления дополнительной информации) был правильным. Еще раз спасибо @steve 👍
Ответ №3:
Я нашел решение, которое сработало для меня…
Сохраните этот файл как lib/active_record/nonpersisted_attribute_methods.rb
: https://gist.github.com/4600209
Затем вы можете сделать что-то вроде этого:
require 'active_record/nonpersisted_attribute_methods'
class Foo < ActiveRecord::Base
include ActiveRecord::NonPersistedAttributeMethods
define_nonpersisted_attribute_methods [:bar]
end
foo = Foo.new
foo.bar = 3
foo.bar_changed? # => true
foo.bar_was # => nil
foo.bar_change # => [nil, 3]
foo.changes[:bar] # => [nil, 3]
Однако, похоже, что мы получаем предупреждение, когда делаем это таким образом:
DEPRECATION WARNING: You're trying to create an attribute `bar'. Writing arbitrary attributes on a model is deprecated. Please just use `attr_writer` etc.
Поэтому я не знаю, сломается ли этот подход или будет сложнее в Rails 4…
Комментарии:
1. Это единственный ответ, который действительно решает вопрос, но должен быть способ сделать это без предупреждений об устаревании. Кто-нибудь знает, что это такое?
Ответ №4:
Напишите метод bar= самостоятельно и используйте переменную экземпляра для отслеживания изменений.
def bar=(value)
@bar_changed = true
@bar = value
end
def bar_changed?
if @bar_changed
@bar_changed = false
return true
else
return false
end
end
Комментарии:
1. Да, что-то подобное я в конечном итоге сделал, но мне было интересно, есть ли способ сделать это с использованием методов ActiveModel.
2. это достаточно просто сделать с базовым ruby, я думаю, что использование ActiveModel для этого является чрезмерным. Если это часть более широкой картины, относящейся к ActiveModel, вы можете создавать модели без таблиц (см. railscasts.com/episodes/219-active-model ) и мог бы использовать аналогичную технику для атрибутов без таблиц, но опять же было бы излишним для такой простой задачи … лучше всего упростить ее
3. Повторное использование ActiveModel::Dirty и ActiveModel::AttributeMethods (которые уже включены в ActiveRecord::Base) бы упростило задачу, если бы только это было возможно. Я не думаю, что повторное использование методов (от предков класса) вообще излишне. И я обвиняю ActiveRecord в переопределении define_attribute_methods таким образом, что вы не можете повторно использовать метод define_attribute_methods из ActiveModel::AttributeMethods. Почему должно быть так сложно иметь сочетание как сохраняемых, так и не сохраняемых атрибутов в модели?
4. Этот подход может сработать в некоторых случаях, но единственное, чего он не делает, чего сделал бы для вас ActiveModel::Dirty, — это включить это изменение в хэш, возвращаемый
changes
методом.