Подтвердить что ты не робот

Трек грязный для атрибута без сохранения в объекте 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.

4b9b3361

Ответ 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