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: