Question

This is probably one of the things that all new users find out about Rails sooner or later. I just realized that rails is updating all fields with the serialize keyword, without checking if anything really changed inside. In a way that is the sensible thing to do for the generic framework.

But is there a way to override this behavior? If I can keep track of whether the values in a serialized fields have changed or not, is there a way to prevent it from being pushed in the update statement? I tried using "update_attributes" and limiting the hash to the fields of interest, but rails still updates all the serialized fields.

Suggestions?

Was it helpful?

Solution

Yes, that was bugging me too. This is what I did for Rails 2.3.14 (or lower):

# 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

Then in your controller use:

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.

Or define it in your model if you do not expect any changes to the serialized field once created (but update_attribute(:serialized_field => "update me" still works!)

class Model < ActiveRecord::Base
  serialize :serialized_field

  def no_serialize_update
    true
  end

end

OTHER TIPS

Here is a similar solution for Rails 3.1.3.

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

Put the following code in 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

I ran into this problem today and ended up hacking my own serializer together with a getter and setter. First I renamed the field to #{column}_raw and then used the following code in the model (for the media attribute in my case).

require 'json'

...

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

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

Now partial updates work great for me, and the field is only updated when the data is actually changed.

The problem with Joris' answer is that it hooks into the alias_method_chain chain, disabling all the chains done after (like update_with_callbacks which accounts for the problems of triggers not being called). I'll try to make a diagram to make it easier to understand.

You may start with a chain like this

update -> update_with_foo -> update_with_bar -> update_with_baz

Notice that update_without_foo points to update_with_bar and update_without_bar to update_with_baz

Since you can't directly modify update_with_bar per the inner workings of alias_method_chain you might try to hook into the chain by adding a new link (bar2) and calling update_without_bar, so:

alias_method_chain :update, :bar2

Unfortunately, this will get you the following chain:

update -> update_with_bar2 -> update_with_baz

So update_with_foo is gone!

So, knowing that alias_method_chain won't let you redefine _with methods my solution so far has been to redefine update_without_dirty and do the attribute selection there.

Not quite a solution but a good workaround in many cases for me was simply to move the serialized column(s) to an associated model - often this actually was a good fit semantically anyway.

There is also discussions in https://github.com/rails/rails/issues/8328.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top