Save related models from one form


#1

I have three tables customers, indentities and people. I hav ecreated
models for them. The are connected with foreign keys and
has_many/belongs_to in this fashion:

customers has_many: identities
identities belongs_to: customer
identities has_many: people
people belongs_to: identities

Now, I have a form in which I would like to create db entries for the
three tables. customers have a password field that needs to be equal to
the params[:passwordconfirm], otherwise none of the models should be
saved to the db.

I also have some data checks (validated_presence_of and
validates_uniqueness_of) in the models.

If the data checks or password confirmation fails, I want that the form
i displayed again so that the user can correct the errors. Else the data
shoud be saved to the db. If I input data in all required fields all is
fine, but…

My problems are:

  1. If i input valid data in the required field, but type the
    passwordconfirmation so that it will fail I get the error message:
    ArgumentError which I guess has something to do with the “raise”-line
    below.

  2. If I omitt some of the required fields I get the following error
    message:
    ActiveRecord::StatementInvalid …Column ‘customer_id’ cannot be null:
    INSERT INTO identities…

The controller action looks like this. I guess the code inside the
transaction block contains errors… however I would need some help in
finding out whats wrong. Please help me!

def create_data
@customer = Customer.new
@identity = Identity.new
@person = Person.new

    @identity.customer = @customer
    @person.identity = @identity

    if request.post?
        @pwdmismatch = false
        @customer.attributes = params[:customer]
        @identity.attributes = params[:identity]
        @person.attributes = params[:person]
        undigestedPassword = @customer.password
        @customer.password = MD5.md5(undigestedPassword).hexdigest
        begin
            @customer.transaction(@customer, @identity, @person) do
                @customer.save
                @identity.save
                @person.save

                if(undigestedPassword != params[:passwordconfirm])
                    @pwdmismatch = true
                end

                raise ActiveRecord::RecordInvalid unless

@customer.valid? && @identity.valid? && @person.valid? && (@pwdmismatch
== false)
login(@customer.username, undigestedPassword,
“identity”) #Login and redirect the user to the selected identity
end #end transaction
rescue ActiveRecord::RecordInvalid
end
end
end #end of create data

My rhtml-file uses “error_message_on”, “error_messages_for” and checking
if pwdmismatch is true to mark errors in the input data.

Like I said, I would need some help on this.

Best regards,
Daniel


#2
  1. If i input valid data in the required field, but type the
    passwordconfirmation so that it will fail I get the error message:
    ArgumentError which I guess has something to do with the “raise”-line
    below.
    See below for the fix.
    def create_data
    @customer = Customer.new
    @identity = Identity.new
    @person = Person.new

     @identity.customer = @customer
     @person.identity = @identity
    

These last two lines won’t do what you want them to do. You cannot
set a relationship like this until the record with the foreign key
has been saved. Eg.
@customer.save
@identity.costomer = @customer
If you jump the gun like you did then @customer.id will be nil so
@identity.customer_id will be nil. Same with @identity.id and
@person.identity_id. But doing what I suggest is a paradox since you
don’t want to save it yet. I struggle with this same paradox all the
time. No one has ever given me a good solutions, only hacks (see hack
below).

(@pwdmismatch
== false)
@customer.save calls @customer.valid? and returns the result. You are
repeating validation several times here.

The grand solution/hack is something like:

def create_data
@customer = Customer.new
@identity = Identity.new
@person = Person.new

     #too soon for this to work
     #@identity.customer = @customer
     #@person.identity = @identity

     if request.post?
         @pwdmismatch = false
         @customer.attributes = params[:customer]
         @identity.attributes = params[:identity]
         @person.attributes = params[:person]
         undigestedPassword = @customer.password
         @customer.password = MD5.md5(undigestedPassword).hexdigest
         begin
             @customer.transaction(@customer, @identity, @person) do
                 customer_valid = @customer.save
                 @identity.customer = @customer
                 identity_valid = @identity.save
                 @person.identity = @identity
                 person_valid = @person.save

                 if(undigestedPassword != params[:passwordconfirm])
                     @pwdmismatch = true
                 end

                 raise ActiveRecord::RecordInvalid.new(@customer)

unless
customer_valid && identity_valid && person_valid && (@pwdmismatch
== false)
login(@customer.username, undigestedPassword,
“identity”) #Login and redirect the user to the selected identity
end #end transaction
rescue ActiveRecord::RecordInvalid
end
end
end #end of create data

-John


John S.
Computing Staff - Webmaster
Kavli Institute for Theoretical Physics
University of California, Santa Barbara
removed_email_address@domain.invalid
(805) 893-6307


#3

Thank you for taking your time to explain this. I really appreciate it!

This solution only works if I remove the “not null”-checks in all
foreign key fields in the affected tables (customer, identities,
people)(mySQL). I guess I dont need to have the db check for “not null”
when I have the validation in the models. Is that right?

It feels somewhat weird that it wont work if I have the checks for “not
null”-foreign keys in the database. Is it not possible to have both
validation in models and that the db checks that the foreign keys are
not null?

If I use “not null” for the foreign keys in the database and
validate_presence_of the foreign keys in the models as well, I get a SQL
error saying something about foreign key cant be null. It seems like the
model validation is ignored, rails tries to save the record straight to
the db which causes the error to pop up.

//Daniel