Ошибка отслеживания для не сохраняемого атрибута в объекте ActiveRecord в rails

#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 методом.