This project is archived and is in readonly mode.

#541 ✓stale
Josh Clayton

Dirty Attributes with Associations

Reported by Josh Clayton | July 2nd, 2008 @ 10:31 PM | in 3.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 set 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.

  • ronin-24025 (at lighthouseapp)

    ronin-24025 (at lighthouseapp) 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 set 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.

  • ronin-24025 (at lighthouseapp)

    ronin-24025 (at lighthouseapp) 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.

  • ronin-24025 (at lighthouseapp)
  • 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?

  • Josh Pencheon

    Josh Pencheon 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

  • Leandro Pedroni

    Leandro Pedroni February 15th, 2009 @ 01:29 AM

    Well, it would make sense that if you overwrite the content of an object's association you would be able to know that you did so and what you are replacing.

    
    user = User.last
    
    user.ticket
    # => #<Ticket id:101, queue_position:41, ...>
    user.create_ticket
    # => #<Ticket id:102, queue_position:42, ...>
    user.ticket_changed?
    # => true
    user.ticket_was
    # => #<Ticket id:101, queue_position:41, ...>
    

    All I can think of now is changing the association_accessor_methods, association_constructor_method and collection_accessor_methods in Activerecord::Associations and cache the old object in the reflection itself. But I'm not sure I understood any of that correctly and that wouldn't be as modular as Dirty that simply wraps around write_attribute when loaded.

  • Jeremy Kemper

    Jeremy Kemper May 4th, 2010 @ 06:48 PM

    • Milestone changed from 2.x to 3.x
  • Brian Armstrong

    Brian Armstrong June 11th, 2010 @ 01:06 AM

    +1 for this - I think it makes sense to include.

    Rationale: when I think of objects in real life "changing" I don't distinguish attributes vs associations. This is closest to it's meaning in the English language. For example, if I say "there is something different about this class" I could just as easily be referring to the new teacher/students in the class (associations) as the title of the class (attribute).

    It also feels more "rubyish" following the principles of:
    1. emphasize human, rather than computer, needs and
    2. the principle of least astonishment (POLA), meaning that the language should behave in such a way as to minimize confusion

    Basically - when I heard about the dirty functions, it was my first assumption to assume/hope this encompassed associations, since that seemed most natural. But I wasn't really surprised to find they didn't, given that it's obviously harder to implement.

    Anyway, just wanted to add that...

  • Santiago Pastorino

    Santiago Pastorino February 2nd, 2011 @ 04:37 PM

    • State changed from “incomplete” to “open”
    • Importance changed from “” to “”

    This issue has been automatically marked as stale because it has not been commented on for at least three months.

    The resources of the Rails core team are limited, and so we are asking for your help. If you can still reproduce this error on the 3-0-stable branch or on master, please reply with all of the information you have about it and add "[state:open]" to your comment. This will reopen the ticket for review. Likewise, if you feel that this is a very important feature for Rails to include, please reply with your explanation so we can consider it.

    Thank you for all your contributions, and we hope you will understand this step to focus our efforts where they are most helpful.

  • Santiago Pastorino

    Santiago Pastorino February 2nd, 2011 @ 04:37 PM

    • State changed from “open” to “stale”

Create your profile

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

<h2 style="font-size: 14px">Tickets have moved to Github</h2>

The new ticket tracker is available at <a href="https://github.com/rails/rails/issues">https://github.com/rails/rails/issues</a>

Attachments

Pages