Forum: Ruby on Rails Handling validation failures on hasmany through records

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
F97e72e61be1ecb3ee931ababd87eb4e?d=identicon&s=25 Will Merrell (Guest)
on 2007-03-16 19:19
(Received via mailing list)
Hi all,

I am fairly new to Ruby and RoR but am making pretty good progress. But
this has me stumped and I wonder if anyone can point me in the right
direction. I haven't been able to find any examples on the web that
shine the light on this yet.

I have a very simple hasmany :through model as follows:

class Member < ActiveRecord::Base
  has_many :member_phones, :dependent => :destroy
  has_many :phones, :through => :member_phones
  validates_exclusion_of :first_name, :in => [ "Bozo"], :message => "You
don't belong here"
end

class MemberPhone < ActiveRecord::Base
  belongs_to :member
  belongs_to :phone
  validates_exclusion_of :name, :in => [ "Foobar" ], :message => "No
Foobar Allowed"
end

class Phone < ActiveRecord::Base
  has_many :member_phones
  has_many :people, :through => :member_phones
end

And this all works fine. The application can do all the CRUD functions,
and the validations really fail when they should.

My problem is that if I enter a phone named Foobar, it will fail to add
the record just as it should, but it does so silently. I cannot figure
out how to display an error message for it.

I can use <%= error_messages_for 'member' %> to display the error
message if I add a member with a first name of Bozo, but I can't figure
out how to get hold of the error message for the phone.

Does anyone know how to get and display the error messages on these
associated tables?

--Will
40db9e75b3f5899258e3bdc0c9210154?d=identicon&s=25 Conrad Taylor (Guest)
on 2007-03-16 19:32
(Received via mailing list)
Hi, I noticed that within your Phone model is referencing :people.
Thus, the Phone model should be

class Phone < ActiveRecord::Base
 has_many :member_phones
 has_many :members, :through => :member_phones
end

Good luck,

-Conrad
F97e72e61be1ecb3ee931ababd87eb4e?d=identicon&s=25 Will Merrell (Guest)
on 2007-03-16 21:03
(Received via mailing list)
Conrad Taylor wrote:
> Hi, I noticed that within your Phone model is referencing :people.
> Thus, the Phone model should be
>
> class Phone < ActiveRecord::Base
>  has_many :member_phones
>  has_many :members, :through => :member_phones
> end
>

Thanks Conrad, your right, that was a typo and I have corrected it.
However, it does not appear to be the cause of my trouble. The fixed
version still fails silently.

I am tying to take on each area of RoR in turn. Right now I'm trying to
figure out validations. ;^)

thanks all,
--Will
C64e63b70be7dfed8b0742540b8b27e5?d=identicon&s=25 Mark Reginald James (Guest)
on 2007-03-17 23:24
(Received via mailing list)
Will Merrell wrote:

>   belongs_to :member
> And this all works fine. The application can do all the CRUD functions,
> Does anyone know how to get and display the error messages on these
> associated tables?

Something like this?

<% member_phones = @member.member_phones.find(:all, :include => :phone)
    for @member_phone in member_phones
      @phone = @member_phone.phone
%>
<%= error_messages_for :member_phone %>
<%= text_field :phone, :number %>
<% end %>

--
We develop, watch us RoR, in numbers too big to ignore.
F97e72e61be1ecb3ee931ababd87eb4e?d=identicon&s=25 Will Merrell (Guest)
on 2007-03-18 16:04
(Received via mailing list)
Mark Reginald James wrote:
>> end
>>   has_many :members, :through => :member_phones
>> message if I add a member with a first name of Bozo, but I can't figure
>       @phone = @member_phone.phone
> %>
> <%= error_messages_for :member_phone %>
> <%= text_field :phone, :number %>
> <% end %>
>

Hi Mark, Thanks for the reply,

Perhaps I am being dumb, but I don't really understand how your
suggestion helps me. When I try to use the code you supplied, it still
fails silently. Could you explain what you expect this to do, (with a
little background) so I can tailor it to my situation?

A word or two of background on my part may help. The way I have found to
make the updates to the associated tables is with a supporting function
(called update_phones) in the member_phones model which is called from
the update action in the members controller. I don't know if this is the
proper way to do this, but it was the first thing I found that worked. I
suppose my problem is that this technique hides the validation failures.
Is there a more proper way to add the post parameters to the model for
updating?

The complete member_phones.rb file is as follows:

class MemberPhone < ActiveRecord::Base
  belongs_to :member
  belongs_to :phone

  validates_presence_of :name
  validates_presence_of :member_id
  validates_presence_of :phone_id
  validates_exclusion_of :name, :in => [ "Foobar" ], :message => "No
