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.

Activerecord::Base#clone does not set dirty bits

#2919

A cloned object does not get marked dirty:

obj = Person.first
obj2 = obj.clone
obj2.changed? # => false

This might trip people up who limit expensive validations to changed attributes.
For example, this is the failure that I ran into:

Class Person
validates_uniqueness_of :name, :if => Proc.new { |u| u.name_changed? } # cloned objects are not checked! end

One could argue that the behaviour is correct since #new() also results in #changed?() being false. In any case, I would suggest updating the documentation to point out the behaviour.

Reported by Fjan · July 16th, 2009 @ 10:52 PM

State: committed
Milestone: 3.x
Assigned to: nobody
Importance: none

Activity

  1. Arpit Jain
    Arpit Jain
    • Tag set to activemodel, dirty

    I ran into the same problem when my records could not be saved on cloning as i used #{attr}_changed? to locate the changes in new/cloned objects.

    I would still say that this behavior is wrong because if you do

    u = User.new(:email => "someemail@email.com") u.email_changed? # => true

    So, if you instantiate a new user with initial hash, dirty bits are set. Isn't cloning the same thing ? We are just duplicating a new object, meaning we are creating a new object and instantiating with values of older object. Hence, dirty bits should be set.

    September 12th, 2009 @ 01:41 PM

  2. Arpit Jain
    Arpit Jain

    Sorry the code block didn't come correctly in previous comment.
    Here is what I meant :

      user = User.new(:email => "someemail@email.com")
      u.email_changed? #=> true
    

    September 12th, 2009 @ 01:43 PM

  3. Jeremy Kemper
    Jeremy Kemper
    • Milestone changed from 2.x to 3.x

    May 4th, 2010 @ 06:48 PM

  4. Dan Pickett
    Dan Pickett
    • Tag changed from activemodel, dirty to activemodel, bugmash, dirty

    May 9th, 2010 @ 07:21 PM

  5. Federico Brubacher
    Federico Brubacher
    • Tag changed from activemodel, bugmash, dirty to activemodel, bugmash, dirty, patch

    I've attached a patch.

    I agree with Arpit, from the docs: Cloned objects have no id assigned and are treated as new records

    Here is a patch that fixes this behavior.

    May 15th, 2010 @ 06:00 PM

  6. Simon Jefford
  7. Anil Wadghule
  8. pleax
    pleax

    -1 A problem with the patch is illustrated by given test case:

      test "cloned object should threat as changed only modified attributes" do
        token = Topic.new :content => 'foo bar baz'
        assert !token.title_changed?
        
        token.save
        assert !token.title_changed?
        
        cloned_token = token.clone
        assert !cloned_token.title_changed?
      end
    

    May 15th, 2010 @ 07:15 PM

  9. elcuervo
    elcuervo

    +1

    @pleax I i disagree with your comment, as Federico and Arpit mentioned, the cloned objects shall be treated as new records then it follows that cloned objects should be dirty

    May 15th, 2010 @ 07:29 PM

  10. pleax
    pleax

    @elcuervo But it doesn't! As you can see from test-case I provided, some attributes of cloned object behaves differently from new one.

    May 15th, 2010 @ 07:37 PM

  11. Federico Brubacher
    Federico Brubacher

    Pleax if you read the thread in this ticket you will see what is the behavior that is bugging me and some of the other users.

    What the thread starter and Armit are saying is that for them the clone method is doing is creating a new Object and copying all the attributes from the original. Even if those were blank in the first object.

    So that's basically the idea of this ticket. To repeat Fian : One could argue that the behaviour is correct since #new() also results in #changed?() being false.

    Doesn't make sense for me to copy the dirty attribute flag from the first object.

    May 15th, 2010 @ 08:05 PM

  12. Federico Brubacher
    Federico Brubacher
    • Tag changed from activemodel, bugmash, dirty, patch to activemodel, bugmash-review, dirty, patch

    May 15th, 2010 @ 09:24 PM

  13. pleax
    pleax

    Still not clear to me.

    You're trying to say that new objects doesn't treat modified attributes as changed?. But they do:

    t = Topic.new :title => 'test'
    t.changed? # => true
    t.title_changed? # => true
    t.content_changed? # => false (!)
    

    And yes. There is no sence to copy dirty-flags from original object. There should be different implementation.

    I'm trying to say that attributes of clonned object should be threated as changed? only if they differ from defaults.

    Also I've found strange behavior with the patch applied:

    t = Topic.new
    t.changed? # => false
    t.clone
    t.changed? # => true (!)
    

    May 15th, 2010 @ 10:20 PM

  14. Rizwan Reza
    Rizwan Reza
    • Tag changed from activemodel, bugmash-review, dirty, patch to activemodel, bugmash, dirty, patch

    May 16th, 2010 @ 02:22 AM

  15. Federico Brubacher
    Federico Brubacher

    I've attached a patch.

    "I'm trying to say that attributes of clonned object should be threated as changed? only if they differ from defaults."

    I think I can agree with that, I added a test and a fix. Please confirm. Also next time upload a patch so i can give you credit.

    About the strange issue, I'm opening a new ticket now. Here : https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets...

    May 16th, 2010 @ 11:29 AM

  16. Federico Brubacher
    Federico Brubacher

    I meant to say in the previous comment that the issue was present in master before my fix.

    May 16th, 2010 @ 11:32 AM

  17. pleax
    pleax

    I added a test and a fix. Please confirm.

    Don't see it.

    I meant to say in the previous comment that the issue was present in master before my fix.

    Agree.

    May 16th, 2010 @ 12:05 PM

  18. Federico Brubacher
  19. pleax
    pleax

    I'm not sure if check for blank? is acceptable here. Because model attributes may have non-empty defaults.

      test "non-empty defaults should not threated as changed" do
        topic = Topic.new
        assert_equal "non-empty-default", topic.group
        assert !topic.group_changed?
        
        cloned_topic = topic.clone
        assert_equal "non-empty-default", cloned_topic.group
        assert !topic.group_changed?
      end
    

    May 16th, 2010 @ 02:49 PM

  20. pleax
    pleax

    Last assertion should be assert !cloned_topic.group_changed? offcourse.

    May 16th, 2010 @ 02:51 PM

  21. Eugene Bolshakov
    Eugene Bolshakov

    We have updated the patch so that it takes default values into account. Cloning now involves creation of another model object against which attributes of the clone are checked (and marked as dirty if necessary), which is not great, but it was the best we could come up with.

    May 16th, 2010 @ 05:18 PM

  22. pleax
    pleax

    I've attached patch with bunch of test-cases and my version of fix.

    May 16th, 2010 @ 10:56 PM

  23. pleax
  24. Rizwan Reza
    Rizwan Reza

    Please test this and report back so it can be committed.

    May 16th, 2010 @ 11:16 PM

  25. Rafael Mendonça França
  26. Repository
    Repository
    • State changed from new to committed

    May 17th, 2010 @ 02:03 AM

  27. Rizwan Reza
    Rizwan Reza
    • Tag changed from activemodel, bugmash, dirty, patch to activemodel, dirty, patch

    May 17th, 2010 @ 12:09 PM

  28. Ryan Bigg
    Ryan Bigg
    • Tag cleared.

    Automatic cleanup of spam.

    October 9th, 2010 @ 09:47 PM

  29. bingbing