Есть ли способ предотвратить обновление сериализованных атрибутов в rails, даже если нет изменений?
Это, вероятно, одна из вещей, которые все новые пользователи узнают о Rails рано или поздно. Я только что понял, что rails обновляет все поля с помощью ключевого слова serialize, не проверяя, действительно ли что-то изменилось внутри. Таким образом, это разумная вещь для общей структуры.
но есть ли способ переопределить это поведение? Если я могу отслеживать, изменились ли значения в сериализованных полях или нет, есть ли способ предотвратить это толкнул инструкцию update? Я попытался использовать "update_attributes" и ограничить хэш интересующими полями, но rails по-прежнему обновляет все сериализованные поля.
предложения?
6 ответов
Да, это меня тоже беспокоило. Это то, что я сделал для Rails 2.3.14 (или ниже):
# config/initializers/nopupdateserialize.rb
module ActiveRecord
class Base
class_attribute :no_serialize_update
self.no_serialize_update = false
end
end
module ActiveRecord2
module Dirty
def self.included(receiver)
receiver.alias_method_chain :update, :dirty2
end
private
def update_with_dirty2
if partial_updates?
if self.no_serialize_update
update_without_dirty(changed)
else
update_without_dirty(changed | (attributes.keys & self.class.serialized_attributes.keys))
end
else
update_without_dirty
end
end
end
end
ActiveRecord::Base.send :include, ActiveRecord2::Dirty
тогда в вашем контроллере используйте:
model_item.no_serialize_update = true
model_item.update_attributes(params[:model_item])
model_item.increment!(:hits)
model_item.update_attribute(:nonserializedfield => "update me")
etc.
или определите его в своей модели, если вы не ожидаете каких-либо изменений в сериализованном поле после его создания (но update_attribute(:serialized_field => "update me" все еще работает!)
class Model < ActiveRecord::Base
serialize :serialized_field
def no_serialize_update
true
end
end
вот аналогичное решение для Rails 3.1.3.
поместите следующий код в config / initializers/
ActiveRecord::Base.class_eval do
class_attribute :no_serialize_update
self.no_serialize_update = false
end
ActiveRecord::AttributeMethods::Dirty.class_eval do
def update(*)
if partial_updates?
if self.no_serialize_update
super(changed)
else
super(changed | (attributes.keys & self.class.serialized_attributes.keys))
end
else
super
end
end
end
я столкнулся с этой проблемой сегодня и в конечном итоге взломал свой собственный сериализатор вместе с геттером и сеттером. Сначала я переименовал поле в #{column}_raw
а затем использовал следующий код в модели (для в моем случае).
require 'json'
...
def media=(media)
self.media_raw = JSON.dump(media)
end
def media
JSON.parse(media_raw) if media_raw.present?
end
Теперь частичные обновления отлично работают для меня, и поле обновляется только тогда, когда данные фактически изменены.
проблема с ответом Джориса в том, что он цепляется за alias_method_chain
цепь, отключив все цепи, сделанные после (например,update_with_callbacks
что объясняет проблемы триггеров, которые не вызываются). Я постараюсь сделать диаграмму, чтобы было легче понять.
вы можете начать с цепи, как это
update -> update_with_foo -> update_with_bar -> update_with_baz
обратите внимание, что update_without_foo
указывает на update_with_bar
и update_without_bar
to update_with_baz
так как вы не можете напрямую изменить update_with_bar
в внутренние работы alias_method_chain
вы можете попытаться подключиться к цепочке, добавив новую ссылку (bar2) и вызвав update_without_bar, так что:
alias_method_chain :update, :bar2
к сожалению, это даст вам следующие услуги:
update -> update_with_bar2 -> update_with_baz
так update_with_foo ушел!
Итак, зная, что alias_method_chain
не позволит вам переопределить _with
способы мое решение до сих пор было переопределить update_without_dirty
и сделайте выбор атрибута там.
Не совсем решение, но хорошим обходным путем во многих случаях для меня было просто переместить сериализованный столбец(ы) в связанную модель - часто это действительно было хорошо подходит семантически в любом случае.