How to trigger different actions based on submit button in rails - ruby-on-rails

I have a form with a list of stuff, and an action already in place to update items.
I want to have another button, which when clicked triggers a different action to remove the selected items.
= form_for #new_item,:url => {:controller => "item_lists",:action => "update_list" } do |f|
- #items.each do |it|
%input{:type=>"hidden",:name=>"item_list[#{it.id}]position",:value=>it.position, :class=>'position'}
%textarea{:name=>"item_list[#{it.id}]field1"}
=it.field1
%textarea{:name=>"item_list[#{it.id}]field2"}
=it.field2
%input{:type=>'checkbox', :name=>'selected_items[]', :value=>it.id}
=(it.valid?) ? "" : it.errors.full_messages
%input{:type=>"submit", :value=>"Save changes", :name=>'save'}
%input{:type=>"submit", :value=>"Remove selected", :name=>'delete'}
This question seems to indicate I should inspect params in my action to figure out what was clicked. But that feels messy, my controller could quickly degenerate into a mass of ifs when I add more actions.
Is there a more elegant way to do this, i.e. get it to just route to the correct method?
Thanks for any help...

This doesn't really gel with REST. In REST and Rails you're typically going to have one action per endpoint, not decide on the endpoint based on some criteria in the request.
That being said, you can filter actions based on the submit button by checking the name of the button pressed. See this SO question.
I'd argue though that this is only appropriate if your form is doing slightly different things, like perhaps a submit button that updates in place versus a submit button that redirects somewhere afterward, e.g. "Update" versus "Update and Continue" (contrived, but you get what I mean).
Addressing your concern in the comments, your method wouldn't have to devolve into a long sequence of ifs. You could just write some code to determine which method to call based on the name of the submit button. A simple implementation might be:
# your form action
def update_list
send update_list_action
end
protected
def update_list_action
# just return the first action name found in the params
action = %w(save delete).detect {|action| params[action] }
"update_list_#{action}"
end
def update_list_save
# handle save
end
def update_list_delete
# handle delete
end

I would suggest you to add a dropdown menue with the option "delete", "update",... and add some jQuery code that observes the selected item and changes the action of your form depending on the value because you shouldnt use one action to update and delete objects! There should be one action for updating and one for deleting!

Related

Rails - go to index, but also submit form with the same click

