In my app there is a list of items which you can upvote. I want to make these votes with AJAX calls.
This is the view:
<ul class="list-groups">
<% #questions.each do |question| %>
<li class="list-group-item">
<%= link_to question.description, question_path(question) %>
<%= form_for(question, :url => url_for(:controller => 'vote', :action => 'vote'), method: :post, html: { class: 'form-inline' }) do |f| %>
<%= f.submit 'Up vote', class: "btn btn-default" %>
<%= f.hidden_field :id, :value => question.id %>
<% end %>
</li>
<% end %>
</ul>
And this the method that does it:
class VoteController < ApplicationController
respond_to :json
def vote
question_id = params[:question][:id]
user_id = current_user.id
vote = Vote.where(["question_id = :q", { q: question_id }]).where(["user_id = :u", { u: user_id }])
respond_to do |format|
if vote.nil?
#vote = Vote.new
#vote.question_id = question_id
#vote.user_id = user_id
#vote.save
format.html { render '/home/index' }
format.json { render :json => { :status => 'ok' } }
else
format.html { render '/home/index' }
format.json { render :json => { :status => 'failed', :msg => 'You already voted' } }
end
end
end
end
If I don't include this format.html { render '/home/index' } I am getting this error:
ActionController::UnknownFormat in VoteController#vote
But I don't want to render the page again, I am just loading the pieces of html that will change after the action with jQuery and ajax.
How can I respond only with the json?
Use respond_with instead of respond_to in your controller action.
respond_with do |format|
respond_to at the top of your controller is designed to work with respond_with in your controller action, whereas respond_to in your controller action ignores the respond_to that you've defined at the top of your controller.
Also make sure you are making a remote call, instead of a normall one if you want your request to go through AJAX.
<%= form_for(question, :url => url_for(:controller => 'vote', :action => 'vote'), method: :post, remote: true, html: { class: 'form-inline' }) do |f| %>
Note the remote: true part that is added as an argument to the form_for helper.
You need to include remote: true in form_for in order to make the call requests AJAX instead of HTML.
<%= form_for(question, :url => url_for(:controller => 'vote', :action => 'vote'), remote: true, method: :post, html: { class: 'form-inline' }) do |f| %>
Check out the Working with JavaScript in Rails documentation for more information.
Related
I am trying to update a record from the view of another scaffold, the problem is that it sends me the following error:
No route matches {:action=>"update", :controller=>"pedidosliberados"}
I'm showing one form per record
this is my form:
<% #pedidosliberados.each do |pedidoliberado| %>
<tr id="pedido_liberado_<%= pedidoliberado.id %>">
<td class="component_name_body_col"><%=pedidoliberado.try(:Client)%></td>
<td class="component_name_body_col">
<%= form_for(pedidoliberado, :method => :put, remote: true, :url => {:controller =>'pedidosliberados', :action => 'update'}, html: {class: "form-horizontal "}) do |f| %><!--ajax-->
<%= f.text_field :Status, value: "1" %>
<%= submit_tag "Actualizar", class: "btn btn-primary"%>
<% end %>
</td>
</tr>
<% end %>
my route:
resources :pedidosliberados
my action update from controller pedidosliberados:
def update
respond_to do |format|
if #pedidoliberado.update(pedidoliberado_params)
format.html { redirect_to #pedidoliberado, notice: 'Pedidoliberado was successfully updated.' }
format.json { render :show, status: :ok, location: #pedidoliberado
format.js {}
else
format.html { render :edit }
format.json { render json: #pedidoliberado.errors, status: :unprocessable_entity }
format.js {}
end
end
end
The problem is that you are using an incomplete route, that is /update; you need to add the id of the object to be updated, something like /update/12 where 12 is the id of the object. So in your code you could remove the :url attribute and you will be fine.
So your view, now should look like this:
<% #pedidosliberados.each do |pedidoliberado| %>
<tr id="pedido_liberado_<%= pedidoliberado.id %>">
<td class="component_name_body_col"><%=pedidoliberado.try(:Client)%></td>
<td class="component_name_body_col">
<%= form_for(pedidoliberado, :method => :put, remote: true, html: {class: "form-horizontal "}) do |f| %><!--ajax-->
<%= f.text_field :Status, value: "1" %>
<%= submit_tag "Actualizar", class: "btn btn-primary"%>
<% end %>
</td>
</tr>
<% end %>
This works because rails automatically assigns the correct controller and action for your object (when using REST routes).
UPDATE
If you want to explicity assign the url, then use :url like you are doing, but adding :id (remember, you need to specify the id of the object to be updated). So, your form should look something like this:
<%= form_for(pedidoliberado, method: :put, remote: true, url: { controller: 'pedidosliberados', action: 'update', id: pedidoliberado.id }, html: { class: "form-horizontal " }) do |f| %>
I am trying to have a link to delete a certain reservation from the database. This is my solution but it keeps giving Template Error. It shouldn't render the view but it is.
def delete
#current = Reservations.find(params['reservations']['id'])
respond_to do |format|
if current_user
if #current.user_id == current_user.id
#current.destroy
format.html { redirect_to :back }
else
format.html { redirect_to root_path }
end
else
format.html { redirect_to root_path }
end
end
end
My routes file
post "reservations/delete", to: 'reservations#delete', as: 'delete_reservation'
My view that includes the delete link:
<%= form_for item, :url => {:controller => 'reservations', :action => 'delete'}, :method => 'post' do |f| %>
<%= f.hidden_field :id, :value => item.id %>
<%= f.submit "Delete", :class =>'btn btn-danger btn-small' %>
<% end %>
What I want to do is pass a the value of the fields in the form to the controller so I can make a custom query in the db. I think something is missing but I just can't see what.
This is my controller
def index
#contributions = Contribution.all
#number1 = params[:number1]
#number2 = params[:number2]
#itemsok = Contribution.where("first_item_id = ?",#numer1).where("first_item_grade = ?",#numer2)
respond_to do |format|
format.html # index.html.erb
format.json { render json: #contributions }
end
This is the view
<%= form_tag(contribution_path, :method => "get") do %>
<%= label_tag(:number1, "Number 1:") %>
<%= text_field_tag(:number1) %>
<%= label_tag(:number1, "Number 2:") %>
<%= text_field_tag(:number2) %>
<%= submit_tag("Searcs") %>
<% end %>
And this is the line in the routes.rb
get 'contribution' => 'contributions#index', :as => 'contribution'
Thank you very much.
Add :remote => true to the form tag to make it submit via ajax, and make sure to respond_to format.js in the controller action.
<%= form_tag(contribution_path, :remote => true) %>
I had hard time to figure out why I've been getting "unknown action" error message when I was do some editing:
Unknown action
No action responded to 11. Actions: bin, create, destroy, edit, index, new, observe_new, show, tag, update, and vote
you can see that Rails did mention each action in the above list - update. And in my form, I did specify action = "update".
I wonder if some friends could kindly help me with the missing links...
here is the code:
edit.rhtml
<h1>Editing tip</h1>
<% form_tag :action => 'update', :id => #tip do %>
<%= render :partial => 'form' %>
<p>
<%= submit_tag_or_cancel 'Save Changes' %>
</p>
<% end %>
_form.rhtml
<%= error_messages_for :tip %>
<p><label>Title<br/>
<%= text_field :tip, :title %></label></p>
<p><label>Categories<br/>
<%= select_tag('categories[]', options_for_select(Category.find(:all).collect {|c| [c.name, c.id] }, #tip.category_ids), :multiple => true ) %></label></p>
<p><label>Abstract:<br/>
<%= text_field_with_auto_complete :tip, :abstract %></label></p>
<p><label>Name: <br/>
<%= text_field :tip, :name %></label></p>
<p><label>Link: <br/>
<%= text_field :tip, :link %></label></p>
<p><label>Content<br/>
<%= text_area :tip, :content, :rows => 5 %></label></p>
<p><label>Tags <span>(space separated)</span><br/>
<%= text_field_tag 'tags', #tip.tag_list, :size => 40 %></label></p>
class TipsController < ApplicationController
before_filter :authenticate, :except => %w(index show)
# GET /tips
# GET /tips.xml
def index
#tips = Tip.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #tips }
end
end
# GET /tips/1
# GET /tips/1.xml
def show
#tip = Tip.find_by_permalink(params[:permalink])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #tip }
end
end
# GET /tips/new
# GET /tips/new.xml
def new
#tip = session[:tip_draft] || current_user.tips.build
end
def create
#tip = current_user.tips.build(params[:tip])
#tipMail=params[:email]
#if tipMail
# TipMailer.deliver_email_friend(params[:email], params[:name], tip)
# flash[:notice] = 'Your friend has been notified about this tip'
#end
#tip = current_user.tips.build(params[:tip])
#tip.categories << Category.find(params[:categories]) unless params[:categories].blank?
#tip.tag_with(params[:tags]) if params[:tags]
if #tip.save
flash[:notice] = 'Tip was successfully created.'
session[:tip_draft] = nil
redirect_to :action => 'index'
else
render :action => 'new'
end
end
def edit
#tip = Tip.find(params[:id])
end
def update
#tip = Tip.find(params[:id])
respond_to do |format|
if #tip.update_attributes(params[:tip])
flash[:notice] = 'Tip was successfully updated.'
format.html { redirect_to(#tip) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #tip.errors, :status => :unprocessable_entity }
end
end
end
def destroy
#tip = Tip.find(params[:id])
#tip.destroy
respond_to do |format|
format.html { redirect_to(tips_url) }
format.xml { head :ok }
end
end
def observe_new
session[:tip_draft] = current_user.tips.build(params[:tip])
render :nothing => true
end
end
the quick answer is that form_tag doesn't support :action as an option, you want to be passing a string as a path in. A slightly longer answer is you shouldn't be using form_tag anyways for a model edit form, you should be using form_for.
what rails are you using? .rhtml is pretty old, rails generators should be giving you .html.erb files. if it is something even remotely recent, you should be able to use
<% form_for #tip do |f| %>
<%= f.label :title, 'Title' %><br />
<%= f.text_field %>
... etc
<% end %>
I have a rails application that models a house. house contains rooms and rooms have nested attributes for light and small_appliance. I have a calculator controller, which is how end users will access the application.
My problem is that I can't get the partial for adding rooms to render and submit correctly from calculator. The initial page lets the user enter house information, which is saved using save_house when submit is clicked. This also redirects the user to the add_rooms page, where they can add rooms to the house.
add_rooms displays correctly, but when I click submit, I get this error:
RuntimeError in Calculator#add_room
Showing app/views/calculator/add_rooms.html.erb where line #2 raised:
Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
Extracted source (around line #2):
1: <div id="addRooms">
2: <p>House id is <%= #house.id %></p>
3:
4: <h3>Your rooms:</h3>
5: <% if #house.rooms %>
RAILS_ROOT: C:/Users/ryan/Downloads/react
Application Trace | Framework Trace | Full Trace
C:/Users/ryan/Downloads/react/app/views/calculator/add_rooms.html.erb:2:in `_run_erb_app47views47calculator47add_rooms46html46erb'
C:/Users/ryan/Downloads/react/app/controllers/calculator_controller.rb:36:in `add_room'
C:/Users/ryan/Downloads/react/app/controllers/calculator_controller.rb:33:in `add_room'
This is odd to me, because when add_rooms first renders, it shows the house_id. I don't understand why it isn't passed after the form is submitted.
Here's the code:
app/models/room.rb
class Room < ActiveRecord::Base
# schema { name:string, house_id:integer }
belongs_to :house
has_many :lights, :dependent => :destroy
has_many :small_appliances, :dependent => :destroy
validates_presence_of :name
accepts_nested_attributes_for :lights, :reject_if => lambda { |a| a.values.all?(&:blank?) }, :allow_destroy => true
accepts_nested_attributes_for :small_appliances, :reject_if => lambda { |a| a.values.all?(&:blank?) }, :allow_destroy => true
end
app/models/house.rb
class House < ActiveRecord::Base
has_many :rooms
# validation code not included
def add_room(room)
rooms << room
end
end
app/controllers/calculator_controller.rb
class CalculatorController < ApplicationController
def index
end
def save_house
#house = House.new(params[:house])
respond_to do |format|
if #house.save
format.html { render :action => 'add_rooms', :id => #house }
format.xml { render :xml => #house, :status => :created, :location => #house }
else
format.html { render :action => 'index' }
format.xml { render :xml => #house.errors, :status => :unprocessable_entity }
end
end
end
def add_rooms
#house = House.find(params[:id])
#rooms = Room.find_by_house_id(#house.id)
rescue ActiveRecord::RecordNotFound
logger.error("Attempt to access invalid house #{params[:id]}")
flash[:notice] = "You must create a house before adding rooms"
redirect_to :action => 'index'
end
def add_room
#room = Room.new(params[:room])
#house = #room.house
respond_to do |format|
if #room.save
flash[:notice] = "Room \"#...#room.name}\" was successfully added."
format.html { render :action => 'add_rooms' }
format.xml { render :xml => #room, :status => :created, :location => #room }
else
format.html { render :action => 'add_rooms' }
format.xml { render :xml => #room.errors, :status => :unprocessable_entity }
end
end
rescue ActiveRecord::RecordNotFound
logger.error("Attempt to access invalid house #{params[:id]}")
flash[:notice] = "You must create a house before adding a room"
redirect_to :action => 'index'
end
def report
flash[:notice] = nil
#house = House.find(params[:id])
#rooms = Room.find_by_house_id(#house.id)
rescue ActiveRecord::RecordNotFound
logger.error("Attempt to access invalid house #{params[:id]}")
flash[:notice] = "You must create a house before generating a report"
redirect_to :action => 'index'
end
end
app/views/calculator/add_rooms.html.erb
<div id="addRooms">
<p>House id is <%= #house.id %></p>
<h3>Your rooms:</h3>
<% if #house.rooms %>
<ul>
<% for room in #house.rooms %>
<li>
<%= h room.name %> has <%= h room.number_of_bulbs %>
<%= h room.wattage_of_bulbs %> watt bulbs, in use for
<%= h room.usage_hours %> hours per day.
</li>
<% end %>
</ul>
<% else %>
<p>You have not added any rooms yet</p>
<% end %>
<%= render :partial => 'rooms/room_form' %>
<br />
</div>
<%= button_to "Continue to report", :action => "report", :id => #house %>
app/views/rooms/_room_form.html.erb
<% form_for :room, #house.rooms.build, :url => { :action => :add_room } do |form| %>
<%= form.error_messages %>
<p>
<%= form.label :name %><br />
<%= form.text_field :name %>
</p>
<h3>Lights</h3>
<% form.object.lights.build if form.object.lights.empty? %>
<% form.fields_for :lights do |light_form| %>
<%= render :partial => "light", :locals => { :form => light_form } %>
<% end %>
<p class="addLink"><%= add_child_link "[+] Add new light", form, :lights %></p>
<h3>Small Appliances</h3>
<% form.object.small_appliances.build if form.object.small_appliances.empty? %>
<% form.fields_for :small_appliances do |sm_appl_form| %>
<%= render :partial => "small_appliance", :locals => { :form => sm_appl_form } %>
<% end %>
<p class="addLink"><%= add_child_link "[+] Add new small appliance", form, :small_appliances %></p>
<p><%= form.submit "Submit" %></p>
<% end %>
application_helper.rb
module ApplicationHelper
def remove_child_link(name, form)
form.hidden_field(:_delete) + link_to_function(name, "remove_fields(this)")
end
def add_child_link(name, form, method)
fields = new_child_fields(form, method)
link_to_function(name, h("insert_fields(this, \"#{method}\", \"#{escape_javascript(fields)}\")"))
end
def new_child_fields(form_builder, method, options = {})
options[:object] ||= form_builder.object.class.reflect_on_association(method).klass.new
options[:partial] ||= method.to_s.singularize
options[:form_builder_local] ||= :form
form_builder.fields_for(method, options[:object], :child_index => "new_#{method}") do |form|
render(:partial => options[:partial], :locals => { options[:form_builder_local] => form })
end
end
end
Thanks,
Ryan
Out of curiosity why not have house accept nested attributes for rooms. This would make your controller code simpler, as adding many rooms, lights and small appliances is as simple as just doing #house.update_attributes(params[:house]). However this is not an answer that helps, as you would still have your current problems if you made the change.
Your first error comes from the first line of app/views/calculator/_room_form.html.erb
<% form_for :room, :url => { :action => :add_room, :id => #house } do |form| %>
You're not giving form_for an object so the new_child_fields method called by add_child
_link is trying to call reflect_on_association on the Nil class.
The solution is change the line to
<% form_for :room, #house.rooms.build, :url => { :action => :add_room } do |form| %>
This lets you simplify your controller, because a room associated with a house is already being passed to it.
def add_room
#room = Room.new(params[:room])
#house = #room.house
respond_to do |format|
if #room.save
flash[:notice] = "Room \"#...#room.name}\" was successfully added."
format.html { render :action => 'add_rooms' }
format.xml { render :xml => #room, :status => :created, :location => #room }
else
format.html { render :action => 'add_rooms' }
format.xml { render :xml => #room.errors, :status => :unprocessable_entity }
end
end
rescue ActiveRecord::RecordNotFound
logger.error("Attempt to access invalid house #{params[:id]}")
flash[:notice] = "You must create a house before adding a room"
redirect_to :action => 'index'
end
I believe your second error is the same problem. However because you're calling a has_many accessor instead of getting a nil being generated, you're passing an empty array, which explains the difference in error messages. Again the solution is to build a light and small appliance before rendering if none exist yet.
<h3>Lights</h3>
<% form.object.lights.build if form.object.lights.empty? %>
<% form.fields_for :lights do |light_form| %>
<%= render :partial => 'rooms/light', :locals => { :form => light_form } %>
<% end %>
<p class="addLink"><%= add_child_link "[+] Add new light", form, :lights %></p>
<h3>Small Appliances</h3>
<% form.object.small_appliances.build if form.object.small_appliances.empty? %>
<% form.fields_for :small_appliances do |sm_appl_form| %>
<%= render :partial => 'rooms/small_appliance', :locals => { :form => sm_appl_form } %>
<% end %>
Your new error comes from this:
def new_child_fields(form_builder, method, options = {})
options[:object] ||= form_builder.object.class.reflect_on_association(method).klass.new
# specifically this line.
options[:partial] ||= method.to_s.singularize
options[:form_builder_local] ||= :form
form_builder.fields_for(method, options[:object], :child_index => "new_#{method}") do |form|
render(:partial => options[:partial], :locals => { options[:form_builder_local] => form })
end
end
new_child_fields is assuming that the _light partial is in the app/views/calculators folder
The solution is to either move the light and small_appliances partials to this folder or modify your helper methods to accept a partial option.