class Document < ActiveRecord::Base
has_many :revisions
# attributes: Title, Number, etc...
end
Class Revision < ActiveRecord::Base
belongs_to :Document
# attributes: document_id, revision, release_status, etc..
end
I have a controller & view for the Revision model (note, this is the
model that belongs to the Document model).
the "edit" view looks something like this:
<% form_for(@revision) do |f| %>
<% fields_for(@revision.number) do |r| %>
<%= r.text_field :title %>
<%= r.text_field :number %>
...
When my "update" method gets called, how can I update the
corresponding "revision.number" record simultaneously with updating my
"revision" record?
I know I can do something like this:
success = @revision.number.update_attributes(params[:number])
success &= @revision.update_attributes(params[:revision])
but, what happens if the second call to "update_attributes" fails? I
will have updated the Number model, but not the Revision model.
Is there an idiomatic way to handle this?
I suppose I could rethink my application, and write a documents
controller & view and update the revision as part of updating the
document. But I would like to know how to update the parent of a
belongs_to association regardless.
Let's say that we wanted to do the same thing, only as part of a
plugin. For instance, say we are creating a plugin called
acts_as_cached that causes a set of fields in a shadow model to be
updated whenever the target model is created/updated. The natural
implementation is to locate or create the shadow record and update the
fields using the appropriate callback. The problem with using
transactions in this case is that there don't seem to be any good ways
to specify or finalize the transaction with callbacks. Without
transactions, there is no guarantee that things will get saved or
recovered on errors. With transactions, there is no way to start and
finish the transaction in the same callback but still save the both
records at the right spot in the callback chain.
Note that observers won't work -- they aren't transactional.
The only thing I can think of is to alias wrap the target class state-
changing methods (create, update, destroy) and put the transaction
code in there, in turn calling the previous CUD operations inside a
transaction.
Thanks...
I thought about using a transaction, but couldn't find any description
of it in my "The Rails Way" book -- at least it's not in the index
anywhere, and I couldn't find it in the ActiveRecord chapters I
skimmed.
I was a little concerned that a transaction seemed to be specific to a
single model (the "Document.transaction" part scared me.) But I now
see in the API that the records need not all be instances of that
class.
Looking at the API, all of the examples use the #save! method. Should
I be concerned that I am using the #update_attributes method? Or
should I presume that it does the right thing. Hmmm... looking more
carefully, I see an #update_attributes! method that will throw an
exception if the update fails (actually, if the #save! fails). I'm
not sure what you mean by "you rollback the transaction". It seems to
me that the rollback would occur automatically if something bad, say
an exception, occurred. Do I have the gist of this right? Or is
there something else I need to do to roll it back?
Finally, I have this niggling suspicion that I read somewhere that the
use of transactions was deprecated, but I could be confusing that with
something else.
Again, thanks for pointing me in a promising direction.
You're right that you should use the update_attributes! bang method,
because it will fail with an exception. Throwing an exception inside a
transaction block causes the transaction to rollback. That's how you
cause a rollback to happen. You should still rescue the exception and
handle it somehow, at the very least by presenting an error message to
the user.
Document.transaction do
# update the revision
# update the document
end
If one fails, you rollback the transaction and you're done; no
half-modified record pairs.
Thanks...
I thought about using a transaction, but couldn't find any description
of it in my "The Rails Way" book -- at least it's not in the index
anywhere, and I couldn't find it in the ActiveRecord chapters I
skimmed.
I was a little concerned that a transaction seemed to be specific to a
single model (the "Document.transaction" part scared me.) But I now
see in the API that the records need not all be instances of that
class.
Document.transaction is just shorthand for Document.connection.transaction: the only time the class upon which you call transaction is relevant is when your model classes are spread across multiple databases and you need to be sure that you're creating transactions in the right one. If you don't like looking like you're favouring one model you could always use ActiveRecord::Base.transaction
Finally, I have this niggling suspicion that I read somewhere that the
use of transactions was deprecated, but I could be confusing that with
something else.
What was deprecated (or even removed, can't remember) was object transactions, ie a rollback triggering the rollback of a ruby object (instance variables etc...)
Document.transaction is just shorthand for
Document.connection.transaction: the only time the class upon which
you call transaction is relevant is when your model classes are spread
across multiple databases and you need to be sure that you're creating
transactions in the right one. If you don't like looking like you're
favouring one model you could always use ActiveRecord::Base.transaction
Which is more common in the community?
MyModel.transaction
or
ActiveRecord::Base.transaction
One of the challenges of learning to speak any foreign language is
learning the idioms, because the literal translation seems strange in
one's native tongue. Hence the use of "MyModel.transaction" feels
strange to me, but if that is the idiom that is typically used (and I
suspect it is), then I can start using it as well and gain slightly
more fluency in this "RoR" language and the culture I am (very slowly)
joining.
Finally, I have this niggling suspicion that I read somewhere that the
use of transactions was deprecated, but I could be confusing that with
something else.
What was deprecated (or even removed, can't remember) was object
transactions, ie a rollback triggering the rollback of a ruby object
(instance variables etc...)
Ahhh.... now that I read that, I do seem to recall seeing the word
"object" in front of the word "transaction". OK, I'll use
transactions in the full confidence that they are the right way to
solve this sort of problem.