Combining submit action with navigation in Ruby on Rails - ruby-on-rails

I want to build a multiple choice quiz in Ruby on Rails, but although I've got it sort of working I still want to improve my navigation.
As it currently stands I have two buttons: One for submitting the quiz_guess (which is an attribute of Answer) and another for selecting the next :question. I would like to combine these two buttons into a single one so I don't have to first update the attributes and then select the next question.
How do you go about doing that?
My questions controller:
def quiz_guess
#survey = Survey.first
Answer.update(params[:answers].keys, params[:answers].values)
if #survey.save
flash[:notice] = "Guess saved successfully."
redirect_to(:action => 'show_quiz', :id => #survey.next, :survey_id => #survey.id)
else
render 'show'
end
end
def show_quiz
#answer = Answer.find(params[:id]) #answer
#answers = Answer.where(:survey_id => #survey.id)
#question = Question.find(params[:id])
#questions = Question.where(:survey_id => #survey.id)
#surveys = Survey.all
#survey = Survey.find(params[:id])
end
My show_quiz view:
<%= form_tag quiz_guess_questions_path, :method => :put do %>
<% for question in #survey.questions do %>
<li><%= question.content %></li>
<% for answer in question.answers do %>
<li>
<%= fields_for "answers[]", answer do |f| %>
<%= answer.content %>
<%= f.check_box :guess, :checked => false %>
<% end %>
</li>
<% end %>
<% end %>
<%= submit_tag "Guess" %>
<% end %>
<%= link_to t('.back', :default => t("helpers.links.back")), surveys_path, :survey_id => #survey.id, :class => 'btn btn-default' %>
<%= link_to t('Previous question', :default => t("helpers.links.previous")), {:action => 'show', :id => #survey.previous, :survey_id => #survey.id}, :class => 'btn btn-default' %>
<%= link_to t('Next question', :default => t("helpers.links.next")), {:action => 'show', :id => #survey.next, :survey_id => #survey.id}, :class => 'btn btn-default' %>
My model:
def next
Survey.limit(1).order("id ASC").where("id > ?", id).first
end
def previous
Survey.limit(1).order("id DESC").where("id < ?", id).last
end

I'd like to improve your next(and previous) functions. First, you don't need to have limit(1) while you have .first or .last at the end. .first and .last each guarantees only 1 record to be returned.
Problem is when you call #survey.next on your last record, it will point to nothing. Here you have two options. First, you can use a check, and if next returns nil, disable the next button.
Second, make the order cyclic. Clicking next on the last survey takes to the first survey, and vice verse. Here's how:
def next
Survey.order("id ASC").where("id > ?", id).first || Survey.first
end
def previous
Survey.order("id DESC").where("id < ?", id).last || Survey.last
end

Related

Rails 4 : Table Boolean Column Update Using "link_to "with a specific value "TRUE" always

In my customer controller the update method code is like bellow:
def update
#customer= Customer.find(params[:id])
if #customer.update_attributes(customer_params)
redirect_to customers_path
else
render 'edit'
end
end
In my view in customers index page I am planning to add a "link_to" link, if it is clicked, then that particular customers field "doc_delete" should be updated with value "TRUE".
<td><%= link_to "[Update", *************what is here ?******** method: :put %></td>
You can pass hidden params through button_to:
<%= button_to "Update", user, method: :put, params: { doc_delete: true } %>
This will create a micro-form, much like what Marwen alluded to. Whilst quite inefficient, it will be the best way to send data to your update action.
--
Another, more efficient, way would be to define a custom route/action:
#config/routes.rb
resources :customers do
patch :doc_delete, on: :member #-> url.com/users/:id/doc_delete
end
#app/controllers/customers_controller.rb
class CustomersController < ApplicationController
def doc_delete
#customer = Customer.find params[:id]
redirect_to customers_path if #customer.update doc_delete: true
end
end
#app/views/customers/index.html.erb
<% #customers.each do |customer| %>
<%= link_to "Update", customer_doc_delete_path(customer) %>
<% end %>
You will need a form to do that for you
<% unless customer.doc_delete? %>
<%= form_for customer do |f| %>
<%= f.hidden_field_tag :doc_delete, true %>
<%= f.submit %>
<% end %>
<% end %>
Where to insert this form?
Well if you are rendering you costumers using:
<%=render #costumers %>
then you will add the form in the /customers/_customer.html.erb
If you are looping them manually:
<% #customers.each do |customer| %>
<%=customer.full_name %>
## Here you can add the form
<% end %>
An another way, you can use Ajax.
#app/views/customers/index.html.erb
<% #customers.each do |customer| %>
<% if !customer.doc_delete == true %>
<%= link_to "Update", customer_doc_delete_path(customer), remote: true %>
<% else %>
<%= Updated %>
<% end %>
<% end %>
#config/routes.rb
resources :customers do
patch :doc_delete, on: :member #-> url.com/customers/:id/doc_delete
end
#app/controllers/customers_controller.rb
class CustomersController < ApplicationController
def doc_delete
#customer = Customer.find params[:id]
if #customer.update doc_delete: true
respond_to do | format |
format.js {render :nothing => true}
end
end
end
end
In my index.html
<td>
<%= hidden_field_tag 'delete_present', :value => "present" %>
<%=link_to "[update]", customer_path(customer, :doc_delete => true), :method => :put, :confirm => "Are you sure?" %>
</td>
In my customer controller
def update
if params[:doc_delete].present?
#customer= Customer.find(params[:id])
#customer.doc_delete=true
#customer.save
redirect_to customers_path
else
#customer= Customer.find(params[:id])
if #customer.update_attributes(customer_params)
redirect_to customers_path
else
render 'edit'
end
end
end

Rails accessing custom method instance variable

What I am trying to do is add a simple search to a 'show' view in my rails app. I've watched the rails cast about simple search and it's given me some good direction but I can't seem to get it to work with my situation. I just have a list of FAQs and want to have a search filter for the 'question(s)'.
Here's my views/guests/show.html.erb
<%= form_tag faq_search_guests_path, method: :get, remote: true, :id => "faq_search_form" do %>
<p>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search", :name => nil %>
</p>
<% end %>
<div id="faq-search-test"></div>
<% #faqs.each do |f| %>
<h4>Question</h4>
<%= f.question %>
<h4>Answer</h4>
<%= f.answer %>
<% end %>
Here's the controller/guests_controller.rb method that originally sets #faqs
def show
#guest = Guest.find(params[:id])
#hotel = Hotel.find(params[:hotel_id])
#faqs = Faq.where(hotel_id: #hotel.id)
#template = Template.where( hotel: #hotel.id )
#visit = Visit.where("hotel_id = ? AND guest_id = ?", #hotel.id, #guest.id).last
#visit = Visit.find(params[:visit_id]) if params[:visit_id] && (#visit.hotel_id == #hotel.id)
unless current_user.hotels.include?(#hotel)
render :file => "public/401.html", :status => :unauthorized
end
end
Here's my controllers/guests_controller.rb custom method my form is calling
def faq_search
#faq_result = Faq.search(params[:search])
render :nothing => true
end
Here's my routes.rb for that
resources :guests do
get 'faq_search', :on => :collection
end
resources :faqs
Here's my models/faq.rb
def self.search(search)
if search
where('question LIKE ?', '%#{search}%')
else
scoped
end
end
So from here, I am having trouble displaying #faq_result in show.html.erb. I guess I have no idea how to access an instance variable from a custom controller method from a view.
Any help would be much appreiciated :)!

Incorrect param submitting

I have a form for casting your vote for your favourite image.
<%= form_for(#imagevote) do |f| %>
<% #miniature.collections(:photo).each do |collection| %>
<% if collection.photo.exists? %>
<td><div class="photo1">
<%= link_to image_tag(collection.photo.url(:thumb), :retina => true), collection.photo.url(:original), :retina => true, :class => "image-popup-no-margins" %>
<%= f.radio_button(:collection_id, collection.id) %>
<%= f.hidden_field :voter_id, :value => current_user.id %>
<%= f.hidden_field :voted_id, :value => collection.user_id %>
<%= f.hidden_field :miniature_id, :value => #miniature.id %>
<p>Painted by <%= link_to collection.user.name, collection.user %></p>
</div></td>
<% end %>
<% end %>
<%= f.submit "Vote" %>
<% end %>
Everything submits correctly except for the hidden_field :voted_id which for some reason duplicates the current_user.id.
UPDATE
I've tried logging in as another user and it seems that :voted_id is not duplicating current_user.id but rather that it is always "7" which was the :user_id I was using to test it before. Now logged in as user number 4 it is still entering the :voted_id as 7. I'm lost.
The link to the imagevotes view is as follows:
<%= link_to "See more and change your vote.", edit_imagevote_path(:miniature_id => #miniature, :voter_id => current_user.id) %>
Here is my image votes controller
class ImagevotesController < ApplicationController
respond_to :html, :js
def new
#imagevote = Imagevote.new
#miniature = Miniature.find(params[:miniature_id])
end
def edit
#imagevote = Imagevote.find_by_miniature_id_and_voter_id(params[:miniature_id],params[:voter_id])
#miniature = Miniature.find(params[:miniature_id])
end
def create
#imagevote = Imagevote.new(imagevote_params)
if #imagevote.save
flash[:success] = "Vote registered"
redirect_to :back
else
flash[:success] = "Vote not registered"
redirect_to :back
end
end
def update
#imagevote = Imagevote.find(params[:id])
if #imagevote.update_attributes(imagevote_params)
flash[:success] = "Vote changed."
redirect_to :back
else
redirect_to :back
end
end
private
def imagevote_params
params.require(:imagevote).permit(:collection_id, :voter_id, :voted_id, :miniature_id)
end
end
You only have one #imagevote object, but you are outputting the hidden fields inside your collection loop so you will have multiple fields in the form referencing the same attribute on the model: if you check the html that is generated, you should see multiple hidden fields with the same name attribute.
The way that browsers handle multiple inputs with the same name means that the param that comes through for :voted_id will always be the :user_id from the last collection.
It's difficult to say because you didn't provide your model and your loop code stripped.
I would guess that you loop over collection that belongs to the current_user. And in this case you will have current_user.id always be the same as collection.user_id. May be you wanted to see collection.photo_id?

No route matches - missing required keys: [id] ?

I'm doing a Rails tutorial, and trying to figure out why this is happening.
I'm making a to-do list, and everytime I try and insert a record into my Todo model, I get the following:
Here is the new.html.erb view that this is from:
<h1>Add new item to your todo listM</h1>
<%= form_for #todo, :url=>todo_path(#todo) do |f| %>
<%= f.label :name %> <%= f.text_field :name%>
<%= f.hidden_field :done, :value => false %>
<%= f.submit "Add to todo list" %>
<% end %>
Here is index.html.erb from where the user is linked to new.html.erb
<h1>TASKS</h1>
<h3> TO DO </h3>
<ul>
<% #todos.each do |t| %>
<li>
<strong><%= t.name %></strong>
<small><%= link_to "Mark as Done", todo_path(t), :method => :put %></small>
</li>
<% end %>
</ul>
<h3> DONE </h3>
<ul>
<% #todones.each do |t| %>
<li>
<strong><%= t.name %></strong>
<small><%= link_to "Remove", t, :confirm => "You sure?", :method => :delete %></small>
</li>
<% end %>
</ul>
<%= link_to "Add new task", new_todo_path %>
Here is the TodoController I have managing these actions:
class TodoController < ApplicationController
def index
#todos = Todo.where(done:false)
#todones = Todo.where(done:true)
end
def new
#todo = Todo.new
end
def todo_params
params.require(:todo).permit(:name, :done)
end
def create
#todo = Todo.new(todo_params)
if #todo.save
redirect_to todo_index_path, :notice => "Your todo item was created!"
else
render "new"
end
end
def update
#todo = Todo.find(params[:id])
if #todo.update_attribute(:done, true)
redirect_to todo_index_path, :notice => "Your todo item was marked done!"
else
redirect_to todo_index_path, :notice => "Couldn't update your task"
end
end
def destroy
#todo = Todo.find(params[:id])
#todo.destroy
redirect_to todo_index_path, :notice => "Your todo item was deleted"
end
end
And finally the routes.rb
Oneday::Application.routes.draw do
devise_for :users
root 'home#index'
resources :todo
end
Any input as to why this is happening and how to rectify it would be great.
You do not comply with the rails convention. Use plural form for resources. Then, your action is correct.
TodosController, todos_controller.rb, resources :todos
( Rails use singular/plural format to support RESTful links and to recognize named actions )
This
<%= form_for #todo, :url=>todo_path(#todo) do |f| %>
will set (or leave) the form http method to get. You could change it to:
<%= form_for #todo, :url=>todo_path(#todo), method: :post do |f| %>
or even shorter, leave it to Rails to find out what method is needed:
<%= form_for #todo do |f| %>
I found a fix to this exact issue if anyone is still curious, i know its an old issue and an easy one at that, but still figured id solve it. the original route todo_path leads to todo#show. todo_index however is assigned to todo#index and todo#create so its what we want. the line should look like this:
<%= form_for #todo, :url => todo_index_url(#todo), method: :post do |f| %>
I encountered a similar issue with one of my applications and stumbled across this post as a fix. None of the suggestions worked for me, but i was able to fix it with a little tinkering on the routes.

Why am I unable to save an associated object in a has_one relationship?

I've got a pickem object that has one result. I'm having an issue getting the result to save properly to the database.
pickems_controller.rb
def results
#pickem = Pickem.find params[:id]
# #pickem.result = #pickem.build_result if #pickem.result.blank?
#pickem.result = Result.new
end
def update_results
#pickem = Pickem.find params[:id]
#pickem.result = Result.new params[:pickem][:result_attributes]
if #pickem.result.update_attributes params[:pickem][:result_attributes]
redirect_to edit_pickem_results_path(#pickem), :notice => 'The results have been successfully updated.'
else
render "edit"
end
end
results.html.erb
<%= simple_form_for #pickem, :url => edit_pickem_results_path(#pickem), :method => :put, do |f| %>
<%= f.simple_fields_for :result do |r| %>
<%= r.input :first_name %>
...
<% end %>
<%= f.submit :class => 'btn btn-success', :label => 'Submit Results' %>
<% end %>
pickem.rb
has_one :result, :dependent => :destroy
accepts_nested_attributes_for :result
result.rb
belongs_to :pickem
I was initially using the build_result code that is commented out in the controller but had to back out of that. With build_result a result record was saved to the database the instant somebody clicked into the results form. There are rules in place in the application that don't allow users to make any picks if a result has been entered. So even if a user clicked into the result form but didn't submit, the result was still being created.
How can I build my form and save the result record only when the user clicks save, and not when the form is loaded? The current solution I've pasted above does not work. It saves a result record with the appropriate foreign key but never gets the form data. If I dump #pickem.result the correct form data is in the result object, I just can't get it to save right. Other solutions I've tried save the form data correctly but have a foreign key of 0.
EDIT:
For whatever reason #pickem.result = Result.new was still saving a record to the database so I changed it to #result = Result.new and updated the form as follows:
<%= simple_form_for #result, :url => edit_pickem_results_path(#pickem), :method => :put, do |r| %>
<%= r.input :first_name %>
<%= r.submit :class => 'btn btn-success', :label => 'Submit Results' %>
<% end %>
Then using the suggestion from Chuck W of #result = #pickem.result.build params[:result], I get undefined methodbuild' for nil:NilClass`.
pickems_controller.rb
def results
#pickem = Pickem.find params[:id]
#pickem.result.blank? ? #result = Result.new : #result = #pickem.result
end
def update_results
#pickem = Pickem.find params[:id]
 #result = #pickem.result.build params[:pickem][:result]
if #result.save
redirect_to edit_pickem_results_path(#pickem), :notice => 'The results have been successfully updated.'
else
render "edit"
end
end
Then, your view should look something like this:
<%= simple_form_for #pickem, :url => edit_pickem_results_path(#pickem), :method => :put, do |f| %>
<%= f.simple_fields_for #result do |r| %>
<%= r.input :first_name %>
...
<% end %>
<%= f.submit :class => 'btn btn-success', :label => 'Submit Results' %>
<% end %>
You might have to play around with how the parameters are being passed back to the update_results action (I'm pretty new to rails), but I think you get the gist of it.

Resources