Трек грязный для атрибута без сохранения в объекте ActiveRecord в рельсах
У меня есть объект, который наследуется от ActiveRecord, но у него есть атрибут, который не сохраняется в БД, например:
class Foo < ActiveRecord::Base
attr_accessor :bar
end
Я хотел бы иметь возможность отслеживать изменения в "bar" с помощью таких методов, как "bar_changed?", как это предусмотрено ActiveModel Dirty. Проблема в том, что когда я пытаюсь реализовать Dirty на этом объекте, как описано в docs, я получаю сообщение об ошибке, так как ActiveRecord и ActiveModel определили define_attribute_methods
, но с различным количеством параметров, поэтому я получаю сообщение об ошибке при попытке вызвать define_attribute_methods [:bar]
.
Я попробовал aliasing 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.
Я признаю, что еще не проверял его до сих пор, но до сих пор у меня не возникало никаких проблем.
Ответ 2
Я выяснил решение, которое сработало для меня...
Сохраните этот файл как 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...
Ответ 3
ActiveRecord
имеет метод #attribute
(source), который однажды вызван из вашего класса, позволит ActiveModel::Dirty
создавать такие методы, как bar_was
, bar_changed?
и многие другие.
Таким образом, вам придется вызывать attribute :bar
внутри любого класса, который выходит из ActiveRecord
(или ApplicationRecord
для самых последних версий Rails), чтобы создать эти вспомогательные методы на bar
.
Изменение: Обратите внимание, что этот подход не следует смешивать с attr_accessor :bar
Редактировать 2: Еще одно замечание: неперспективные атрибуты, определенные с помощью attribute
(например, attribute :bar, :string
), будут уничтожены при сохранении. Если вам нужно, чтобы attr зависал после сохранения (как я сделал), вы можете (осторожно) смешивать с attr_reader
, например, так:
attr_reader :bar
attribute :bar, :string
def bar=(val)
super
@bar = val
end
Ответ 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