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.