Why do you submit the current user id in the form? I assume the user is logged in by the statement current_user.id
in the form. If you're placing the current user id into the form, you allow the user to change the value (assuming you use the submitted user id in your handler). I know the field is hidden, but everything that an client submits can be manipulated. The only thing to do is open up the developer tools and alter the HTML source. Instead of submitting the id through the form I recommend simply using current_user
in the handler and don't submit the user id at all.
You mention you're looking into Action Cable at the moment. However Action Cable is based on the web-socket technology. This could be used, but the main reason they where invented is to keep the connection between the server and client open so the server can send data to the client without the client making a request first. This is mostly done when something changed on the server that has impact on the current view of the client. The term for this would be push notifications or messages.
Since the view doesn't have to change when something changed on the server there is no need to make things more complicated. Instead you want to update the responses when user selects an answer (user initiated). The solution is to update the user response on change (using JavaScript). Rails has a build in system that does this documented in the Ruby on Rails Guides - Working with JavaScript in Rails: 3.2 Customize remote elements.
Let me provide you with an (untested) example:
First we ensure that the correct routes are present so we can update the response in a reasonable fashion.
# config/routes.rb
# ...
resources :questions, only: [] do
member do
post 'answer', to: 'responses#answer'
end
end
# ...
This should create the route /questions/:id/answer
that will be handled in ResponsesController#answer. This route should create a helper named answer_question
that accepts an question or question id as its first argument.
The controller might look something like this:
# app/controllers/responses_controller.rb
# ...
def answer
question = Question.find(params[:id])
answer = question.answers.find(params[:answer_id])
response = question.responses.find_or_initialize_by(user: current_user)
if response.update(answer: answer)
head :ok
else
render 'answer_error'
# renders /app/views/responses/answer_error.js.erb
# Here you can write some JS to update the DOM and sets an error
# message somewhere.
end
end
# ...
And now for the final part of the puzzle we need the view.
<% # I don't know which view this is, but it replaces the one that OP provided. %>
<h1><%= @question.prompt %></h1>
<% @question.answers.each do |question| %>
<%= form_for @response, url: answer_question_path(@question), method: :post, remote: true do |f| %>
<%= f.hidden_field :answer_id, value: answer.id %>
<%= f.submit a.answer.titleize, class: 'btn btn-block btn-lg btn-primary' %>
<% end %>
<% end %>
By having an nested route you eliminate the need to submit the question id. And the user id can be left out for reasons mentioned before.
From the tone in your post I assume you also want to drop those forms. This is possible by using the "Customize remote elements" that I linked above. This would leave you with a view that might look something like this.
<h1><%= @question.prompt %></h1>
<% @question.answers.each do |answer| %>
<%= radio_button_tag "question_#{question.id}_answer", answer.id, false, data: {
remote: true,
method: :post,
url: answer_question_path(@question),
params: { answer_id: answer.id }
} %>
<% end %>
The above would simply generate radio buttons (using radio_button_tag
) that are linked to each other (per question). On change a request will be send to the specified URL. This eliminates the need for a form altogether. Keep in mind that no labels are currently present for the radio buttons, you'll have to add them yourself.
Of course you could also use other input HTML input elements to achieve the same result. If you want to stick to the button approach I recommend taking a look at button_tag
in combination with the technique shown above. Or button_to
which is an helper that generates the wrapping form for you.
As I said, this is an untested example. Might any errors come up let me know in the comments.