# Best practice for object transaction?

**URL:** <https://www.ruby-forum.com/t/best-practice-for-object-transaction/136312>\
**Category:** Rails\
**Created:** [April 30, 2008, 7:22pm UTC](https://www.ruby-forum.com/t/best-practice-for-object-transaction/136312 "2008-04-30T19:22:51Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![fxn](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/f/77aa72/32.png) [@fxn](https://www.ruby-forum.com/u/fxn)\
**Post date:** [April 30, 2008, 7:22pm UTC](https://www.ruby-forum.com/t/best-practice-for-object-transaction/136312/1 "2008-04-30T19:22:51Z")

</div>

I have a new @offer that needs to generate a document if saved. But  
that’s all or nothing:

```
transaction do
  @offer.save!
  generate_document
end
# handle exceptions if needed

```

If save! fails everything is fine, but if generate\_document raises an  
exception @offer is left as a model with timestamps, id, which is not  
a new\_record? anymore (though the database was rolled back).

I’ve played a bit cloning, and with the object transaction plugin, but  
I am not satisfied with the solutions. Problem is we need to rollback  
the object except its errors, so to speak.

Do you have any best practices for this?

– fxn

---

<div class="post-metadata">

**Author:** ![fxn](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/f/77aa72/32.png) [@fxn](https://www.ruby-forum.com/u/fxn)\
**Post date:** [May 1, 2008, 2:01am UTC](https://www.ruby-forum.com/t/best-practice-for-object-transaction/136312/2 "2008-05-01T02:01:29Z")

</div>

I’m not 100% sure on this, but I think what you’re looking for is  
ActiveRecord::Observer.

See this example that is used to send an email after saving comments  
to what I assume is a blogging application:

class CommentObserver \< ActiveRecord::Observer  
def after\_save(comment)  
Notifications.deliver\_comment(“removed\_email\_address@domain.invalid”, “New comment was  
posted”, comment)  
end  
end

---

<div class="post-metadata">

**Author:** ![fxn](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/f/77aa72/32.png) [@fxn](https://www.ruby-forum.com/u/fxn)\
**Post date:** [May 1, 2008, 2:36am UTC](https://www.ruby-forum.com/t/best-practice-for-object-transaction/136312/3 "2008-05-01T02:36:05Z")

</div>

On May 1, 2008, at 2:00 , Robert W. wrote:

> posted", comment)  
> end  
> end

Thank you, problem is that the document is not always generated.

By now the best I’ve got is

```
 rollback_active_record_state! do
   transaction do
     save!
     generate_document
   end
 end
 # handle exceptions

```

self preserves the @new\_record flag and id housekeeping. It still has  
created\_at even if it was not actually created, but is the better  
solution I’ve seen so far.

– fxn

---

<div class="post-metadata">

**Author:** ![fxn](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/f/77aa72/32.png) [@fxn](https://www.ruby-forum.com/u/fxn)\
**Post date:** [May 1, 2008, 3:06am UTC](https://www.ruby-forum.com/t/best-practice-for-object-transaction/136312/4 "2008-05-01T03:06:30Z")

</div>

I guess I’m not seeing the problem.

What do you mean by?

> Thank you, problem is that the document is not always generated.

Does this mean that if the document fails to generate then the offer  
should not save to the database and remain as a new (unsaved) object?

I am assuming that you only want to create the document when creating  
offers, not updating existing offers. If that’s the case, then the  
observer should implement after\_create instead of after\_save. If  
anything goes wrong during create\_document and it raises an exception  
then ActiveRecord will not insert the offer, and you should still have  
a new Offer object. Then you can present errors back to the user, or  
whatever error handling you need to do.

I suppose I must be missing something obvious here, but I can’t think  
of what that might be.

---

<div class="post-metadata">

**Author:** ![fxn](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/f/77aa72/32.png) [@fxn](https://www.ruby-forum.com/u/fxn)\
**Post date:** [May 1, 2008, 3:41am UTC](https://www.ruby-forum.com/t/best-practice-for-object-transaction/136312/5 "2008-05-01T03:41:37Z")

</div>

Never mind. I think I see the problem now. You need the document to be  
created when and only when the offer creates successfully, and you  
also need the offer to roll back if the document creation fails.

Is your create\_document method inside of the Offer model class or is  
it in a different class? The observer allows you to move this rather  
unrelated code outside of the model object (since creating the  
document is not a direct responsibility of the Offer model class).  
Normally doing before\_create inside the model class and returning  
false in that method if something goes wrong will suspend the callback  
chain, and will leave your table in an unaltered state. However,  
returning false from the before\_create implementation inside the  
observer does not appear to suspend the callback chain. So this must  
be why you are using transactions to work around this problem.

Out of curiosity would create\_document be creating database records?  
If that’s the case then transactions would likely be necessary anyway.

---

<div class="post-metadata">

**Author:** ![fxn](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/f/77aa72/32.png) [@fxn](https://www.ruby-forum.com/u/fxn)\
**Post date:** [May 2, 2008, 3:30pm UTC](https://www.ruby-forum.com/t/best-practice-for-object-transaction/136312/6 "2008-05-02T15:30:36Z")

</div>

On Thu, May 1, 2008 at 11:58 AM, Xavier N. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> I think leaving timestamps with the new values is a bug. I mean, a  
> new\_record? shouldn’t have a timestamp in created\_at, I can live with that  
> though I’ll try to determine if that’s a bug and fill a ticket if that’s the  
> case.

Here it is:  
[http://rails.lighthouseapp.com/projects/8994/tickets/93-restore-timestamps-in-model-rollback#ticket-93-1](http://rails.lighthouseapp.com/projects/8994/tickets/93-restore-timestamps-in-model-rollback#ticket-93-1)

---

<div class="post-metadata">

**Author:** ![fxn](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/f/77aa72/32.png) [@fxn](https://www.ruby-forum.com/u/fxn)\
**Post date:** [May 1, 2008, 11:59am UTC](https://www.ruby-forum.com/t/best-practice-for-object-transaction/136312/7 "2008-05-01T11:59:44Z")

</div>

On May 1, 2008, at 3:41 , Robert W. wrote:

> Never mind. I think I see the problem now. You need the document to be  
> created when and only when the offer creates successfully, and you  
> also need the offer to roll back if the document creation fails.

That’s right.

> Is your create\_document method inside of the Offer model class or is  
> it in a different class? The observer allows you to move this rather  
> unrelated code outside of the model object (since creating the  
> document is not a direct responsibility of the Offer model class).

Well in this case an offer knows how to generate its own document.

Note than an observer as far as this problem is concerned is not much  
different than a regular AR callback. I mean, the techique that does  
not work is after\_\*, no matter whether it is in the model or in an  
observer.

after\_\* does not work for what I need because the offer is not aware  
of whether it needs to save a document or not, so I can’t put the  
conditional in the callback. I think I can revise that, but even if it  
knew it you can’t rollback the INSERT in an after\_create, you can  
abort in before\_\*s. Well you could raise an exception in after\_\*s, but  
that’s unexpected behaviour because you do not expect @offer.save to  
raise an exception like that.

So what I do with rollback\_active\_record\_state! is basically what AR  
does, plus catching exceptions from document generation. With that  
code I can implement the transaction I need, have @offer (almost)  
restored, and return a controlled boolean as usual.

if @offer.save\_and\_generate\_document  
# …  
end

I think leaving timestamps with the new values is a bug. I mean, a  
new\_record? shouldn’t have a timestamp in created\_at, I can live with  
that though I’ll try to determine if that’s a bug and fill a ticket if  
that’s the case.

Thank you for helping Robert.

– fxn
