Rails: Multi-submit buttons in one Form - ruby-on-rails

Say I have an Article model, and in the article 'new' view I have two buttons, "Publish" and "Save Draft".
My question is how can I know which button is clicked in the controller.
I already have a solution but I think there must be a better way.
What I currently used in the view is:
<div class="actions">
<%= f.submit "Publish" %>
<%= f.submit "Save Draft", :name => "commit" %>
</div>
So in the controller, I can use the params[:commit] string to handle that action.
def create
#article = Article.new(params[:article])
if params[:commit] == "Publish"
#article.status = 'publish'
// detail omitted
end
#article.save
end
But I think using the view related string is not good. Could you tell me another way to accomplish this?
UPDATE: Since these buttons are in the same form, they're all going to the 'create' action, and that's OK for me. What I want is to handle that within the create action, such as give the Article model a 'status' column and holds 'public' or 'draft'.

This was covered in Railscast episode 38. Using the params hash to detect which button was clicked is the correct approach:
View:
<%= submit_tag 'Create' %>
<%= submit_tag 'Create and Add Another', name: 'create_and_add' %>
Controller:
if params[:create_and_add]
# Redirect to new form, for example.
else
# Redirect to show the newly created record, for example.
end

it can also be done on the form_for helper like this
<%= f.submit "Publish",name: "publish", class: "tiny button radius success" %>
<%= f.submit 'Mark as Draft', name: "draft", class: "tiny button radius " %>
and the logic is the same on the controller
if params[:publish]
// your code
elsif params[:draft]
// your code
end

We solved using advanced constraints in rails.
The idea is to have the same path (and hence the same named route & action) but with constraints routing to different actions.
resources :plan do
post :save, constraints: CommitParamRouting.new("Propose"), action: :propose
post :save, constraints: CommitParamRouting.new("Finalize"), action: :finalize
end
CommitParamRouting is a simple class that has a method matches? which returns true if the commit param matches the given instance attr. value.
This available as a gem commit_param_matching.

I remember coming across this problem once. You cannot keep two buttons and then call some action based on the params[:commit]. the submit button onclick is going to call the url the form refers to. There are certain bad ways to get the desired behavior. Keep a button to call the action the form refers to and to get another button to call a action, I used a link_to and then changed the styles to match a button. Also, alternatively you can use jQuery to change the url the form would call, hence deciding what action is invoked at run-time. Hope this helps.

You could also set some data attributes on the submit buttons and use JavaScript to change out the form action on click of one of the buttons

usually i using the suggestion given by John Topley (see answer above).
another way is using JQuery /JS changing the form action attribute- upon clicking the submit button
example:
form_tag({} ,:method => 'post', :id => 'reports_action') do
.......
.......
submit_tag 'submit', :onclick => "return changeAction();"
end
and then .....
function changeAction(){
$('#reports_action').attr('action','my_new_action');
}

Related

How to attach an action call to a submit button in Rails?

