Monday, November 14, 2011

Create multiple models in a single form without nested attributes

So I am surprised that Rails does not give you an easy way to create multiple models in a single form without the use of nested attributes. Ryan Bates does an excellent job of explaining Nested Models, and the creation of multiple models in a single for in his Railscasts part 1 & 2. Incidentally, if you are looking for a Rails 3 version of this app check THIS out.

First, lets look at the rather trivial case of simply entering multiple models in a single form. Lets says we have a small survey application, and we want to enter a number of questions into the survey. Using standard scaffolding Rails will only allow us to create one question at a time. In THIS app I have modified the controller and view which allows the user to enter 3 questions at a time. This code of a rehash of code presented in THIS forum post.

…but lets consider another use case:

Since this is a Survey application we will want to enter results for each question, so lets have Question and Result models. Question hasmany :results, and Results belongsto :question. This sets up the classic scenario where we would want to use acceptsnestedattributesfor. However, acceptsnestedattributesfor is typically used for ‘creation’ of multiple models at the same time, on the same form. Lets consider a use case where we have pre-existing Question data, and we want to create/associate a Result with a Question AFTER the creation of the Questions. So, we will enter our questions using the standard rails forms, but the twist comes when we go to enter our results. Let say for however many Questions we have in the app, lets display them on the screen, and allow the user to enter a score for each question. For our purposes we will say our ‘score’ will simply be a number between 0 and 4, and will will display a radio button for each number. This is tough nut to crack, and let know one tell you otherwise! This will require use to gather all the questions and put them in an array. Then for each question we will ‘build’ a result. This will ensure the association between the result and question is put in place (ie, we save the questionid in the result). We will do this in the results controller and view. So lets look at that code…
results controller

def new @results = Array.new @questions = Question.all @questions.each do |q| @results.push q.results.build end end

Next, in the view we need to display each question name, and its associated radio buttons..
app/views/result/form.html.erb

<%= formtag :action => ‘create’ %> <% @results.eachwithindex do |result, index| %>

<%= fieldsfor “results#{index}”, result do |f| %> Queston:<%= @questionsindex.name %> <%= f.hiddenfield :questionid%> <%= f.radiobutton :score, ‘0’ %>0 <%= f.radiobutton :score, ‘1’ %>1 <%= f.radiobutton :score, ‘2’ %>2 <%= f.radiobutton :score, ‘3’ %>3 <%= f.radiobutton :score, ‘4’ %>4 <% end %> <% end %>

<%= submittag ‘Submit’ %>

I’d love to say I know everything that is going on in this code, but I don’t, so some chime in with a nice step-by-step explanation of what’s going on and I will post it. Same goes for the create action in the controller code…

def create @results = params:results.values.collect { |result| Result.new(result) } if @results.all?(&:valid?) @results.each(&:save!) redirectto :action => ‘index’ else render :action => ‘new’ end end

You can check out the app HERE https://github.com/marklocklear/multiple_models_one_form

Someone respond with an explanation of views and create actions. Thanx!

No comments:

Post a Comment