I have a redirect that works fine for getting me to my index. It is below:
redirect_to action: :index, filters: { recent_price_change: "#{1.week.ago.strftime('%m/%d/%Y')} - #{Date.today.strftime('%m/%d/%Y')}" }, hide_filters: true
Thing is, I am populating a filter on click. On that same page is a button that is tagged with the submit action.
here is the excerpt of the HAML (it's sorta kinda like ERB... don't worry much on it, just note how it is type: submit) file containing it:
%button.btn.btn-primary.btn-sm{ type: 'submit', style: 'background-color: #a38672;' }
%span.glyphicon.glyphicon-play
Update List
Clicking the button above submits the form, which applies the filter put some results in a table on the screen.
This was/is all fine and good - but there is a new requirement where I want that submit button to be "automatically clicked" on redirect. AKA, I don't want to have to click this button manually ... instead, as part of the redirect, the form needs to be submitted. So when the page loads, it is AS IF I had just clicked the button - the data is populated, etc. Do you all know if this is possible/how I would do it? I have dug through documentation for the redirect_to action hoping I would find some help there - but no dice as of yet.
UPDATE:
I added:
document.querySelector('.button.btn.btn-primary.btn-sm').click();
Onto the haml at the end of the file like so:
:javascript
document.querySelector('.button.btn.btn-primary.btn-sm').click();
It does submit the form on load... but turns out my index page must be getting re-rendered over and over... it repeatedly clicks the button. It doesn't do it just once. Was wondering if there is some way to bind this just to onload.
UPDATE 2:
So I wrote
firstSubmit = function() {
document.querySelector('.button.btn.btn-primary.btn-sm').click();
};
$(document).on('page:load', firstSubmit);
The problem is... seems my page is reloading itself repeatedly. So then, it spams my button over and over, leading to total flux of the page. Anyway to limit this without getting really janky (global var? - I'd hate to)?
You may add flag variable to form (assume form action redirects to index):
= hidden_field_tag :do_not_redirect
%button.btn.btn-primary.btn-sm{ type: 'submit', style: 'background-color: #a38672;' }
%span.glyphicon.glyphicon-play
Update List
Then modify javascript:
- unless params[:do_not_redirect]
:javascript
$(document).on('page:load', function () {
$('#do_not_redirect').val(1);
$('.button.btn.btn-primary.btn-sm').click();
});
And controller:
redirect_to action: :index, filters: {
recent_price_change: "#{1.week.ago.strftime('%m/%d/%Y')} - #{Date.today.strftime('%m/%d/%Y')}"
}, hide_filters: true, do_not_redirect: params[:do_not_redirect].presence
I think what you want to do is utilize a callback. Not sure if you're familiar, but a callback is just a routine that you run after some other action has taken place. In this case, after you click the submit button (which would most likely be an after_save or after_commit callback), you want to redirect to the index. Callbacks are generally found at the top of the controller in a case like this, something like the following:
class SomeController < ApplicationController
after_save :go_to_index
Then, after your normal controller actions:
private
def go_to_index
// Redirect code
end
I think this is what you're looking for, you can find the callback api info here: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html.

submit in rails without formhelpers

I'm new to rails and still learning the ropes via railstutorial, but the book does all changes to the db via form submissions (http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html). But how would I go about submitting (updating the db) without this, lets say I want to add some score which is computed on page ,(for example via a js counter - for simplicity lets just say its a constant 10) and my db consists of a column called score. Then after pressing a submit button how would I go about updating the db?
Thanks
Two trivial ways:
Use a form
Use a URL parameter with a link
The processing (other than GET v. POST) on the Rails side is identical--it's just a parameter.
If you're using JavaScript, there's not necessarily a reason to not use a form, though, since you could just update a form input element and submit normally.
It is quite simple, actually.
The constant 10 is submitted from the view. The submit needs to point to the controller action that will handle this request. So the submit button should build the url using :controller, :action, :id parameters. In a form, this is handled in the form_for declaration. You will deal with in the button_tag declaration.
The routes should be configured so that this message can reach the controller/ action.
The constant 10 is transported in the params hash. If the field is my_counter, then look for params[:my_counter]. If the form had been for a model, say tweets, then it might be in params[:tweet][:my_counter].
In the controller action, possibly update, you will first fetch the record to change with something like #score = Score.find(:params[:id]). This params[:id] is also coming from the view with the submit. Change the counter here, and save.
def update
#score = Score.find(:params[:id])
#score.counter = params[:my_counter]
#score.save
redirect_to :action => :index # or wherever
end
Good luck.

Rails - User Pressing 'Back' after object creation, Creating Duplicates

I'm having a problem where when a user fills out my evaluation form, click "Create", then click the browser's back button, make some edits, and click "Create" again, it's creating duplicate Evaluations.
What is the best way to prevent something like this happening.
Only ONE evaluation should exist for each survey_criterion on creation. I don't want the user to lose any data they enter after hitting the back button, filling out the form with new stuff, and clicking "Create" again.
UPDATE
routes.rb
resources :survey_criteria do
resources :groups do
resources :evaluations
end
end
survey_criterion.rb
has_many :evaluations
evaluation.rb
belongs_to :survey_criterion
belongs_to :group
There are more complicated associations, but the answer I'm looking for is more, "how does one handle it when users press the 'Back' button, modify the form, then click Create again".
I want it to update the one that was automatically created I think in this instance, and not throw an error to the user. I know I could add a validation that would error out, but I want this to be invisible to the user I think.
Thoughts?
The simplest solution, would be to change the create action, which should work like this pseudocode:
def create
# ...
if evaluation_exists?
update_evaluation(params[:evaluation])
else
create_evaluation(params[:evaluation])
end
# ...
end
As for Your question "how does one handle it when users press the 'Back' button, modify the form, then click Create again", then I use some random token (a short string) placed as a hidden field in the form.
When the create-request comes, I check whether this token is already stored in the session. If it is not, then I create the object, and add that token to the list of used ones. If the token is already present in the session, I know that user has just resubmitted the form, and I can act accordingly. Usually I ask him whether another object should be created. In the session I store usually not more that 3-5 tokens.
It looks like this (yes, that's just an illustration):
def create
token = params[:token]
session[:tokens] ||= []
if session[:tokens].include? token
render_the_form_again( "You have already created the object. Want another?" )
else
create_the_object
session[:tokens] << token
end
# ...
end
In your Evaluation model, add this line :
validates_uniqueness_of :survey_criterion_id
This is assuming that SurveyCriterion holds the foreign key that associates with your Evaluation.
You can also do 2 things :
Prevent the browser cache.
Disable the Create button with :disable_with => "Processing" option.
It is discussed here too: https://stackoverflow.com/a/12112007/553371
A less elegant way but more generic way to do this is to use history.pushState. On the page after create:
$(function(){
if(history.pushState){
window.onpopstate = function(event){
if(window.history.state && window.history.state.previousStep){
window.location = window.history.state.previousStep;
}
}
window.history.replaceState({ previousStep: '#{edit_resource_url(resource)}'}, document.title, window.location);
window.history.pushState({}, document.title, window.location);
}
})
This example uses HTML5's History API. A similar thing can be done with fallback using the history.js project

Best way to create preview functionality in Rails

I'm looking to implement preview functionality in my posts scaffold. All I need to do is allow a user to enter information in the new view (/posts/new) and then replace the submit button with a preview button.
Once the preview button is clicked, the user is routed to the preview page (probably /posts/new/preview). If the user wants to make a change they would click 'go back' or if they are happy with the post they can then submit the post.
I found this article (http://eyedeal.team88.org/node/105) but it seems dated. Any ideas on what the best approach for this would be?
Many thanks,
Tony
On submit from create page, in the new action, build the object but do not save it to the database. Then render the object in its show view with a flag set in the new action to display a submit button. In your show view, always have a form with all the attributes of the object to be saved to db in hidden input fields or in display:none's. When the flag is set, you show the submit button. On submit, you go to the new_to_db action which saves the object to the db.
The link you have posted is a way, but I prefer to save object and set a boolean flag, let's say public to false (:default => false defined in migration).
Then what you basically do is actually create the post and redirect to show action, where you have
edit button (render edit action),
post button (custom action to set public flag to true)
and cancel button (which actually deletes the post)
and maybe continue later button, which keeps the post and redirects to any other page, so the user can come back later and finish editing it.
When you need to show all posts, define a named_scope :visible, :conditions => ['posts.public = ?', true] and call Post.visible instead of Post.all in index and similar actions. You could also define a default_scope with conditions ['posts.public = ?', false], but bare in mind that if you want to find posts that are not visible, you will have to use #without_scope.
This way is better than the one in your link, because user can always come back later and finish editing the post and the publish it. However you will store more objects in DB and have to deal with invisible posts (don't show them by default, etc.)

Is there any harm in using a typical GET action for a PUT? (RESTfully speaking)

I have an action that doesn't require a form. So it really only needs the one 'edit' method instead of the RESTful 'edit' --> 'update'. Is there any reason not to do this or a better way?
def edit
#Do a POST(PUT)
end
The harm is that a user could easily navigate to that url and perform a potentially destructive action.
/noform/edit #URL typed by user => Action Performed
/noform/update #URL typed by user => Error is thrown, No Action Performed
A normal browsing experience generates GET requests to the server. The assumption is, any page you can easily navigate to (or type into your address bar) will not perform any data changing functions.
A POST request, generated via a form submission or a AJAX request expects the result that data is changed on the server.
Similarly the two rails "faked" versions of PUT and DELETE also are not actions you could simply navigate to using a browser.
The solution
The solution is to have only the update action and where you originally would have linked to edit use something like the following:
button_to "Add new tracker", noform_path, :method => :put
If there is any type of error, you may still need an edit path to show the user so they can correct something. But from what you have described, a single update action should do the trick.
Gets should always be idempotent -- that is they should not perform any action that will alter the state of the application, database, etc.
Just as an aside -- in true RESTful form an edit would be performed by an HTTP Update action, but Rails simulates this with a post and a hidden value on the form, since browsers don't have HTTP Updates.
It's still not clear to me why you need an update without an input field. Perhaps a little more detail would be helpful.

Resources