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/.