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 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 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 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 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
nameis 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 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 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 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.
-
-

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) 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 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"..
