Forum: Ruby on Rails Validations not working on complex DB transaction

10db1164f471223045c846f476cbc59f?d=identicon&s=25 masta Blasta (mastablasta)
on 2013-08-01 16:56
The models:

class User
    has_many :institution_memberships
    belongs_to :account

    after_create :set_default_membership

    def set_default_membership
        if institution_memberships.empty?
          institution_memberships.create(default_data_from_account)
        end
    end
end

class InstitutionMembership
    validates_uniqueness_of :user_id, :scope => :institution_id
end

Controller:

def create
    @account = Account.new
    @account.transaction do
        @user = User.new(params[:user])
        @institution  = Institution.new(:name => @account.name)
        @user.account = @account
        @institution.account = @account
        @institution_membership      = InstitutionMembership.new(
          :institution      => @institution,
          :user             => @user
        )

        if @user.save && @institution.save &&
@institution_membership.save

        end
    end
end

To put this into context. A user can belong to one or many institutions.
The relationship is managed through :institution_memberships. A user can
only have one membership per institution, hence the
validates_uniqueness_of :user_id, :scope => :institution_id

There is a lot more code happening around all of this than i've shown,
but the main problem is that basically two memberships are being created
for a user with the same institution. The validate is not working. The
after_create callback is creating an object successfully, and the
standard save in the controller is working.

My guess is it has something to do with the timing of things and when
they're actually written to the database, but i don't know that process
well enough to pinpoint the exact cause. Obviously there is no need to
create a membership object in the controller, but the code had been that
way for a long time without a problem. Nobody noticed it until suddenly
the bad data started showing up. I'm more curious as to why the
validation goes through.

What's the community think?
6883e5ef03484d4fcef507d7b4f1d243?d=identicon&s=25 Matt Jones (Guest)
on 2013-08-02 18:18
(Received via mailing list)
On Thursday, 1 August 2013 10:56:06 UTC-4, Ruby-Forum.com User wrote:
>         if institution_memberships.empty?
>
>         )
> only have one membership per institution, hence the
> well enough to pinpoint the exact cause. Obviously there is no need to
> create a membership object in the controller, but the code had been that
> way for a long time without a problem. Nobody noticed it until suddenly
> the bad data started showing up. I'm more curious as to why the
> validation goes through.
>
> What's the community think?
>
>
Can you post a log of the SQL that gets executed during the transaction?
I'm curious about what the validates_uniqueness_of is looking up, if
anything.

--Matt Jones
10db1164f471223045c846f476cbc59f?d=identicon&s=25 masta Blasta (mastablasta)
on 2013-08-02 22:30
Attachment: query_log.txt (2 KB)
Matt Jones wrote in post #1117517:
> On Thursday, 1 August 2013 10:56:06 UTC-4, Ruby-Forum.com User wrote:
>>         if institution_memberships.empty?
>>
>>         )
>> only have one membership per institution, hence the
>> well enough to pinpoint the exact cause. Obviously there is no need to
>> create a membership object in the controller, but the code had been that
>> way for a long time without a problem. Nobody noticed it until suddenly
>> the bad data started showing up. I'm more curious as to why the
>> validation goes through.
>>
>> What's the community think?
>>
>>
> Can you post a log of the SQL that gets executed during the transaction?
> I'm curious about what the validates_uniqueness_of is looking up, if
> anything.
>
> --Matt Jones


I attached a part of the log from my functional testing. What i assume
to be the validation check SELECT statement does not have a user_id yet.
However the DB INSERT occurring right after, does have the user_id.

Very puzzling. FYI I'm on Rails 2.3.15
A33d5d269edd153e33826538060d1a22?d=identicon&s=25 selva4210@gmail.com (Guest)
on 2013-08-06 11:22
(Received via mailing list)
Hi,

The @user object is getting saved first. That time, there is no
instuition membership attached to it. So one is getting created. Then
when you save the instuition membership again inside the controller it
also gets saved.

Is this clear?

On Sat, Aug 3, 2013 at 2:00 AM, masta Blasta <lists@ruby-forum.com>
wrote:
>>> validation goes through.
>
> --
> Posted via http://www.ruby-forum.com/.
>
> --
> You received this message because you are subscribed to the Google Groups "Ruby
on Rails: Talk" group.
> To unsubscribe from this group and stop receiving emails from it, send an email
to rubyonrails-talk+unsubscribe@googlegroups.com.
> To post to this group, send email to rubyonrails-talk@googlegroups.com.
> To view this discussion on the web visit
https://groups.google.com/d/msgid/rubyonrails-talk....
> For more options, visit https://groups.google.com/groups/opt_out.
>
>



--
Azhagu Selvan

