How to attach an action call to a submit button in Rails? - ruby-on-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

Related

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.

One select_tag and multiple button_to submit buttons

I have one select_tag and multiple button_to buttons on the same page. I'm looking to use the parameters from the select_tag for several of the buttons, is there a non-form way to do this?
The reason I don't want to use a form is two fold:
1. the number of buttons is dynamic
2. the positioning of the buttons do not follow the form structure (object at the top and submit at the bottom)
-here is one of the create methods that are linked to one of the buttons
def create
params[:options].each do |x|
#connector = Connector.find_or_create_by_options_id_and_follow_id(x.id, current_user.follow(#product).id)
#connector.save
end
end
I checked and this params[:options] is always nil no matter what is selected by me when I test
<%= select_tag :options, options_for_select(#current_user_options.map {|p| [p.name, p.id] }), {:multiple => true} %>
button_to method actually creates a form which has a submit button, with the form action being the url which you had mentioned in button_to. So when you hit the submit button, obviously the options param will be nil, since the button_to form has no data in it
You can use a form itself, and form does not mandate, having an object at the top and submit button below. Submit button just submits the form irrespective of its position in the form
It sounds like you actually need a second field in your form that's a select field or radio buttons to choose your action. Then you have one button that submits the form, and in your controller you can then parse that new action field and decide where to redirect the user.

Best practices to manage action button status in Rails

I'm new to Rails and web development in general. I'm trying to better understand what are the options to render different buttons on my views depending on which user is visiting a page. I'm working with Rails 3.2.
2 specific cases for me:
On my movie page, I want to display a Bookmark button. The button should say "add bookmark" and not be checked if the user is not logged in or if the movie is not in user's bookmark list. The button should say "remove bookmark" and be checked (with an "active" css class) if the user added the movie in her bookmarks.
If user search for "action movie", the view renders a list of movies. Each item of the list should display an "add bookmark" / "remove bookmark" button depending on whether the movie is in the user's bookmark list.
What are best practices to manage my buttons text, actions, and display depending on my user status? Where does the code go (view, javascript...), and what key methods I would need to implement?
Well, best practice is to using something like Draper: https://github.com/jcasimir/draper which handles the responsibility of presenting the correct content based on a set of conditions.
This would work however, but should probalby be refactored into a Draper Decorator:
<% if user.has_bookmarked(#movie) %>
<%= link_to 'Remove bookmark', remove_bookmark_path%>
<% else %>
<%= link_to 'Add bookmark', add_boookmark_path %>
but again, in the decorator you could just do it once
def display_correct_bookmark_link_for_movie(movie)
if current_user.has_bookmarked(movie)
h.link_to 'Remove bookmark', remove_bookmark_path
else
h.link_to 'Add bookmark', add_boookmark_path
end
end
and then in your view you could just call:
<%= display_correct_bookmark_link_for_movie(#movie) %>
Otherwise, you'll be repeating that logic all over your views which is not good.
Obviously some of that is psuedo-code but I think you get my drift.

Rails: Multi-submit buttons in one Form

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');
}

Resources