views:

60

answers:

1

Hello,

I've come across an oddity in ActiveRecord's #relationship_ids method (that's added automatically when you declare 'has_many'), which saves immediately for existing records, which is causing me some issues, and I wonder if anyone had any useful advice.

I'm running Rails 2.3.5.

Consider this simple scenario, where an article has_many tags, say:

a = Article.first
a.name = "New Name" # No save yet
a.author_id = 1     # No save yet
a.tag_ids = [1,2,3] # These changes are saved to the database
                    # immediately, even if I don't subsequently
                    # call 'a.save'

This seems surprising to me. It's specifically causing problems whilst trying to build a preview facility - I want to update a bunch of attributes and then preview the article without saving it - but in this instance the tag changes do get saved, even though no other fields do.

(Of possible relevance is that if 'a' is a new article, rather than an existing one, things behave as I'd expect - nothing is saved until I call 'a.save')

I have a fairly nasty workaround - I can override the tag_ids= method in my model to instead populate an instance variable, and actually save the related models in a before_save callback.

But I'd love to know of a simpler way than me having to do this for every model with a has_many relationship I'd like to create a preview facility for.

Does anyone have any fixes/workarounds/general advice? Thanks!

+3  A: 

There's a reason things are this way. It's called foreign keys. In a has many relationship, the information that links to the model that has many is stored outside of that model as a foreign key.

As in Articles, has many tags. The information that links a tag to an article is stored either in the tags table or in a join table. When you call save on an article you're only saving the article.

Active record modifies those other records immediately. Except in the case where you're working with a new article that hasn't been saved yet. Rails will delay creating/updating the associated records if it doesn't know which id to place in the foreign key.

However, if you're modifying existing records, the solution you've decided on is really all that you can do. There's an even uglier hack using accepts_nested_attributes_for, but it's really not worth the effort.

If you're looking to add this behaviour to many models but not all models, you might want to consider writing a simple plugin to redefine the assigment the method you need and add the call back in a single class method call. Have a look at the source of something like acts_as_audited to see how it's done.

If you're looking to add this behaviour to all models, you can probably write a wrapper for has_many to do that.

EmFi
Thanks for your comments.I somewhat understand your foreign key arguments, although I'm still not clear what part of the arrangement dictates that the save must happen immediately - or makes most sense when it does.But thanks for your suggestions - I'll do some further investigation.
NeilS