Transaction started before Validations are checked

Hi all.. I was going to submit this as a bug...but thought I should learn more first ...

When #save is called on an ActiveRecord a transaction is started to catch any potential problems and try and recover from them.

This is cool.

But..

as a result of the Transaction module being mixed in quite late in the game, the transactions system is one of the *first* things to get initiated.........is the save_with_transaction system intentionally called before the validations system?

It seems wasteful to begin the transactions block if the call to #valid? simply returns false at the last minute?

Can anyone give me any insight into this system?, and can I start an agrument about allowing validations to occur *before* the transactions!

Thanks

It seems wasteful to begin the transactions block if the call to #valid? simply returns false at the last minute? Can anyone give me any insight into this system?,

I may not be correct here, I haven't checked the code, but...

When you save a record for which you have built some also unsaved associated records via association.build method and similars, calling save will attempt to save all the associated records.

Again, I haven't checked this, but I suspect if any associated record fails validation, all saves will be rolled back, hence the transaction.

Sure you could do a lot of checks before initiating any transaction, but at some point running all the validations would become more expensive than initiating a transaction and halting it early.

Also, many things can happen in ruby code, specially if you add plugins and do some metaprogramming magic. It's best for transactions to wrap all of it.

and can I start an agrument about allowing validations to occur *before* the transactions!

Can I start an argument about premature optimization? :stuck_out_tongue_winking_eye:

Thanx for the response... I'm not sure if a failed validation would cause a rollback.... since they don't raise an error. Unless you called #save!, but if this were the case then failed calls to #save would get past the rollback.

it is not the transactions job to rollback an #invalid? record .... that record should not have started the save process at all.

b = Book.new b.author = @author

b.valid? will call @author.valid? if told to by the relevent validation method on Book

> and can I start an > agrument about allowing validations to occur *before* the > transactions!

hehe..

now for my _real_ reason :slight_smile:

I'm throwing errors around during some of my own validations, and I'm managing to break the transaction system (it's getting confused because it thinks it should rollback - when it shouldn't)

after I looked around a bit I thought that what solves my problem [doing validation before starting a transaction], is actually slightly cleaner anyway...... unless there is some reason I'm blind to for having validation within the transaction?

Thanx for the response... I'm not sure if a failed validation would cause a rollback.... since they don't raise an error. Unless you called #save!, but if this were the case then failed calls to #save would get past the rollback.

Indeed, I meant save!.

I'm throwing errors around during some of my own validations, and I'm managing to break the transaction system (it's getting confused because it thinks it should rollback - when it shouldn't)

If you raise within a transaction and don't catch it, it rollsback. That's how it works.

Is anything different or specially weird happening in your case?

after I looked around a bit I thought that what solves my problem [doing validation before starting a transaction], is actually slightly cleaner anyway...... unless there is some reason I'm blind to for having validation within the transaction?

You can always call valid? yourself before saving. I guess that's what you're doing now?

A good reason to have a validation happen within the transaction is the save! case mentioned above.

But if the validation was happening first....then there would be no need to roll anything back, as the commit would not have started....no data would have changed ...and...the InvalidRecord error would still get raised as usual ...

and yes there is something weird happening in my case ....but shhh I'm using continuations - so I shall understand if I get no sympathy :slight_smile:

Thanks for your help with this.

Hi !

But if the validation was happening first....then there would be no need to roll anything back, as the commit would not have started....no data would have changed ...and...the InvalidRecord error would still get raised as usual ...

You cannot not wrap your validations inside a transaction :slight_smile:

Imagine the following:

class User < AR::B   validates_uniqueness_of :login end

The validation must look at a consistent database. If two requests come in at the same time, and request A's validation runs, then gets interrupted, request B runs and saves, then back to request A, that would create an invalid record.

I know, I know, ActionPack takes a global lock to prevent this problem, but if you aren't using ActionPack, you *must* run the validations inside the transaction.

Hope that helps !

Hi...

thanks this was closer to me giving in...but

I'll just write out your example in pseudoness, Use your mind's version of Thread to simulate this example... dave1 and dave2

1) dave1.save 1) dave2.save

2) transaction is started and dave1's state recorded for later 2) transaction is started and dave2's state recorded for later

3) dave1 is found to be valid but some process is stuck in IO and slows down] 3) dave2 is found to be valid and continues to save

4) dave1 waits 4) dave2 commits the save

5) dave1 saves fine unless an Exception is raised from the DB (or anywhere) 5) dave2 sulks

you're case would still fail under the current implimentation, unless you had placed some sort of restriction onto the column within your database itself which could cause the DB adaptor to raise during commit (5)

.... the active record validations are all in Ruby-land they are only useful for the very instance they are called (3).....inside or outside of a transaction block..... hence the big lock.

so...

validations outside of the transactions? :slight_smile:

You're post did actually make me think of an improvement to the validation system... that could actually solve the issue above.... might make another post to suggest the idea.

*bump*

Anyone have any other ideas / reasons for validations being within the transaction block?

I've been running rails patched for a few days and haven't had any problems....

would love some more reasoning, to find out if there is any future in my idea.

cheers

Out of curiosity, do all of the original ActiveRecord tests pass with your patched version?

Would you mind putting out a working example which demonstrates how validations outside the transaction are useful while inside are harmful?

I've seen your previous post with pseudocode, but I'd like to see running code that I can mess around with.

validates_uniqueness_of?

Kind regards, Thijs

the update:

After writing a little test case that fails under the current system and giving a few tests I've actually found that my particular issue is only with SQLite3.

Now, while testing I've actually found that the transaction system is not very friendly generally! ... it doesn't correctly remember when it has opened transactions and also is insistant on COMMITing transaction that have been ROLLBACKed ... [this is actually where my problem lies]

so...I've decided to fix it...

So far my fixes work on postgres and mysql and include:

** moving the object level transactions into their own module (ready for depreciation) ** properly ensuring transactions are ended correctly (no more postgres warnings!)

is it me or does SQLite3 fall flat on it's face in the tests? ... it fails badly on my macbook and my solaris systems... is this known or have just installed SQLite with my eyes closed?

thanks for your help.

oh..and... what initiates the migrations for the tests?... I can't find it and think this is where my sqlite3 test issues lie?

I've had issues with SQLite3 before where the primary keys were not incrementing correctly -- it would try to use 0 for everything! IIRC, the solution was to install swig, then install SQLite3 and finally install the SQLite3 ruby gem. I could be wrong about that -- can't find the reference (and it may have nothing to do with your problem).

Thanks John, I think I've had that problem too ... although along time ago, the latest gem doesn't require as much fiddling... I have a feeling most of my problems are just lack of love/testing on the SQLite adaptor development.

it seems to fails some pretty basic tests ..... I'm fixing as I find them... we'll see if they get in .... I just wanna see those little dots get right to the end :slight_smile: