Imagine we had a basic survey application (Rails 4.2 with Devise)
similar
to the following:
class User < ActiveRecord::Base
Assume devise user here
has_many :user_surveys
has_many :surveys, through: :user_surveys
has_many :responses
end
class UserSurvey < ActiveRecord::Base
belongs_to :user
belongs_to :survey
end
class Survey < ActiveRecord::Base
has_many :questions
has_many :user_surveys
has_many :users, through: :user_surveys
accepts_nested_attributes_for :questions
name: string
end
class Question < ActiveRecord::Base
belongs_to :survey
has_many :responses
accepts_nested_attributes_for :responses
response_text: string
end
Class Response < ActiveRecord::Base
belongs_to :question
belongs_to :user
response_text: string
end
The idea being, an admin can create a survey with questions, and then
assign that survey to users through the join model UserSurvey.
Now imagine a basic form that we give the user for their survey that
looks
like this:
<%= @survey.name %> Answers
<%= form_for(@survey) do |f| %>
<%= current_user.name %>
<% @questions.each do |question| -%> <% end -%>Questions | Response |
<%= question.question_text %> | <%= f.fields_for :questions, question do |q| -%> <%= q.fields_for :responses, question.responses.find_or_initialize_by(user: current_user.id) do |r| -%> <%= r.text_area :response_text %> <%= r.hidden_field :user_id, current_user.id %> <% end -%> <% end -%> |
My main question revolves around the following line (under fields_for
responses):
<%= r.hidden_field :user_id, current_user.id %>
I see it suggested other places (eg: here
https://stackoverflow.com/questions/4038833/creating-surveys-with-accepts-nested-form-for/4038876#4038876
on
stackoverflow, or this tutorial here
http://www.createdbypete.com/articles/working-with-nested-forms-and-a-many-to-many-association-in-rails-4/
)
to put a hidden user_id field, but this feels incredibly wrong to me –
if
a user is malicious, they could edit the form and modify the hidden
user_id
field to modify another participants answer.
Now, the one solution I did think was that on the controller side I
could
mess with the params, IE assume the submitted parameters look as follows
“survey”=>{“questions_attributes”=>{“0”=>{“responses_attributes”=>{“0”=>{“response_text”=>“one”}},
“id”=>“7”},
“1”=>{“responses_attributes”=>{“0”=>{“response_text”=>“two”}},
“id”=>“8”}}}
I could do something like:
def update
@survey = current_user.surveys.find(params[:id])
@questions = @survey.questions
params[:survey][:question_attributes].each do |key, attribs|
question = @questions.find(attribs[:id])
response = question.responses.where(:user_id =>
current_user.id).first_or_initialize
response.response_text
= attribs[“responses_attributes”][“0”][“response_text”]
response.save!
end
end
This way, I not only check to make sure the survey is attached to the
user,
but also make sure the questions they are submitting are attached to
that
specific survey, and the responses are not only tied to the correct
question, but also back to the correct user. This feels messy though,
and I
feel I’m majorly overthinking this.
Can anyone give me a sanity check as to what I’m doing wrong?