#541 √ incomplete
Josh Clayton

Dirty Attributes with Associations

Reported by Josh Clayton | July 2nd, 2008 @ 10:31 PM | in 2.x

Currently, to track dirty attributes on associations, you need to reference with a foreign key.

class Ticket < ActiveRecord::Base
  belongs_to :owner, :class_name => "User", :foreign_key => "user_id"
end

class User < ActiveRecord::Base; end

ticket = Ticket.create! :name => 'My Ticket'
ticket.changed? # => false
ticket.owner = User.first
ticket.changed? # => true
ticket.changed # => ['user_id']
ticket.user_id_changed? # => true

I would like to have access to methods like ticket#owner_changed?, ticket#owner_was, etc.

I've attached a patch that resolves most of these issues; the only thing I was unable to do was make available association virtual attributes to get recognized by the attribute_method_suffix initially. Once they are set, the additional methods become available

Comments and changes to this ticket

  • Pratik

    Pratik July 4th, 2008 @ 12:23 AM

    • → Assigned user changed from “” to “Jeremy Kemper”

    I think this is a better fit for a plugin. It's not very common to track association changes.

  • TheBreeze

    TheBreeze July 8th, 2008 @ 12:09 AM

    • → Tag changed from “activerecord enhancement patch tested” to “association changes”
    • → Assigned user cleared.

    Josh: I wrote plugin for this functionality. The plugin definitely needs some work, including specs, but it works well. I'll be presenting it at SDRuby this Thursday, so I'm looking to get some work done on it this week. Info below:

    http://github.com/TheBreeze/shad...

    Contact me if you're interested.

  • Joran

    Joran August 20th, 2008 @ 07:58 AM

    Re: "It's not very common to track association changes."

    I would say that the data suggests otherwise.

    And as an example: "→ Assigned user changed from “” to “Jeremy Kemper”".

    I agree with Josh, we definitely need this in.

    And Dirty needs a whole lot of work. Where it could have been an elegant and conceptually simple solution, it's currently certainly not. This is giving rise to cruft (will_change!), unreliability and a whole load of recent tickets.

    I've seen votes for better syntax too: person.name.changed? instead of person.name_changed? since person.name.changed? asks the attribute (instead of the model) if it has changed, which is more semantically correct, and represents a more meta-data purpose. Plus it's far less painful to type.

  • Josh Susser

    Josh Susser August 20th, 2008 @ 03:00 PM

    person.name.changed? instead of person.name_changed?

    This seems like a bad idea to me. How can you tell the difference between asking if person's name attribute has a different value and the name itself having an attribute value changed? This could easily happen if name is an association to a model that contains first, middle and last attributes. Also, the API you propose would require layering a proxy object on top of simple attribute access the way it is on associations now. Sounds very complicated for minimal short-term benefit. Adding an AttributeProxy object could do some interesting stuff, but it would be a huge change and seems like a 3.0 kind of move.

  • Jeremy Kemper

    Jeremy Kemper August 20th, 2008 @ 05:30 PM

    • → State changed from “new” to “incomplete”
    • → Assigned user changed from “” to “Jeremy Kemper”

    Joran, your elegant, simple patches are welcome ;)

    Josh, it feels wrong to add association awareness to a feature that deals only with attribute changes. Perhaps it's asking for a better abstraction.

  • Joran

    Joran August 20th, 2008 @ 05:54 PM

    Jeremy, thanks, I better keep my mouth shut more in future! ;)

    Josh, surely "the difference between asking if person's name attribute has a different value and the name itself having an attribute value changed" is already the case with the existing syntax? Turtles all the way down? I agree it's complicated. And it's not the most important issue. In this case, it's getting rid of will_change! and adding better association support (I had to redefine other_ids=() today because of this and it's no fun).

    On the subject of a simpler design, hinging attribute_changed? on the attribute setter requires the will_change! flag and discourages use of methods such as attribute.upcase! etc. It's a slippery slope trying to remember when and where to use will_change!. The one advantage however is that the row is not kept in memory twice. i.e. there's no "before" and "after". But this is a machine advantage with not much human upside.

    I think the memory cost of a before and after approach is worth it. Then attributes can be changed any which way. There's no need for will_change! and Dirty gets more simple and reliable, esp. re: partial updates.

    More joy for the human. More pain for the machine.

    I'll put my newbie Rails simple mind to it and see what I can do. ;)

  • Josh Susser

    Josh Susser August 20th, 2008 @ 06:14 PM

    Too many Joshes! Plz use an unambiguous name when there's more than one of us about. How ironic when the point I'm trying to make is about ambiguous naming, heh.

    surely "the difference between asking if person's name attribute has a different value and the name itself having an attribute value changed" is already the case with the existing syntax?

    Not really. Right now name.changed? will tell you if a Name model has any changed attributes. It's difficult to tell by looking at the code if person.name.changed? means the person has a new name or the same name with a different attribute.

    Whenever you introduce a proxy you need to make sure it doesn't block methods on its target. For an example, create a Missile model with a :target attribute. Silo.belongs_to :missile. silo.missile.target returns.... the silo's missile, not the missile's target.

    Sorry if this is off-topic. I'm all for improving the dirty tracking API. Just let's not make things worse.

  • Joran

    Joran August 20th, 2008 @ 06:25 PM

    Josh Susser, right, got you now.

  • cs

    cs October 23rd, 2008 @ 08:42 AM

    Currently, to track dirty attributes on associations, you need to reference with a foreign key.

    How to do that with habtm associations?

    Thx, cs.

  • yves.vogl (at dock42)

    yves.vogl (at dock42) November 26th, 2008 @ 02:32 PM

    I just ran into the same problem.

    There's a Project model which has_and_belongs_to_many :users After assigning adding some more users and removing a few from the team (project.user_ids) I'd like to write an observer which determines the new teammates by substraction of project.users and project.users_were On the one hand I understand that especially a habtm association is more likely a foreign mapping of associations than a attribute of an object itself – on the other hand one could argue from a more abstract and object-related point of view where an association is just a way composing a single object.

    Using something like acts_as_versioned for this scenario seems to me like breaking a butterfly on the wheel. So… what do you suggest?

  • joshpencheon

    joshpencheon December 10th, 2008 @ 10:31 PM

    I've also had a go at making a plugin to do this, very much pre-alpha, but if you want a look:

    github.com/joshpencheon/dirty_associations

Please Login or create a free account to add a new comment.

You can update this ticket by sending an email to from your email client. (help)

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile »

Source available from github

Repository is at http://github.com/rails/rails

Check out the development master (Edge Rails):

git clone git://github.com/rails/rails.git

Creating or reviewing a patch

See the contributor guide.

Creating a feature request

Please don't. If you want a new feature in Rails, you'll have to pull up your sleeves and get busy yourself. Or convince someone else to do it. See the contributor guide on how to get going. But posting them here is just going to lead to ticket root.

Creating a bug report

When creating a bug report, be sure to include as much relevant information as possible. Post the code sample that causes the problem. Preferably, alter the unit tests and show through either changed or added tests how the expected behavior is not occuring.

Security vulnerabilities should be reported via an email to security@rubyonrails.org, do not use trac for reporting security vulnerabilities. All content in trac is publicly available as soon as it is posted.

Then don't get your hopes up. Unless you have a "Code Red, Mission Critical, The World is Coming to an End" kinda bug, you're creating this ticket in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the ticket automatically will see any activity or that others will jump to fix it. Creating a ticket like this is mostly to help yourself start on the path of fixing the problem and for others to sign on to with a "I'm having this problem too"..

Shared Ticket Bins