【问题标题】:Is there a way to prevent serialized attributes in rails from getting updated even if there are not changes?即使没有更改,有没有办法防止 Rails 中的序列化属性更新?
【发布时间】:2012-02-03 10:29:54
【问题描述】:

这可能是所有新用户迟早会发现有关 Rails 的事情之一。我刚刚意识到 rails 正在使用 serialize 关键字更新所有字段,而没有检查内部是否真的发生了任何变化。在某种程度上,这对通用框架来说是明智的做法。

但是有没有办法覆盖这种行为?如果我可以跟踪序列化字段中的值是否已更改,有没有办法防止它被推送到更新语句中?我尝试使用“update_attributes”并将哈希限制为感兴趣的字段,但 rails 仍会更新所有序列化字段。

建议?

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-3 serialization update-attributes


    【解决方案1】:

    在许多情况下对我来说不是一个很好的解决方案,但一个很好的解决方法是将序列化的列移动到关联的模型 - 通常这实际上在语义上很合适。

    【讨论】:

      【解决方案2】:

      【讨论】:

        【解决方案3】:

        Joris 的答案的问题在于它与alias_method_chain 链挂钩,从而禁用了之后完成的所有链(例如update_with_callbacks,它解决了未调用触发器的问题)。我将尝试制作图表以使其更易于理解。

        你可以从这样的链开始

        update -> update_with_foo -> update_with_bar -> update_with_baz
        

        注意update_without_foo 指向update_with_barupdate_without_bar 指向update_with_baz

        由于您无法根据alias_method_chain 的内部工作原理直接修改update_with_bar,您可以尝试通过添加新链接(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 并在那里进行属性选择。

        【讨论】:

          【解决方案4】:

          我今天遇到了这个问题,最后用 getter 和 setter 破解了我自己的序列化程序。首先,我将该字段重命名为 #{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
          

          现在部分更新对我来说非常有用,并且该字段仅在数据实际更改时才会更新。

          【讨论】:

          • 很棒的解决方案!我很想知道是否有任何缺点,也许是性能?每次都必须解析原始数据?
          【解决方案5】:

          这是 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
          

          【讨论】:

          • 它可以工作(在 RoR 3.2.18 上试过)。您是否在包含 serialize 的模型中添加了:def no_serialize_update true end 您希望默认情况下不会更新?
          【解决方案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
          

          【讨论】:

          • 这看起来很有希望。谢谢!既然我知道要覆盖什么,我可能只是这样做(与 super 一起)而不是使用方法链接,除非在采用这种方法时有警告。如果我只是使用 override + super,如果您发现任何问题,请告诉我。
          • 我也试过了,但发现没有用。问题是 update_with_dirty 在你有机会覆盖它并使用 super 之前已经加载(通过原始链)。
          • @Tabrez:警告:我的应用程序中有一个奇怪的错误,即在更新模型时,acts-as-versioned 和friendly_id gem 没有在我的数据库中创建记录。看起来 before_update 回调在我的模型中停止工作。我花了很长时间才将它链接到我的应用程序中的上述代码。我现在把它删了。 (还)不确定为什么会这样。
          • @Joris 我刚刚添加了一个解释您的问题的答案
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-02-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多