Foobar Allowed"

  before_destroy { |record|
    if( MemberPhone.find(:all, :conditions => {:phone_id =>
record.phone_id}).length < 2 )
      Phone.find(record.phone_id).destroy
    end
  }

  def update_phone(phonename, phonenumber)
    self.name = phonename
    self.phone.number = phonenumber
    saved = self.save
    saved = self.phone.save
    return saved
  end
end



Thanks,
-- Will
hasmany :questions, :throught => :acts_as_nuby!
C64e63b70be7dfed8b0742540b8b27e5?d=identicon&s=25 Mark Reginald James (Guest)
on 2007-03-19 08:15
(Received via mailing list)
Will Merrell wrote:

> Perhaps I am being dumb, but I don't really understand how your
> suggestion helps me. When I try to use the code you supplied, it still
> fails silently. Could you explain what you expect this to do, (with a
> little background) so I can tailor it to my situation?

Will, I thought your problem was how to display the errors on the
through
model, but I think your problem is also not preventing the update of
both
the phone and member_phone if there was a validation failure on one of
them.

You want to make sure that no record is updated if either the Phone
or MemberPhone has errors.  To do this you can add a
validates_associated
directive:

class MemberPhone < ActiveRecord::Base
   validates_associated :phone

   def update_phone(phonename, phonenumber)
     self.name = phonename
     self.phone.number = phonenumber
     phone.save if saved = save
     return saved
   end
end

But sometimes this is a problem because you get a "phone is invalid"
error message in your member_phone error list.  If so, you can instead
use:

class MemberPhone < ActiveRecord::Base

   def update_phone(phonename, phonenumber)
     self.name = phonename
     self.phone.number = phonenumber
     valid_phone = phone.valid?
     if valid_combo = valid? && valid_phone
       save(false)
       phone.save(false)
     end
     return valid_combo
   end
end

(For simplicity, I'm only dealing with the situation in which an
existing
number is updated, rather than a new number added.)

--
We develop, watch us RoR, in numbers too big to ignore.
F97e72e61be1ecb3ee931ababd87eb4e?d=identicon&s=25 Will Merrell (Guest)
on 2007-03-19 12:12
(Received via mailing list)
Mark Reginald James wrote:
> Will, I thought your problem was how to display the errors on the through
> model, but I think your problem is also not preventing the update of both
> the phone and member_phone if there was a validation failure on one of them.
>

Mark, Thanks for the reply and your time on this.

I agree that the code I originally posted probably had the problem you
suggest. I was (and is) work in progress. However, that is not the
problem I was originally asking about.

My question was, and is, how to get and display the errors when they do
arise. The model, (when adjusted to prevent saving one when the other
fails,) does work. If I try to enter some data that causes a validation
failure, the record is indeed not saved. If a pepper the update_phone
code with logger.info calls, I can see that Phone.errors does contain
the error messages I would expect.

My problem is I cannot find any code that I can put in the view that
lets me display the errors.

Examples:

<%= error_messages_for 'member' %>   <---- This works, but only for
failures in members

<%= error_messages_for @member %>   <---- Fails to compile
<%= error_messages_for 'member'.member_phones[1] %>   <---- Fails to
compile
<%= error_messages_for 'member.member_phones' %>   <---- Fails to
compile
<%= error_messages_for 'member.member_phones[1]' %>   <---- Fails to
compile
<%= error_messages_for 'member.member_phones.find(1)' %>   <---- Fails
to compile

<%  @mphone = @member.member_phones.find(1) %>
<%= error_messages_for 'mphone' %>               <----- Compiles, but
doesn't do anything

So I can raise the errors, I can control what goes into the database,
what I can't do is tell the user why his change was not saved!

Any ideas?????

-- Will
C64e63b70be7dfed8b0742540b8b27e5?d=identicon&s=25 Mark Reginald James (Guest)
on 2007-03-19 13:46
(Received via mailing list)
Will Merrell wrote:

> <%  @mphone = @member.member_phones.find(1) %>
> <%= error_messages_for 'mphone' %>               <----- Compiles, but
> doesn't do anything

error_messages_for works on controller instance variables, so
the above is on the right track.  But when you do a find you're
reloading from the db, not using the objects to which your response
handling has added errors.  So I think I mislead you when in my
original reply I used a find in the view as a shorthand to generate
the required data structure.

So after you've manipulated and validated the objects in the
member_phone collection in the controller action, you want
something like:

<% @mphone = @member.member_phones.first %>
<%= error_messages_for :mphone %>

or to do them all:

<% for @mphone in @member.member_phones
      @phone = @mphone.phone
%>
<%= error_messages_for :mphone %>
<%= error_messages_for :phone %>
<% end %>

--
We develop, watch us RoR, in numbers too big to ignore.
This topic is locked and can not be replied to.