|
Editing and Creating Views of Nested (Relational) Data
By: Bruce Bahlmann - Contributing Author (your
feedback
is important to us!)
In rails development, within your view, often you will want to either create
a new record or edit that record. Well, generally this is easy. However when
you are using nested (relational) data it gets complicated. The following is
a solution when creating and editing an array of data (responses to
questions) when the actual responses varies with the questions (which also
varies by survey). This is a case of a completely dynamic data structure and
we use MongoDB as our database.

The above graphic represents a response entry form that is generated
dynamically upon a user wanting to complete a survey. Since the questions
within a given survey could be any number (there is no limit), there is
accordingly no limit to the responses (other than which answer the questions
which the responses correspond). As the above entry form is blank, this
represents a NEW entry so rails would flow through the new section of the
responses_controller.
A problem exists when you want to both provide a create (new) interface as
well as an edit interface. That is because when you provide an edit
interface which requires the VALUE attribute to be correctly populated (to
allow you to view the value of the field as stored in the database), as well
as a create new form, and these
interfaces access the same form, you will receive unknown method errors when
trying to access the create (new) form. In our case, new instances of ANSWER
in our response model, haven't been populated yet (because we are in the
process of creating it).
[apps/views/responses/_form.html.erb]
...
<span class="step" id="first">
<table border="0">
<% @survey.sorder.split('&').each do |ti| %>
<% key = ti.sub('s[]=','') %>
<% value = @survey.question[key] %>
<% if value["questionText"] == "PageBreak" %>
</table>
</span>
<span id="" class="step">
<table border="0">
<% else %>
<% if value["questionType"] == "0" %>
<tr><td><%= value["questionText"] %>:</td>
<td> <%= text_field("response[answer]", key, :class=> "q"+key, :value=>@response.answer[key], :disabled=>@dfv) %></td></tr>
<% elsif value["questionType"] == "1" %>
<tr><td><%= value["questionText"] %>:</td><td>
<%= select("response[answer]", key, value["choices"].split("\r\n") {|k,v| [v,k.to_i]},
{ :selected => @response.answer[key], :include_blank=>0}, :class=>"q"+key, :disabled=>@dfv) %>
</td></tr>
<% elsif value["questionType"] == "2" %>
<tr><td><%= value["questionText"] %>:</td><td>
<% lcv = 1 %>
<% value["choices"].split(/\r\n/).each do |v| %>
<input <%= @df %> id="answer_<%= key %>_<%= lcv %>" class="q<%= key %>" type="checkbox"
name="response[answer][<%= key %>][]" value="<%= v %>"
<% if !@response.answer.empty? %>
<% if @response.answer[key].include?(v) %>
checked
<% end %>
<% end %>
> <%= v %>
<% lcv = lcv + 1 %>
<% end %>
</td></tr>
<% elsif value["questionType"] == "3" %>
<tr><td style="vertical-align: top"><%= value["questionText"] %>:</td>
<td> <%= text_area ("response[answer]", key, :class=> "q"+key, :rows=>6,
:cols=>60, :value=>@response.answer[key], :disabled=>@dfv) %></td></tr>
<% end %>
<% end %>
<% end %>
</table>
A simple, but less elegant way of dealing with this issue is to just NOT
use the edit form with the survey recreated like the user once saw with the
values they entered. Instead, use the show, which allows you to just dump
the values of the response. However, since in our case the response doesn't
really understand the questions (rather it is simply just an array of
responses to the questions), we need the survey to make sense of the
response. So, here is the error we were seeing:
undefined method `answer' for #<Response:0x1185fb6f8>
Also, here was what we had entered into the responses_controller - which
is pretty standard. Again, we needed to pass both the survey as well as the
response that we were creating for the survey.
[apps/controllers/responses_controller.rb]
## GET /surveys/new
## GET /surveys/new.xml
def new
@survey = Survey.find(params[:survey_id])
@response = Response.new
Here was our model for both response and answer:
[apps/models/response.rb]
class Response
include MongoMapper::Document
key :responder, Integer
key :cdate, Date
belongs_to :survey
many :answers
end
[apps/models/answer.rb]
class Answer
include MongoMapper::EmbeddedDocument
belongs_to :response
end
The solution ended up being very simple, we just defined the ANSWER
attribute when creating a NEW response and since we were creating a new
response, we set the attribute to an empty hash.
apps/controllers/surveys_controller.rb
## GET /surveys/new
## GET /surveys/new.xml
def new
@survey = Survey.find(params[:survey_id])
@response = Response.new(:answer => Hash.new)
As a result, the method error above went away, and now the form can be
accessed either via create or edit (see below) elegantly and simply.

This seems too simple, but perhaps its just rails common sense, because
finding a good explanation for this proved difficult - which is why I'm
wring this post.
.
Can Birds-Eye.Net help you or your Company?
Receive your Birds-Eye.Net articles and white
papers hot off
the presses by adding our RSS feed to your reader.
|
|