Handling new form entries with relational tables

Ok. I'm new at this, but let me try and explain my problem. I hope some
generous soul will be kind enough to offer some guidance...

Let's say I have a form that accepts new input from an applicant. I
want to collect the applicant name (and other details) and a list of
two references, (actually more, but I'm trying to keep it simple).
Here's a form I've created for this, (it's called index.rhtml):

<%= start_form_tag :action => 'save_applicant' %>
<p>Applicant Info</p>
    <p>
    Name:
    <%= text_field "applicant", "name", "size" => 20 %>
    </p>
    <p>
    Address:
    <%= text_field "applicant", "address", "maxsize" => 20 %>
    </p>

<p>Reference Info</p>
    <p>
    Ref Name 1:
    <%= text_field "ref1", "name", "size" => 20 %>
    </p>
    <p>
    Ref Email 1:
    <%= text_field "ref1", "email", "maxsize" => 20 %>
    </p>

    <p>
    Ref Name 2:
    <%= text_field "ref2", "name", "size" => 20 %>
    </p>
    <p>
    Ref Email 2:
    <%= text_field "ref2", "email", "maxsize" => 20 %>
    </p>

    <input type="submit" value="Save">

<%= end_form_tag %>

Ok. I've got two models set up. An 'applicant' model and a 'ref' model.
The applicant 'has_many' refs and the ref 'belongs_to' the applicant.
I've got validation going on in both models...just
'validates_presence_of' for all fields.

The controller I'm submitting to is called apply_controller. It
contains the following method:

def save_applicant
    @applicant = Applicant.new(params[:applicant])
    if @applicant.save
       @applicant.refs.create(params[:ref1])
       @applicant.refs.create(params[:ref2])
        #go to an appropriate view to say 'yeah, it worked'
    else
      render_action "index" #redisplay the index page
    end
end

This all works fine as far as getting the data into the database goes.
But it does not validate the second model, i.e. the references. I can
leave them blank and not get an validation message.

Anyone know of a way to get the reference model to validate the data.

Also, I'm almost positive that my methodology here is not the greatest.
If you have any suggestions on a slicker ruby-esque way to accomplish
this same thing, I'd love to hear it.

Thanks in advance to any takers! :slight_smile:

Grasshoppa wrote:

Ok. I'm new at this, but let me try and explain my problem. I hope some
generous soul will be kind enough to offer some guidance...

Let's say I have a form that accepts new input from an applicant. I
want to collect the applicant name (and other details) and a list of
two references, (actually more, but I'm trying to keep it simple).
Here's a form I've created for this, (it's called index.rhtml):

<%= start_form_tag :action => 'save_applicant' %>
<p>Applicant Info</p>
    <p>
    Name:
    <%= text_field "applicant", "name", "size" => 20 %>
    </p>
    <p>
    Address:
    <%= text_field "applicant", "address", "maxsize" => 20 %>
    </p>

<p>Reference Info</p>
    <p>
    Ref Name 1:
    <%= text_field "ref1", "name", "size" => 20 %>
    </p>
    <p>
    Ref Email 1:
    <%= text_field "ref1", "email", "maxsize" => 20 %>
    </p>

    <p>
    Ref Name 2:
    <%= text_field "ref2", "name", "size" => 20 %>
    </p>
    <p>
    Ref Email 2:
    <%= text_field "ref2", "email", "maxsize" => 20 %>
    </p>

    <input type="submit" value="Save">

<%= end_form_tag %>

Ok. I've got two models set up. An 'applicant' model and a 'ref' model.
The applicant 'has_many' refs and the ref 'belongs_to' the applicant.
I've got validation going on in both models...just
'validates_presence_of' for all fields.

The controller I'm submitting to is called apply_controller. It
contains the following method:

def save_applicant
    @applicant = Applicant.new(params[:applicant])
    if @applicant.save
       @applicant.refs.create(params[:ref1])
       @applicant.refs.create(params[:ref2])
        #go to an appropriate view to say 'yeah, it worked'
    else
      render_action "index" #redisplay the index page
    end
end

This all works fine as far as getting the data into the database goes.
But it does not validate the second model, i.e. the references. I can
leave them blank and not get an validation message.

Anyone know of a way to get the reference model to validate the data.

Also, I'm almost positive that my methodology here is not the greatest.
If you have any suggestions on a slicker ruby-esque way to accomplish
this same thing, I'd love to hear it.

You'll want something like:

  def save_applicant
     @applicant = Applicant.new(params[:applicant])
     @applicant.refs.build(params[:ref1])
     @applicant.refs.build(params[:ref2])
     if @applicant.save
         #go to an appropriate view to say 'yeah, it worked'
     else
       render_action "index" #redisplay the index page
     end
  end

Mark Reginald James wrote:

You'll want something like:

  def save_applicant
     @applicant = Applicant.new(params[:applicant])
     @applicant.refs.build(params[:ref1])
     @applicant.refs.build(params[:ref2])
     if @applicant.save
         #go to an appropriate view to say 'yeah, it worked'
     else
       render_action "index" #redisplay the index page
     end
  end

Thanks for the reply Mark. I appreciate it. Using this approach leads
to a couple of difficulties, though (at least for me).

The models and corresponding validations for my models look like this:

class Applicant < ActiveRecord::Base

     has_many :refs

     validates_presence_of :name, :address, :message => "must be filled
in."

     validates_associated :refs

end

class Ref < ActiveRecord::Base

   belongs_to :applicant

   validates_presence_of :name, :email, :message => "fields must be
filled in."

end

Now when I submit the form with blank reference fields I get the
following:

There were problems with the following fields:

    * Refs is invalid
    * Refs is invalid

I've experimented with a number of different things and haven't been
able to get the validations to do what I want them to do, which is
either to display the validation message set in the Ref model or to
display a customized validation method some other way.

I got the page to display a custom validation message by changing the
Applicant model to include:

validates_associated :refs, :message => "validation message from
validates_associated."

The problem, though is that it also still includes the two 'Refs is
invalid' messages.

In other words the result looks like this:

There were problems with the following fields:

    * Refs message from Applicant v_a.
    * Refs is invalid
    * Refs is invalid

Any suggestions would be greatly appreciated! :slight_smile:

Grasshoppa wrote:

I got the page to display a custom validation message by changing the
Applicant model to include:

validates_associated :refs, :message => "validation message from
validates_associated."

The problem, though is that it also still includes the two 'Refs is
invalid' messages.

In other words the result looks like this:

There were problems with the following fields:

    * Refs message from Applicant v_a.
    * Refs is invalid

Any suggestions would be greatly appreciated! :slight_smile:

The bottom two messages are from the validation check of
new associates that Rails automatically does on save of
new parents, so if you remove your validates_associated
declaration you'll just get these.

In order to fully customize your error messages you'll
have to check the validity of the triple, before you
transactionally save the refs followed by the applicant.

Watch out for a bug in validates_associated that causes it to
stop validating an array of objects after the first invalid
object, preventing the generation of error messages for other
invalids.

The bottom two messages are from the validation check of
new associates that Rails automatically does on save of
new parents, so if you remove your validates_associated
declaration you'll just get these.

That's interesting, I just ran into this today for the first time. Try
as I might, I can't find it documented anywhere either in the Agile
Development with Ruby on Rails book
(which seems to suggest in the "when things get saved" section that the
opposite is true*) or in the API docs. Perhaps my google-fu is weak
today but would you happen to know where I could find more information
on this feature?

Many thanks,
  G.

* "Finally, there's a danger here. If the child row cannot be saved
(for example,
because it fails validation), Active Record will not
complain-you'll get no indi-
cation that the row was not added to the database."

Gavin wrote:

The bottom two messages are from the validation check of
new associates that Rails automatically does on save of
new parents, so if you remove your validates_associated
declaration you'll just get these.

That's interesting, I just ran into this today for the first time. Try
as I might, I can't find it documented anywhere either in the Agile
Development with Ruby on Rails book
(which seems to suggest in the "when things get saved" section that the
opposite is true*) or in the API docs. Perhaps my google-fu is weak
today but would you happen to know where I could find more information
on this feature?
...
* "Finally, there's a danger here. If the child row cannot be saved
(for example,
because it fails validation), Active Record will not
complain-you'll get no indi-
cation that the row was not added to the database."

The automatic pre-save validation of new associates is only for
has_many and has_and_belongs_to_many associations, and is not
to my knowledge properly documented. Here's the part of the core
source where it's done:

http://dev.rubyonrails.org/browser/tags/rel_1-1-6/activerecord/lib/active_record/associations.rb#L908

Your extract from the Agile book is referring to has_one associations,
which try to validate and save their associates /after/ the parent
has been saved.

Like has_many & habtm, belongs_to associations try to validate and save
new associates /before/ trying to save the parent, but unlike has_many
and habtm if the associate fails validation an attempt will still be made
to save the parent.

So there is some inconsistency between the different association types
with regards to automatic validation.

Probably the best thing to do would be change the core code so
that any automatic associate validation is only done if the
associate is clear of errors, preventing your problem of a second
validation adding unwanted error messages.