I have a page where a deactivated user account can be activated - but before they can be activated, they need to be assigned a new password. Currently I have two buttons to perform these actions:
At the end of the 'add password' form there is this button:
<%= f.submit %>
A little further on the page there is this button, which sets the status of the user to 'active':
<%= standard_button 'Activate user', update_status_user_path(#user),
:method => :patch %>
If possible, I'd like add the functionality of the second button to the submit button.
I don't fully grasp the magic behind forms and their buttons, so I am wondering whether this is possible and if so, how I could accomplish this?
You can think of a button as a mini form. View the docs to get more information about the button_to rails method.
In your form_for, there will be a url which specifies where the form is posting to. In that controller method, you can include the logic to update the user's status to 'active'. Something like
def some_method
user = User.find(params[:user_id])
user.update_attribute(:status, "active")
end

Fire up custom action when user clicks 'Search" on RAILS 3.2 form?

Is it possible to fire up an custom action when user clicks 'Search' button on search form?
There is an mechanism in our app to save every URL the app has hit. In our search form, when clicking 'Search' button, there will bring up the search result page. The problem is that the URL for the search result form was not saved. The Back button brings back the search page (for setup search params) instead of the search result page (because its URL was not saved).
Here is the search form for model configs:
<h4>Search Form></h4>
<%= simple_form_for #config, :method => :get, :url => search_result_configs_path do |f| %>
<%=render :partial => 'search_params', :locals => {f: f} %>
<%= f.button :submit, t('Search') %>
<% end %>
The URL for the search result looks like this (with the search params set by user) after user clicks Search button:
http://localhost:3000/configs/search_results?utf8=%E2%9C%93&engine_config[start_date_s]=&engine_config[end_date_s]=&engine_config[engine_id_s]=1&engine_config[argument_name_s]=&engine_config[commissioned_s]=&commit=%E6%90%9C%E7%B4%A2
This is the URL we would like the app to remember. We figure we need custom action triggered when a user clicks 'Search' button. Is it possible?
Route
Firstly, calling a custom application is actually quite a simple process - you just need to call its route:
#config/routes.rb
resources :search do
collection do
get :custom_action
end
end
This will allow you to use the likes of form_tag to call the custom route:
#app/views/your_controller/view.html.erb
<%= form_tag search_custom_action_path, method: :get do %>
...
<% end %>
--
Form
Secondly, you're using simple_form for your search form.
This is completely fine, but the problem you have here is that when you use this, it has to have a ActiveRecord object to populate the form with. This is probably where you're getting confused, as to do this, you need ot make sure #config is available every time you load that form, which I imagine can be a lot.
We've created a search form here:
Although in Rails 4, we used a form_tag for this form, as it allowed us to create & display the form where-ever we need in the app. This allows us to pass the required params through the form & access them on the other side
--
Params
You mention you want to "save the URL" - what do you mean by this?
Surely you'd prefer to save the params?
If this is true, the way to do this is actually relatively simple - you'll get access to the params hash in your controller when you send the request through:
#app/controllers/your_controller.rb
Class YourController < ApplicationController
def custom_action
params[:your_param] #-> this is accessible here
end
end
The bottom line is if you wanted to save the query strings, you'll have to create a model called Search or similar, allowing you to pass the params through when you process the custom action in your controller, just like you would any other ActiveRecord object

How do I create a button that increments an attribute of a model and can be pressed once per browser?

An apartment_listing has many reviews, and a review belongs to an apartment_listing.
In the file views/apartment_listings/show.html.erb, I show a list of reviews for that particular apartment_listing. These reviews are generated with the partial view apartment_listings/_review.html.erb like so:
<%= render :partial => "review", :collection => #apartment_listing.reviews %>
In _review, I want to have a button that, when pressed:
Increments that review's helpful_count attribute.
Makes it so that it cannot be pressed again while in the same browser - probably using cookies.
I feel like the former shouldn't be too hard to figure out, but it's got me beat. I'm really not sure where to start with the second goal.
EDIT: I managed to update the review's helpful_count attribute with this code in apartment_listings/_review.html.erb:
<%= form_for review, :method => :put, :remote => true do |f| %>
<%= f.hidden_field :helpful_count, value: (review.helpful_count + 1) % >
<%= f.submit 'Helpful?' %>
<% end %>
However, I'm not sure if this is the best way to do it, and I'd like to be able to disable the button after it is clicked.
Your code for updating helpful_count has the potential for problems. Imagine two users have loaded an apartment on their web page. One of them marks it helpful, and the next one does as well. Since when they initially loaded the page, helpful_count was the same, after both of them click helpful, the count will only be incremented by one: it would be updated twice to the same value.
Really, you want to create a new action, probably under the reviews resource for an apartment. That action could use ActiveRecord's increment method to update the helpful_count (technically there's still a race condition in increment!, you'd encounter it much less often) http://apidock.com/rails/ActiveRecord/Persistence/increment%21
Cookies seem like a reasonable solution for the latter problem. Simply bind to submit on the form with jQuery, and create the cookie in the handler.
What does the code look like in your reviews controller? More experienced RESTful coders might be able to speak more coherently on this, but the way I see it, incrementing the helpful_count attribute should be an action sent to the reviews controller. That way, you can create a link that performs the action asynchronously.
For example, inside _review.html.erb:
<% collection.each do |review| %>
<%= link_to "Mark as Helpful", "/apartment_listing/#{#apartment_listing.id}/reviews/#{#review.id}/incHelpful?nonce=#{SecureRandom.rand(16)}", :remote => true, :method => :put %>
# ... Do something cool with your review content ...
<% end %>
Inside your ReviewsController class:
def incHelpful
unless params[:nonce] == session[:nonce][params[:id]]
#review = Review.find(params[:id])
#review.helpful_count += 1
#review.update_attributes(:helpful_count)
session[:nonce][params[:id]] = params[:nonce]
end
render :nothing
# Optionally return some javascript or JSON back to the browser on success/error
end
Inside /config/routes.rb:
put "apartment_listing/:apart_id/reviews/:id/incHelpful" => "reviews#incHelpful"
The main idea here is that actions that edit a resource should use the PUT http method, and that change should be handled by that resource's controller. Rails' built-in AJAX functions are engaged by setting :remote => true inside the link_to helper. The second concept is that of a nonce, a random value that is only valid once. Once this value is set in the user's session, subsequent requests to incHelpful will do nothing.

Pass Parameters in Link_to

I would like to pass a parameter through a link_to method in Rails. I know there is a way to do it via the URL but I really don't want to do that. Is there any way to pass a param via a link without adding it to the link itself?
I know in PHP you can post and then retrieve that value by using the post variable. Is there something similar in Rails?
link_to signature looks as follows:
link_to(body, url_options = {}, html_options = {})
So, POST would look like (lets say you want to POST user's data):
link_to "Link text", some_path(:foo => "bar", :baz => "quux"), user: #user, :method => :post
User's data can be retrieved in the controller using params[:user]
Here's how you can pass a parameter around via the link_to method in order to, say, create a new object with the passed parameter. This strategy would allow you to pass variables among actions in your controller and create objects with predefined attributes:
Say in your show view, you have a variable called #foo that you want to pass to your new controller action. In which case, in your show view, you can have
<%= link_to "Link Text", new_widget_path(:foo => #foo) %>
which would store #foo in params[:foo], allowing you to use params[:foo] in your controller. Which controller action you get directed to depends upon *new_widget_path*. In this case, you get directed to the new action in WidgetController.
Clicking on Link Text will direct Rails to the new action of your WidgetController. You can have
def new
#widget = Widget.new(:foo => params[:foo])
end
Then, in your new.html.erb view file, you can allow the user to create a new Widget object with this pre-defined foo attribute already filled out via a hidden form field:
<%= form_for(#widget) do |f| %>
<%= f.label :other_attribute %><br />
<%= f.text_field :other_attribute %>
<%= f.hidden_field :foo %>
<%= f.submit %>
<% end %>
Allowing the user to create a new widget with the foo attribute already filled out!
Passing information through a web request can be done either by the URL: http://example.com/foo?bar=blah in a GET request which is what link_to does, or through a POST operation which usually requires a form. The form could have hidden elements if you just want a submit button:
<form method="POST" action="http://example.com/foo">
<input type="hidden" name="bar" value="blah">
<input type="submit">
</form>
There are various rails helpers to help build the form if needed.
Lastly, if you really want a link, you could either CSS style that button, or you could use javascript to observe a link and then POST the info. (the method Simon Bagreev posted does this with javascript)
What sort of parameter? If it's a key for a GET request, convention would dictate using the url (e.g. params[:id] or a an active record path variable). If you want to POST something, you should be using a form. Otherwise, you could write a helper method to set a session variable or something, but think about your architecture and what you're semantically trying to do, and I'm sure someone here can help you out.

Multiple Submit button in a rails form

I am trying to implement a "Preview" feature before creating an object for a product. I followed Ryan Bates railscast on doing so, in which he does so by passing a parameter :name with the submit button.
<%= submit_tag 'Preview', :name => 'preview_button' %>
However, it's not working in my case, don't know why, may be because I'm using rails 3, I can't tell, but I do not see any changes when I add :name => 'preview_button' with the submit button. Can anyone suggest me a solution?
Adding Paulo's answer, it might be better to route to different actions based on commit param.
We solved using advanced constraints in rails.
The idea is to have the same path (and hence the same named route & action) but with constraints routing to different actions.
resources :plan do
post :save, constraints: CommitParamRouting.new("Propose"), action: :propose
post :save, constraints: CommitParamRouting.new("Finalize"), action: :finalize
end
CommitParamRouting is a simple class that has a method matches? which returns true if the commit param matches the given instance attr. value.
This available as a gem commit_param_matching.
You need to add two submit buttons with different names
<%= submit_tag 'Submit', :name => 'submit_button' %>
<%= submit_tag 'Preview', :name => 'preview_button' %>
then in your controller you need to check the parameters:
if params[:commit] == "Submit"
# do great stuff
end
if params[:preview] == "Preview"
# preview great stuff
end

Resources