Есть ли способ предотвратить обновление сериализованных атрибутов в рельсах, даже если нет изменений? - PullRequest
10 голосов
/ 05 января 2012

Это, вероятно, одна из вещей, которую рано или поздно узнают все новые пользователи о Rails. Я только что понял, что rails обновляет все поля с помощью ключевого слова serialize, не проверяя, действительно ли что-то изменилось внутри. В некотором смысле это разумно сделать для общей структуры.

Но есть ли способ отменить это поведение? Если я могу отследить, изменились ли значения в сериализованных полях или нет, есть ли способ предотвратить его вставку в операторе обновления? Я попытался использовать update_attributes и ограничить хеш интересующими полями, но rails все еще обновляет все сериализованные поля.

Предложения

Ответы [ 6 ]

2 голосов
/ 18 мая 2012

Вот аналогичное решение для Rails 3.1.3.

От: https://sites.google.com/site/wangsnotes/ruby/ror/z00---topics/fail-to-partial-update-with-serialized-data

Поместите следующий код в 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
1 голос
/ 26 мая 2015

Не совсем решение, но во многих случаях хорошим обходным путем для меня было просто переместить сериализованные столбцы в связанную модель - часто это все равно было семантически подходящим образом.

1 голос
/ 08 декабря 2012

Проблема с ответом Joris заключается в том, что он подключается к цепочке 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 на 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 и сделать там выбор атрибутов.

1 голос
/ 22 октября 2012

Я столкнулся с этой проблемой сегодня и в итоге взломал свой собственный сериализатор вместе с геттером и сеттером.Сначала я переименовал поле в #{column}_raw, а затем использовал следующий код в модели (для атрибута media в моем случае).

require 'json'

...

def media=(media)
  self.media_raw = JSON.dump(media)
end

def media
  JSON.parse(media_raw) if media_raw.present?
end

Теперь частичные обновления отлично работают для меня, и полеобновляется только при фактическом изменении данных.

1 голос
/ 24 января 2012

Да, это меня тоже беспокоило.Это то, что я сделал для 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 => "обнови меня" все еще работает!)

class Model < ActiveRecord::Base
  serialize :serialized_field

  def no_serialize_update
    true
  end

end
0 голосов
/ 17 октября 2013

Есть также обсуждения в https://github.com/rails/rails/issues/8328.

...