Howdy,
I''m working on my first RoR project, and I want to build a form. The
tricky thing is, this form needs to insert / update against two models,
each with their own validation rules. Unfortunately, I''m having an
incredible amount of difficulty with the error handling, specifically
the fields wrapped in <div
class=''fieldWithErrors''></div>
nevans@bell:app/models$ ls
user_preference.rb user.rb
The User class has_one :user_preference, and UserPreference belongs_to
:user. Both classes have a half dozen validation rules or so.
In my RegisterController, I have two actions: one to display the form,
and one to save the form.
---
# Don''t laugh too hard at how ugly my code is, I''ve only read
# Agile Web Development and Why''s Poignant Guide!
class RegisterController < ApplicationController
def index
@user = User.new
@pref = UserPreference.new
@user.user_preference = @pref
end
def save
@user = User.create(params[:user])
@pref = UserPreference.create(params[:user_preference])
@user.user_preference = @pref
# Default display name to something nice
@user.user_preference.display_name = @user.username
User.transaction do
if @user.save and @user.user_preference.save
# do something useful
else
# breakpoint(''User not saved.'')
render(:action => ''index'')
end
end # end transaction
end # end save
end
---
I also have a template that renders my partial template for the new user
form. Yes, it does use the form helper functions to generate fields:
<%= text_field ''user'', ''username'',
''maxlength'' => 20, ''size'' => 20 %>
<%= text_field ''user_preference'',
''email'', ''size'' => 20,
''maxlength'' =>
255 %>
etc...
When the form is submitted, both sets of validation rules are run. The
standard error_messages_for, however, only supports rendering out the
errors from one model''s validation. I fixed this with a (really
hackish)
helper. It take in an array of instance variables and build the error
box for them.
But, the remaining issue I am unable to solve: <div
class=''fieldWithErrors''></div>. Rails only wraps the
tags that fail the
User class'' validation. I''ve been unable to grok how Rails
figured out
which fields it needs to put in the fieldWithErrors divs, even after
reading a whole lot of Rails source. :-(
What would an optimal solution be? Can I insert my own code anywhere to
take over this part of error handling? Or, failing that, is there a way
I can disable the wrapping of elements in fieldWithErrors for only this
form?
Thank you all very much!
Regards,
Nick Evans
I think the create method actually creates and saves the model to the DB : @user = User.create(params[:user]) I think you should use the new method instead : @user = User.new(params[:user]) Chris -- Posted via http://www.ruby-forum.com/.
You are correct, create does save the model to the database. My code has
been updated appropriately:
@user = User.new(params[:user])
@pref = UserPreference.new(params[:user_preference])
But, I no longer get both sets of errors displayed. I examined @user
with the breakpointer to confirm this. Perhaps it''s because
I''m using a
transaction?
After reviewing the material on transaction in Agile Web Development, I
corrected my save action:
---
def save
@user = User.new(params[:user])
@pref = UserPreference.new(params[:user_preference])
@user.user_preference = @pref
# Default display name to something nice
# (perhaps this needs to be moved to an initialize or something?)
@user.user_preference.display_name = @user.username
begin
User.transaction do
@user.save!
@user.user_preference.save!
end # end transaction
rescue
# Could not save both, catch the exception and show errors.
render(:action => ''index'')
end
# SUCCESS!
# redirect somewhere useful
end # end save
---
Unfortunately, the same behaviour I described above is exhibited, with
only the User object''s errors showing up anywhere. The user_preference
instance does not even have the errors in it this time:
irb(#<RegisterController:0xb76beab0>):006:0> @user.user_preference
=> #<UserPreference:0xb76a8a08
@attributes {
"gender"=>"MALE",
"display_name"=>"a",
"user_id"=>"0",
"age"=>nil,
"email"=>""
},
@new_record=true>
Thank you for your reply, Chris.
Regards,
Nick Evans
Chris wrote:> I think the create method actually creates and saves the model to the DB
> :
>
> @user = User.create(params[:user])
>
> I think you should use the new method instead :
>
> @user = User.new(params[:user])
>
> Chris
>
OK. Using the valid? method on a model will check if all validations passed and add the errors to the model without saving the model. Also, saving @user will save any models it contains (e.g. @user.user_preference will be auto saved). SO do this: @user = User.new(params[:user]) @pref = UserPreference.new(params[:user_preference]) @user.user_preference = @pref @user.user_preference.display_name = @user.username if @user.valid? and @pref.valid? @user.save else render :action=>..... ... end Its nice and simple too. Its nice to give something back after asking a million questions myself in this great forum!! Chris -- Posted via http://www.ruby-forum.com/.
It looks to me like one cannot do both valid? calls in an if-and
statement. When doing:
---
if @user.valid? and @pref.valid?
@user.save
else
render :action=>.....
...
end
---
I would only get the errors for @user. A tinkered a bit and came up with
this:
---
@user.valid?
@user.user_preference.valid?
# breakpoint(''Validated, stopping before save.'')
if @user.save
# SUCCESS!
else
render(:action => ''index'')
end
---
I get both sets of errors into my error handler, finally. But, this
still doesn''t solve the original issue of only User fields in the form
wrapped with fieldWithErrors.
Additionally, the UserPreference form elements do not get defaulted to
the value I entered on the first screen when its reporting errors. I
guess the cause is probably the same thing as the error div''s cause.
Thanks for your help, though! I feel like I''m making progress. :-)
Regards,
Nick Evans
Gd wrote:> OK. Using the valid? method on a model will check if all validations
> passed and add the errors to the model without saving the model. Also,
> saving @user will save any models it contains (e.g.
> @user.user_preference will be auto saved). SO do this:
>
> @user = User.new(params[:user])
> @pref = UserPreference.new(params[:user_preference])
> @user.user_preference = @pref
> @user.user_preference.display_name = @user.username
>
> if @user.valid? and @pref.valid?
> @user.save
> else
> render :action=>.....
> ...
> end
>
> Its nice and simple too.
>
> Its nice to give something back after asking a million questions myself
> in this great forum!!
>
> Chris
try this instead : @user.valid? @pref.valid? -- Posted via http://www.ruby-forum.com/.
Chris wrote:> try this instead : > > @user.valid? > @pref.valid?on second thoughts, i dont think that will do anything -- Posted via http://www.ruby-forum.com/.
Rails might use the labels around the text boxes to figure out which elements to wrap the error box around. COuld be wrong. -- Posted via http://www.ruby-forum.com/.
Nicholas Evans
2006-Apr-09 21:07 UTC
[Rails] Re: Re: Re: Validating two models from one form
I don''t think that is the case. I believe this:
---
alias_method :tag_without_error_wrapping, :tag
def tag(name, options)
if object.respond_to?("errors") &&
object.errors.respond_to?("on")
error_wrapping(tag_without_error_wrapping(name, options),
object.errors.on(@method_name))
else
tag_without_error_wrapping(name, options)
end
end
---
Is what puts the the divs around the elements. Besides, label tags are
supposed to go around the field labels.
I think my issue is that the tag method above is that, like the
error_messages_for, it only wraps the error hash from the top-leve
object [I don''t think I''m using the right terminology there,
please
forgive me].
Actually, now that I think about it, all I need to do is concat the
error lists together and Rails should handle the rest. Hopefully.
Lessee'' what I can cook up...
Regards,
Nick Evans
Chris wrote:> Rails might use the labels around the text boxes to figure out which
> elements to wrap the error box around. COuld be wrong.
>
Nicholas Evans
2006-Apr-09 22:51 UTC
[Rails] Re: Re: Re: Validating two models from one form
I''m just an idiot. The field names in the form doesn''t map to
a /model/,
but to an instance variable. Once I changed @pref to @user_preference, I
was home free.
I also had to add a hack to concatonate all of the error messages from
preference into @user.errors in order for error_messages_for to display
the errors for both models'' validation rules.
---
class RegisterController < ApplicationController
def index
@user = User.new
@user_preference = UserPreference.new
@user.user_preference = @user_preference
# For some reason, gender defaults to male before
# submission.
if params[:commit]
@user.user_preference.gender = ''''
end
end
def save
@user = User.new(params[:user])
@user_preference = UserPreference.new(params[:user_preference])
@user.user_preference = @user_preference
# Default display name to something nice
@user.user_preference.display_name = @user.username
# Get any error messages.
@user.valid?
@user.user_preference.valid?
if @user.save
# SUCCESS!
else
# Hack
@user.user_preference.errors.each do
|attribute, error|
@user.errors.add(attribute, error)
end
render(:action => ''index'')
end
end # end save
end
---
And that was all. Thanks guys!
Regards,
Nick Evans
Nicholas Evans wrote:> I don''t think that is the case. I believe this:
>
> ---
> alias_method :tag_without_error_wrapping, :tag
> def tag(name, options)
> if object.respond_to?("errors") &&
object.errors.respond_to?("on")
> error_wrapping(tag_without_error_wrapping(name, options),
> object.errors.on(@method_name))
> else
> tag_without_error_wrapping(name, options)
> end
> end
> ---
>
> Is what puts the the divs around the elements. Besides, label tags are
> supposed to go around the field labels.
>
> I think my issue is that the tag method above is that, like the
> error_messages_for, it only wraps the error hash from the top-leve
> object [I don''t think I''m using the right terminology
there, please
> forgive me].
>
> Actually, now that I think about it, all I need to do is concat the
> error lists together and Rails should handle the rest. Hopefully.
>
> Lessee'' what I can cook up...
>
> Regards,
> Nick Evans
>
> Chris wrote:
>
>> Rails might use the labels around the text boxes to figure out which
>> elements to wrap the error box around. COuld be wrong.
>>
> _______________________________________________
> Rails mailing list
> Rails@lists.rubyonrails.org
> http://lists.rubyonrails.org/mailman/listinfo/rails
>
Jeff Coleman
2006-Apr-10 09:08 UTC
[Rails] Re: Re: Re: Re: Validating two models from one form
Nicholas Evans wrote:> I''m just an idiot. The field names in the form doesn''t map to a /model/, > but to an instance variable. Once I changed @pref to @user_preference, I > was home free. > > I also had to add a hack to concatonate all of the error messages from > preference into @user.errors in order for error_messages_for to display > the errors for both models'' validation rules.Would it not work to simply call "error_messages_for" in your view for each of the models you''re dealing with? Jeff Coleman -- Posted via http://www.ruby-forum.com/.
For displaying validation errors from more than object on a form take a look at this plugin: http://www.railtie.net/articles/2006/01/26/enhancing_rails_errors I used it as a basis to figure out how to improve error_messages_for so that it can take multiple models. Calling error_messages_for twice gives you two blocks on the page which looks odd. This way, all your errors will be contained into a single block. The syntax is a bit cumbersome but it''s flexible. -- Posted via http://www.ruby-forum.com/.
a plugin that fixes this problem and so easy to us
2006-Jun-25 19:35 UTC
[Rails] Re: Validating two models from one form
http://railtie.net/articles/2006/01/26/enhancing_rails_errors#commentform install it in vendor/plugins and take a look at the readme - and your good to go! make sure you restart your server once you install the plugin. -- Posted via http://www.ruby-forum.com/.