I've got the following form:
<%= form_for(#subscription = #task.subscriptions.build(:user_id => subscribers.id)) do |f| %>
<%= f.check_box :subscribed, :class => 'submittable' %>
<%= f.label :subscribed, subscribers.full_name %>
<%= f.hidden_field :user_id, :value => subscribers.id %>
<%= f.hidden_field :task_id, :value => #task.id %>
<% end %>
The 'submittable' class on the checkbox causes the form to be submitted (via jQuery) on update.
:susbcribed is returned via a method in model that returns whether a user is subscribed or not - it cannot be modified directly.
The controller is available here: http://pastebin.com/zZy6KcXz - it is the standard scaffold.
When I click the checkbox, the subcription is successfully created, but I cannot work out how to get it to delete the subscription when unticked.
cjm, in follow up to jimworm's answer, the controller's destroy method is called when you DELETE (HTTP verb, it's actually a POST with a _method=DELETE field passed since some browsers don't support the DELETE verb.
as he said:
<%= link_to 'Delete', #model, :confirm=> 'Are you sure?', :method=> :delete %>
The route is the same as your show or GET /models/1 but the verb DELETE is used instead,
DELETE /models/1
which is actually
POST /models/1 with a hidden field _method=DELETE passed in order to support all browsers.
As he also mentioned, Rails automatically figures out which action to use when using form_for by checking to see if the #model is a new_record? (no id yet) or an existing one. It will then pick
POST /models for create
or
PUT /models/1 for update
It's a form_for #subscription, so it'll probably be submitting to the create or update actions, which don't destroy models. You could hack the action or the model to make it destroy on create(!)/update. Probably the action... updating the model for this hack gives me the heeby-jeebies.
The Rails way™ to the DELETE method and get to the destroy route is a link like this:
<%= link_to 'Delete', #model, :confirm=> 'Are you sure?', :method=> :delete %>
Watch out for IE9 though. If you let your redirects go to another "deleteable" it'll potentially go up the chain and delete your entire database. http://techno-weenie.net/2011/8/19/ie9-deletes-stuff/
The correct redirect after successful POST, PUT and DELETE is:
redirect_to path, :status => 303
Related
I'm trying to create an order confirmation page for my Rails 6 app. The idea is that user will see a preview of the item they are creating before submitting and the object being saved in the database. Below desired flow:
User visits /cash_transactions/withdrawals/new
User enters data and clicks submit
User is redirected to /cash_transactions/withdrawals/confirm which
displays the entry
User clicks confirm to save object to db or cancel
Object is saved
I followed two main threads that describe this type of action, but they are quite old - 11 and 12 years old. Nevertheless based on that I've created below code:
# controllers/cash_transactions/withdrawals_controller.tb
module CashTransactions
class WithdrawalsController < CashTransactions::BaseController
(...)
def confirm
#cash_transaction = CashTransaction.new(cash_transaction_params)
render 'cash_transactions/_confirm'
end
end
end
# routes.rb
namespace :cash_transactions do
resources :withdrawals, only: %i[new create] do
collection do
post :confirm
end
end
end
With corresponding views:
# app/views/cash_transactions/new.html.erb
<%= render 'cash_transactions/form', cash_transaction: #cash_transaction %>
# views/cash_transactions/_form
# the form is rendered for cash_transaction create action
<%= simple_form_for cash_transaction, url: { action: :confirm } do |f| %>
<%= f.input :amount %>
<%= f.button :submit, 'Submit' %>
<% end %>
# confirmation page under views/cash_transactions/_confirm.html.erb
<div>
Total of withdrawal: <%= #cash_transaction.amount.to_i %>
</div>
<%= link_to 'Confim', cash_transactions_withdrawals_path(#cash_transaction), method: :post %>
<%= link_to 'Cancel', cash_transactions_path %>
And everything works until the user clicks confirm button in views/cash_transactions/_confirm.html.erb - instead of creating a record an error appears:
param is missing or the value is empty: cash_transaction
Did you mean?
authenticity_token
action
controller
_method
where did I go wrong? or there is a completely different way to do so?
tl/dr: You need to add parameters to your create request.
Why this is happening
The /confirm view is being rendered with an (unsaved) #cash_transaction object, however that object is not being used and so the information is being lost when the page is rendered.
The line:
<%= link_to 'Confim', cash_transactions_withdrawals_path(#cash_transaction), method: :post %>
Will submit a POST request with no parameters to the /cash_transactions/withdrawals#create (because you've given it no parameters to post). It doesn't know to include the params from the previous request.
There are a few options to fix this... you can add params as URL parameters in link_to like this, however I wouldn't recommend posting with params in the URL.
You can use button_to instead, and pass in the cash_transaction arguments from the previous request in the params: option (or pull them out of the unsaved #cash_transaction object).
Approach #1 - reuse create params
# Get them from the params sent in the previous request. In the controller...
def create
#cash_transaction = CashTransaction.create!(cash_transaction_params)
# etc...
end
#...
protected
def cash_transaction_params
params[:cash_transaction].permit(:amount, :whatever)
end
helper_method :cash_transaction_params
# In the view
<%= button_to 'Confirm', {action: 'create', params: cash_transaction_params}
Approach #2 - Access attributes from the model you built
<%= button_to 'Confirm', {action: 'create', params: #cash_transaction.attributes.slice('amount', 'other_attribute') }
Or you could do something like render the form again but hidden and have the "confirm" button submit the hidden form (with { action: :create } instead of { action: :confirm}). This last solution is probably the easiest to understand.
<% #books.each do |book| %>
<% unless book.checkout_user_id == nil %>
<%= link_to "delete checkout", book_checkout_path(book_id: book.id), :confirm => 'Are you sure?', :class => "button", :method => :delete %>
<% end %>
<% end %>
In the above code, I want to pass a parameter to my book_checkout controller. How can I make it so my destroy method will retrieve the :book_id passed in from book_checkout_path.
Then create an instance to search for a checkout with the corresponding book_id attribute rather than search by ID.
def destroy
#book_checkout = BookCheckout.where(book_id: book_id).first # this line is wrong
#book_checkout.destroy
redirect_to books_path
end
EDIT: Added routes.
routes:
book_checkout GET /book_checkouts/:id(.:format) book_checkouts#show
PATCH /book_checkouts/:id(.:format) book_checkouts#update
PUT /book_checkouts/:id(.:format) book_checkouts#update
DELETE /book_checkouts/:id(.:format) book_checkouts#destroy
I had to fix my original code for a workaround, but it isn't the cleanest.
<%= link_to "delete checkout", book_checkout_path(id: book.id, check: book.id), :confirm => 'Are you sure?', :class => "button", :method => :delete %>
My original link_to NEEDS to pass id because my routes expect that, so I added a check which passes the attribute i will need.
#book_checkout = BookCheckout.find_by(book_id: params[:check])
In my checkout controller, I used the params[:check] instead of params[:id], because I cannot overwrite params[:id] with the book.id.
#book_checkout = BookCheckout.find_by(book_id: params[:book_id])
Since the route only has one id, I think you can use
book_checkout_path(book.id)
However, it seems strange to use the Book id to find the BookCheckout. Is there any special reason you can't do:
book_checkout_path(book_checkout.id)
and
#book_checkout = BookCheckout.find_by(id: params[:id])
Also, is it possible that both the Book id and the Book Checkout id are the same (1)? This would make it appear to succeed when it shouldn't.
Okay so I'm building a really simple list with items app, pretty much exactly the same as your standard to-do list application. I've managed to ajax-ify the creation of new 'points' within a list (point belongs_to :list and list has_many :points) but I'm having trouble with the 'destroy' action.
When I click on the destroy link in the browser, nothing visibly occurs, and I get the error Error: Syntax error, unrecognized expression: /lists/10/points/125 obviously with different values depending on the id of the list and point.
If I refresh the page or look at the db, it's clear that the entry has indeed been deleted. Without ajax, my destroy action works just fine. I feel like I must be missing something obvious, any ideas?
fyi the 'pro' attribute is just a boolean associated with every point.
points_controller.rb
def destroy
#point = #list.points.find(params[:id])
#point.destroy
respond_to do |format|
format.html { redirect_to list_url(#list) }
format.js
end
end
lists/show.html.erb
<% #list.points.each do |point| %>
<% if point.pro == true and point.valid? == true %>
<li class="weight-<%= point.weight %>"><%= point.content %>
<%= link_to "×".html_safe, [#list, point],
:remote => true,
:method => :delete,
:class=> "close",
:data => {:dismiss => 'alert'} %>
</li>
And it doesn't seem to matter what I put in views/points/destroy.js.erb, because the code doesn't seem to be getting executed.
Update
I figured it out, I had to change the path in the delete link to list_point_url(#list, point). The other problem was that my invalid javascript was causing a server error, so I didn't realize what the problem was (turns out #<%= dom_id(#point) %> needed to be wrapped in quotes).
Thanks all!
Maybe check if the delete link routes to the destroy controller action, because list_point_path doesn't really seem like a delete route.
Edit
Sorry for the lake of knowledge but I'm not sure what [#list, point] will produce as a route. This is what I have for a view of my own, just for your reference:
link_to "Delete", admin_photo_path(photo), :method => :delete, :confirm => "Delete this image?", :class => "btn-trash"
My admin_photo_path is a singular path that route to a single Photo instance; not a collection.
Edit
Simple way could be sending delete to the point object, maybe this could help?
link_to "×".html_safe, point,
:remote => true,
:method => :delete,
:class=> "close",
:data => {:dismiss => 'alert'}
If you have a post action to "do_something/:id" => :start (say with a named route 'start'), how do you create a form_tag that submits the :id based on a select_tag selection by the user ?
Assuming you aren't using resources.
The issue is that you have a post action, however you want to set a GET variable via a form submission(that has to use POST).
You can make the form make a GET request, like so:
<%= form_tag(start_path, :method => "get") do %>
<%= select_tag "id", "<option>1</option><option>2</option><option>3</option><option>4</option>" %>
<% end %>
I have a form which is always submitting the form to the "update" function in the controller, and I have created this form using "remote_form_for" tag. In this form I have objects from different tables, and from this form I want to submit entire form data to another function(not to the "update" function) via AJAX request.
I have tried many methods including using submit tag with action
<% remote_form_for #employee, :url => organization_employee_path(#organization, #employee), :method => :put do |employee_form| %>
// form objects and other functionalities
....
....
// views inside the forms
<div id="employee_header_div">
<%= render :partial => "employee_header", :locals => {:employee => #employee} %>
</div>
...
...
<%= submit_tag "page_level_validation", :id => "page_level_validation" , :action=>"validate"%>
<% end %>
But the Ajax request always calling the same "update" function.
It would be very helpful, if anyone helps to resolve this issue.
You can't set the submit to point to a different place than the main form has specified (unless you want to use the HTML5 formaction attribute and deal with the browser compatibility consequences).
However, what you could do is create a new action in your controller which deals with the situation.
e.g..
<% remote_form_for #employee, :url => organization_employee_validate_path(#organization, #employee), :method => :put do |employee_form| %>
in your controller
def validate
#do something loosely based around the update method
end
not forgetting to add the appropriate routes.
Try this:
<% form_for #employee, :remote => true, :url => organization_employee_path(#organization, #employee), :method => :put do |employee_form| %>