http://tamizhgeek.in
06e4c84000579d88c9298a6616a42e29?d=identicon&s=25 Carlos Figueiredo (Guest)
on 2013-08-06 13:21
(Received via mailing list)
Yeah, It's exactly that... you are creating a user before the
Institution...
But I your controller isn't searching for some Institution while
creating
the User... I just see your controller creating an Institution...
doesn't
matter if already exists...



*Carlos Figueiredo*
10db1164f471223045c846f476cbc59f?d=identicon&s=25 masta Blasta (mastablasta)
on 2013-08-06 16:13
selva4210@gmail.com wrote in post #1117870:
> Hi,
>
> The @user object is getting saved first. That time, there is no
> instuition membership attached to it. So one is getting created. Then
> when you save the instuition membership again inside the controller it
> also gets saved.
>
> Is this clear?

You just restated what i had initially written.


The "problem" is something to do with how rails connects newly
initialized objects that are in memory but not yet in the database.

We have built two objects like this
u = User.new
im = InstitutionMembership.new :user => u

At that point the u (User) object doesn't know anything about the im
object. Saving the u object does not trigger an autosave on the
association, and the create callback creates a new membership. That's
somewhat expected.

The odd issues appear after -
u = User.new
im = InstitutionMembership.new :user => u
u.save
im.save

'im' is aware of the user object, however it is not able to validate
against it. When it attempts to validate, the user_id==NULL. At the very
next log entry though, it saves itself with the correct user_id. So
somewhere between the validate callbacks, and the create and commit
callbacks, the 'im' object refreshed the user object and retrieved the
user_id. Also the new_record? flag on 'im' is still set, so a new row is
created.

One of the better solutions was actually to do:
u = User.new
im = u.institution_memberships.build

This is able to properly connect the objects in memory, and the process
works smoothly. The association is autosaved on u.save
9570b2f45e7de7a24d8f3bf4b2517192?d=identicon&s=25 Rob Biedenharn (Guest)
on 2013-08-06 16:46
(Received via mailing list)
On 2013-Aug-6, at 10:13 , masta Blasta wrote:

> You just restated what i had initially written.
> object. Saving the u object does not trigger an autosave on the
> association, and the create callback creates a new membership. That's
> somewhat expected.

So tell ActiveRecord that you *want* the autosave:

class User < ActiveRecord::Base
belongs_to :account
has_many :institution_memberships, :autosave => :always
has_many :institutions, :through => :institution_memberships

after_create :set_default_membership

def set_default_membership
  if institution_memberships(true).blank?
    institution_memberships.create(default_data_from_account)
  end
end
end

class InstitutionMembership < ActiveRecord::Base
belongs_to :user
belongs_to :institution
validates_uniqueness_of :user_id, :scope => :institution_id
end

Note the argument to 'institution_memberships(true)' which causes the
memberships to be reloaded from the database.

>
> The odd issues appear after -
> u = User.new
> im = InstitutionMembership.new :user => u
> u.save
> im.save

I assume that you've simplified this so that the account_id or however
the default_data_from_account manages to find the institution_id is
missing.

u = User.new
u.institutions << Institution.find(somehow)

or

u = User.new
u.institution_memberships << InstitutionMembership.new(:institution_id
=> from_a_parameter_perhaps)

and then the u.save should do the right thing.

-Rob
9570b2f45e7de7a24d8f3bf4b2517192?d=identicon&s=25 Rob Biedenharn (Guest)
on 2013-08-06 16:48
(Received via mailing list)
On 2013-Aug-6, at 10:13 , masta Blasta wrote:

> You just restated what i had initially written.
> object. Saving the u object does not trigger an autosave on the
> association, and the create callback creates a new membership. That's
> somewhat expected.

So tell ActiveRecord that you *want* the autosave:

class User < ActiveRecord::Base
belongs_to :account
has_many :institution_memberships, :autosave => :always
has_many :institutions, :through => :institution_memberships

after_create :set_default_membership

def set_default_membership
 if institution_memberships(true).blank?
   institution_memberships.create(default_data_from_account)
 end
end
end

class InstitutionMembership < ActiveRecord::Base
belongs_to :user
belongs_to :institution
validates_uniqueness_of :user_id, :scope => :institution_id
end

Note the argument to 'institution_memberships(true)' which causes the
memberships to be reloaded from the database and the :autosave option
added to the has_many.

>
> The odd issues appear after -
> u = User.new
> im = InstitutionMembership.new :user => u
> u.save
> im.save

I assume that you've simplified this so that the account_id or however
the default_data_from_account manages to find the institution_id is
missing.

u = User.new
u.institutions << Institution.find(somehow)

or

u = User.new
u.institution_memberships << InstitutionMembership.new(:institution_id
=> from_a_parameter_perhaps)

and then the u.save should do the right thing.

-Rob
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.