Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

Dirty tracking on serialized columns is broken

#360
class Monkey < ActiveRecord::Base
  serialize legs, Array
end

Now in the console:

>> m = Monkey.create
>> m.changed? => false
>> m.legs = [1,2,3]
>> m.changed? => false

Reported by Andre Foeken · June 7th, 2008 @ 01:48 AM

State: invalid
Milestone: 3.x
Assigned to: Neeraj Singh Neeraj Singh
Importance: Low

Activity

  1. Jeremy Kemper
    Jeremy Kemper
    • Milestone set to 2.1.1
    • State changed from new to open
    • Assigned user set to Jeremy Kemper

    June 11th, 2008 @ 07:59 PM

  2. Ernie Miller
    Ernie Miller
    • Tag set to activerecord, bug, dirty

    Is this bug valid? I can't reproduce in Rails 2.1, anyway.

    class Monkey < ActiveRecord::Base

    serialize :legs, Array

    end

    And in script/console:

    Loading development environment (Rails 2.1.0)

    >> m = Monkey.create

    => #

    >> m.changed?

    => false

    >> m.legs = [1,2,3,4]

    => [1, 2, 3, 4]

    >> m.changed?

    => true

    
    

    July 3rd, 2008 @ 02:51 PM

  3. Ernie Miller
    Ernie Miller

    Hm. Trying once more since Lighthouse formatting eluded me.

    class Monkey < ActiveRecord::Base
    

    serialize :legs, Array

    end

    and

    Loading development environment (Rails 2.1.0)
    

    >> m = Monkey.create

    => #

    >> m.changed?

    => false

    >> m.legs = [1,2,3,4]

    => [1, 2, 3, 4]

    >> m.changed?

    => true

    July 3rd, 2008 @ 02:58 PM

  4. Ernie Miller
    Ernie Miller

    Eh, I give up. Trying Textile but it's not working. You get the idea.

    July 3rd, 2008 @ 02:59 PM

  5. Ripta Pasay
    Ripta Pasay

    I couldn't reproduce this either. I did, however, find that dirty tracking doesn't work with in-place manipulation of said serialized column:

    >> m.legs
    => [1, 2, 3]
    >> m.changed?
    => false
    >> m.legs << 4
    => [1, 2, 3, 4]
    >> m.changed?
    => false
    

    IMHO, AR should let the object itself figure out whether it's been changed or not. If anything, AR could provide a way for either: (a) the serialized object to notify AR that the attribute has been changed, or (b) AR to optionally query the serialized object when its own #changed? is called.

    July 12th, 2008 @ 09:19 PM

  6. quake wang
    quake wang

    I can't reproduce this error in rails 2.1, however, dirty checking is broken is this case:

    >> m.reload

    >> m.legs = m.legs << 4

    >> m.changed?

    => false

    July 13th, 2008 @ 04:04 AM

  7. Jeremy Kemper
    Jeremy Kemper
    • State changed from open to invalid

    July 16th, 2008 @ 12:20 AM

  8. barunio
    barunio

    Why was this ticket changed to 'invalid'? This is still broken as far as I can tell (yes, AR knows if the field went from nil to an array, but it still can't tell if an existing serialized array was modified).

    If you have a serialized field named "array_field", then "array_field_was" method doesn't return what array_field was, and "array_field_changed?" doesn't tell you if array_field was changed. To me that looks like clearly broken behavior (and I don't think the docs anywhere specify that one ought to expect different behavior for serialized fields)..

    March 28th, 2009 @ 12:01 AM

  9. Lluís
    Lluís

    This still happens on Rails 2.3.5
    if serialized changes of type (nil to Array, Array to Hash..) changed? method returns true, but not if serialized changes conserving its type (adding an element to array..)

    May 14th, 2010 @ 01:32 PM

  10. Neeraj Singh
    Neeraj Singh

    This is no longe an issue with rail3.

    This is what I found on rails edge.

    ActiveRecord::Schema.define(:version => 20100516025941) do
    
      create_table "users", :force => true do |t|
        t.string   "name"
        t.text     "bio"
      end
    
    end
    class User < ActiveRecord::Base
      serialize :bio
    end
    #rails -v : Rails 3.0.0.beta3
    #ruby -v : ruby 1.8.7 (2010-01-10 patchlevel 249) [i686-darwin10.3.0]
    
    > u = User.create
    > u.changed?
     => false 
    > u.bio = 'w'
     => "w" 
    > u.changed?
     => true 
    > u.changes
     => {"bio"=>[nil, "w"]}
    

    May 16th, 2010 @ 04:05 AM

  11. Karl Varga
    Karl Varga
    • Importance changed from to High

    I've just run into this now on Rails 2.3.8.

    Neeraj, that's not a good test. You want to use a Hash and add a new key, then you'll see what happens.

    In this output I have a User model with a prefs serialized Hash:

    (rdb:158) current_user.changed? false
    (rdb:158) current_user.prefs[:some_new_key] = 42 42
    (rdb:158) current_user.changed? false

    July 21st, 2010 @ 01:26 AM

  12. Neeraj Singh
    Neeraj Singh

    In rails 3 this is what I am getting

    
    class User < ActiveRecord::Base
      serialize :bio
    
      def self.lab
        u = User.create
        assert !u.changed?
        u.bio = {:social => :github}
        assert u.changed?
    
        u.reload
        u.bio = {}
        u.bio[:social] = :github
        assert u.changed?
      end
    end
    

    All assertions are passing.

    July 21st, 2010 @ 01:59 AM

  13. Karl Varga
    Karl Varga

    @neeraj, if u.bio is nil then assigning a hash to it will show a change because it is no longer nil.

    Try it with a user that has bio already set and saved, then assign a key:

    u.bio = {}
    u.save
    u.bio[:social] = :github
    assert u.changed?

    July 22nd, 2010 @ 09:16 PM

  14. Neeraj Singh
    Neeraj Singh
    • Milestone changed from 2.1.1 to 3.x
    • State changed from invalid to open
    • Tag changed from activerecord, bug, dirty to rails 3, activerecord, bug, dirty
    • Assigned user changed from Jeremy Kemper to Neeraj Singh
    • Importance changed from High to Low

    @Karl thanks

    class User < ActiveRecord::Base
      serialize :bio
    
      def self.lab
        u = User.create(:bio => {:eye_color => "black"})
        assert !u.changed?
        u.bio = {:social => :github}
        assert u.changed? # passes
    
        u.reload
        u.bio[:eye_color] = :green
        assert u.changed? # Fails
      end
    end
    

    Opening the ticket.

    PS: I think the case where it is failing is okay. That is because changed? works by trakcing assigning changed valeu and in this case in-memory value is being changed. Nothing is being assigned.

    July 22nd, 2010 @ 11:30 PM

  15. José Valim
    José Valim
    • State changed from open to invalid

    Yes, this ticket is invalid because we cannot possibly track changes in place. This is mentioned in the documentation. You can either: 1) do not change serialized attributes in place or 2) call attribute_will_change!

    July 22nd, 2010 @ 11:38 PM

  16. Neeraj Singh
    Neeraj Singh

    In case someone wants to see the complete solution. Here it is with the usage of attribute_will_change!

    class User < ActiveRecord::Base
      serialize :bio
    
      def self.lab
        u = User.create(:bio => {:eye_color => "black"})
        assert !u.changed?
        u.bio = {:social => :github}
        assert u.changed?
    
        u.reload
        u.send(:attribute_will_change!, :bio)
        u.bio[:eye_color] = :green
        assert u.changed?
      end
    end
    

    July 22nd, 2010 @ 11:46 PM

  17. Glenn Vanderburg
    Glenn Vanderburg

    José, you said this is mentioned in the documentation. Where?

    And here's a more idiomatic example of how to do the right thing with attribute_will_change!:

        class User < ActiveRecord::Base
          serialize :bio
        
          def self.lab
            u = User.create(:bio => {:eye_color => "black"})
            assert !u.changed?
            u.bio = {:social => :github}
            assert u.changed?
    
            u.reload
            u.bio_will_change!
            u.bio[:eye_color] = :green
            assert u.changed?
          end
        end
    

    August 11th, 2010 @ 09:55 PM