How to create one form with many possible actions in Rails? - ruby-on-rails

I want to create one form with 2 buttons in rails. Both forms operate on the same data in different ways, and I would prefer to keep the associated functionality in two different methods. Previously I've redirected to the appropriate method after doing a string comparision on params[:commit], but my gut says there's a better approach. Suggestions?

Two different submit buttons that send the form to two different actions:
<%= submit_tag('Insert', :onclick=>"document.myForm.action = 'insert';") %>
<%= submit_tag('Update', :onclick=>"document.myForm.action = 'update';") %>
Instead of "myForm" you need to put whatever is in the "name" property of your tag.
You can set that property in your default form:for tag like this:
<%= form_for(#something, :html => {:name => "myForm"}) do |f| %>

Without using JavaScript, your only solution is what you mention: checking which button was clicked by looking at the POST data in the controller. This is simply due to the nature of the HTML form element. It cannot have more than one value for its action attribute.
If you're not worried about what will happen when JavaScript isn't available, then you can write some script to change the action attribute when one of the submit buttons is clicked, prior to actually submitting the form. In the case of an ajax request, it could simply submit to the correct URL directly without altering attributes on the form.

I also used the params[:commit] method on a form already. Using the I18n helpers makes this a bit less fragile as you can use the same lookup in the view and controller, so you don't encounter the problem that the string changes a bit.
Besides that I can only think of using JavaScript to handle the clicks on the buttons and then send the form data to different Rails actions (Maybe you can change the HTML action attribute of the form with JavaScript before you submit the form).

If you're using prototype.js, you can use Form.serialize() to grab your data from your form and from there use the different buttons to post to different actions.

Related

How to process text input data before form submit ruby on rails

I'm using rails form helper to generate form and retrieve data from users.
like this
<div class="controls">
<%= f.text_field :duration, class: "input-large" %>
</div>
however,I need this input, before its store to database 'duration' column,multiply by 10.
I have search about this and try implement a method,create a function,which multiply the value,and call it under before_save
before_save :multiply_ten, on: [ :create,:update ]
problem is,i'm not only updating this column using web page form,and also by http request send by device.by implementing this I multiply all value by 10,But I only want the web form input part multiply 10.
is there a way to do it in form css or have other tricks?
thanks
There a multiple ways to achieve what you're looking for. Since you only want to multiply duration by 10 in the case of a particular web form, I would suggest doing the multiplication at the controller level, or even at the view level, instead of at the model level.
You could have your view modify the http params before the request is made via javascript.
Or, if you'd prefer to do it at the controller level, you need a way to communicate to the controller that the submitting form is in fact the one requiring the multiplication. Example:
First, in your view, add an extra parameter via hidden input indicating that the form needs a multiplication of 10:
<%= hidden_field_tag :special_form, true %>
Then, in your controller, before you assign attributes to a new object to be saved, modify the params if params[:special_form] exists:
params[:duration] = params[:duration].to_f * 10 if params[:special_form].present?
Hope this helps!

How can we circumvent these remote forms drawback?

In an effort to have everything translateable in our website ( including the error messages for the validations ), we switched almost all of our forms to remote forms. While this helps with the ability to translate error messages, we have encountered other problems, like:
if the user clicks on the submit button multiple times, the action gets called multiple times. If we have a remote form for creating a new record in the database, and assuming that the user's data is valid, each click will add a new object ( with the exact same contents ). Is there any way of making sure that such things cannot happen?
Is there somewhere I could read about remote forms best practices? How could I handle the multiple clicks problem? Is switching all the forms to remote forms a very big mistake?
There is a rails 3 option called :disable_with. Put this on input elements to disable and re-label them while a remote form is being submitted. It adds a data-disable-with tag to those inputs and rails.js can select and bind this functionality.
submit_tag "Complete sale", :disable_with => "Please wait..."
More info can be found here
Easy, and you can achieve that in many ways depending your preferences:
Post the form manually simply using an ajax request and while you wait for the response disable/hide (or whatever you need) the form to ensure the user can't keep doing posts as crazy. Once you get the response from the server, again you can allow the user to post again (cleaning the form first), or show something else or redirect it to another page or again whatever you need.
Use link_to :remote=>true to submit the form and add a callback function to handle the response and also to disable/hide (or whatever you need) the form when it's submitted
Add a js listener to the form to detect when it's submitted and then disable/hide/whatever the form
As you see, there are lots of different ways to achieve what you need.
EDIT: If you need info about binding or handling a form submit from js here you'll find very easy and interesting examples that may help you to do what I suggested you! jQuery Submit
I have remote forms extensively myself, and in most cases I would avoid them. But sometimes your layout or UX demands for on-the-fly drop-down forms, without reloading or refreshing the complete page.
So, let me tackle this in steps.
1. Preventing Normal form double-post
Even with a normal form, a user could double-click your button, or click multiple times, if the user does not get a clear indication that the click has been registered and the action has started.
There are a lot of ways (e.g. javascript) to make this visible, but the easiest in rails is this:
= f.button :submit, :disable_with => "Please wait..."
This will disable the button after the first click, clearly indicating the click has been registered and the action has started.
2. Handling the remote form
For a remote form it is not that much different, but the difference most likely is: what happens afterward ?
With a remote form you have a few options:
In case of error: you update the form with the errors.
you leave the form open, allowing users to keep on entering the data (I think this is your case?)
you redirect the users to some place.
Let me handle those cases. Please understand that those three cases are completely standard when doing a normal form. But not when doing a remote call.
2.1 In case of error
For a remote form to update correctly, you have to do a bit more magic. Not a lot, but a bit.
When using haml, you would have a view called edit.js.haml which would look something like
:plain
$('#your-form-id').replaceWith('#{j render(:partial => '_form') }');
What this does: replace the complete haml with only the form. You will have to structure your views accordingly, to make this work. That is not hard, but just necessary.
2.2 Clearing the form
You have two options:
* re-render the form completely, as with the errors. Only make sure you render the form from a new element, not the just posted one!!
* just send the following javascript instead:
$('#your-form-id').reset();
This will blank the form, and normally, that would effectively render any following clicking useless (some client validation could block posting until some fields are filled in).
2.3 Redirecting
Since you are using a remote form, you can't just redirect. This has to happen client-side, so that is a tad more complicated.
Using haml again this would be something like
:plain
document.location.href = '#{#redirect_uri}';
Conclusion
To prevent double (triple, quadruple, more) posts using remote forms you will have to
disable the button after first click (use :disable_with)
clear the form after succesful submission (reset the form or render with a new element)
Hope this helps.
The simplest solution would be to generate a token for each form. Then your create action could make sure it hasn't been used yet and determine whether the record should be created.
Here's how I would go about writing this feature. Note that I haven't actually tested this, but the concept should work.
1.
Inside the new action create a hash to identify the form request.
def new
#product = Product.new
#form_token = session["form_token"] = SecureRandom.hex(15)
end
2.
Add a hidden field to the form that stores the form token. This will be captured in the create action to make sure the form hasn't been submitted before.
<%= hidden_field_tag :form_token, #form_token %>
3.
In the create action you can make sure the form token matches between the session and params variables. This will give you a chance to see if this is the first or second submission.
def create
# delete the form token if it matches
if session[:form_token] == params[:form_token]
session[:form_token] = nil
else
# if it doesn't match then check if a record was created recently
product = Product.where('created_at > ?', 3.minutes.ago).where(title: params[:product][:title]).last
# if the product exists then show it
# or just return because it is a remote form
redirect_to product and return if product.present?
end
# normal create action here ...
end
Update: What I have described above has a name, it is called a Synchronizer (or Déjà vu) Token. As described in this article, is a proper method to prevent a double submit.
This strategy addresses the problem of duplicate form submissions. A synchronizer token is set in a user's session and included with each form returned to the client. When that form is submitted, the synchronizer token in the form is compared to the synchronizer token in the session. The tokens should match the first time the form is submitted. If the tokens do not match, then the form submission may be disallowed and an error returned to the user. Token mismatch may occur when the user submits a form, then clicks the Back button in the browser and attempts to resubmit the same form.
On the other hand, if the two token values match, then we are confident that the flow of control is exactly as expected. At this point, the token value in the session is modified to a new value and the form submission is accepted.
I hate to say it, but it sounds like you've come up with a cure that's worse than the disease.
Why not use i18n for translations? That certainly would be the 'Rails way'...
If you must continue down this route, you are going to have to start using Javascript. Remote forms are usually for small 'AJAXy things' like votes or comments. Creating whole objects without leaving the page is useful for when people might want to create lots of them in a row (the exact problem you're trying to solve).
As soon as you start using AJAX, you have to deal with the fact that you'll have to get into doing some JS. It's client-side stuff and therefore not Rail's speciality.
If you feel that you've gone so far down this road that you can't turn back, I would suggest that the AJAX response should at least reset the form. This would then stop people creating the same thing more than once by mistake.
From a UI/UX point of view, it should also bring up a flash message letting users know that they successfully created the object.
So in summary - if you can afford the time, git reset and start using i18n, if you can't, make the ajax callback reset the form and set a flash message.
Edit: it just occurred to me that you could even get the AJAX to redirect the page for you (but you'd have to handle the flash messages yourself). However, using a remote form that then redirects via javascript is FUGLY...
I've had similar issues with using a popup on mouseover, and not wanting to queue several requests. To get more control, you might find it easier to use javascript/coffeescript directly instead of UJS (as I did).
The way I resolved it was assigning the Ajax call to a variable and checking if the variable was assigned. In my situation, I'd abort the ajax call, but you would probably want to return from the function and set the variable to null once the ajax call is completed successfully.
This coffeescript example is from my popup which uses a "GET", but in theory it should be the same for a "POST" or "PUT".
e.g.
jQuery ->
ajaxCall = null
$("#popupContent").html " "
$("#popup").live "mouseover", ->
if ajaxCall
return
ajaxCall = $.ajax(
type: "GET"
url: "/whatever_url"
beforeSend: ->
$("#popupContent").prepend "<p class=\"loading-text\">Loading..please wait...</p>"
success: (data) ->
$("#popupContent").empty().append(data)
complete: ->
$"(.loading-text").remove()
ajaxCall = null
)
I've left out my mouseout, and timer handling for brevity.
You can try something like that for ajax requests.
Set block variable true for ajax requests
before_filter :xhr_blocker
def xhr_blocker
if request.xhr?
if session[:xhr_blocker]
respond_to do |format|
format.json, status: :unprocessable_entity
end
else
session[:xhr_blocker] = true
end
end
end
Clear xhr_blocker variable with an after filter method
after_filter :clear_xhr_blocker
def clear_xhr_blocker
session[:xhr_blocker] = nil
end
I would bind to ajax:complete, (or ajax:success and ajax:error) to redirect or update the DOM to remove/change the form as necessary when the request is complete.

Rails - Reload action with new value in params

I have the following relation in my model:
A user can have several consumers
According to that, I call several actions from different controllers that depend on that consumer_id in the URL. So, I do stuff like:
/:consumer_id/products/all
/:consumer_id/locals/all?params...
I want to be able to given that I am in a particular view, let's say /3/products/all, be able to refresh, redirect or whatever is the best to /4/products/all, with a form that shows the different consumers attached to that user.
I know how to display the form, but I fail to see which action should I put given that I want to load the current controller, current action and the current params, except that I want to change the params[:consumer_id] and the session[:consumer_id] to the one chosen by the user in the form of the view.
What's the best or appropriate way of doing so?
This is a link to the same page with the same parameters and one additional parameter:
<%= link_to "Show more results", url_for(params.merge(:per => 100)) %>
If you want to modify a parameter rather than add a new one, you can modify the params hash just as you would modify any hash, and use url_for again.
Inside the view you can access both the current controller using controller_name and the current action using action_name
Your question is not clear. You want to choose consumer_id from your form, then use that consumer id inside the same page or for links to other pages?
If you want to use the consumer id for the current page, you need to use javascript or JQuery to update all attributes of the current page.
If you want to use that consumer id for links,
you may also use JQuery to update all links on your page
Or, you can submit the form to the server with new consumer_id.

How Does Rails 3's "data-method='delete'" Degrade Gracefully?

Rails 3 does some cool stuff to make Javascript unobtrusive, so they've done things like this:
= link_to "Logout", user_session_path, :method => :delete
..converts to
Logout
But it just occurred to me.. When I turn off javascript the method isn't DELETE anymore, it's GET as expected. So are there plans to, or is there some way to, allow these data- attributes to degrade gracefully, so that link still is a DELETE request?
The change they made in Rails 3 with these data- attributes wasn't about graceful degradation, it was about unobtrusive JavaScript.
In Rails 2, specifying :method => :delete on a link would generate a whole bunch of inline JavaScript that would create a form with a hidden input and then submit the form. That was the same as it is now: turn off JavaScript and it defaults to a GET request. As such, supporting the case of no JavaScript is the same as it was before.
One option is to use a form/button instead of a link so you can include the method as a hidden field, much like the Rails 2 JavaScript does. Another option is to have the GET version take you to an intermediate page which in turn has the form/button.
The benefit of the new approach is that it's unobtrusive. The JavaScript for changing the HTTP verb exists in an external file and uses the data- attributes to determine which elements it should be attached to.
Rather than using the link_to method -- which would require you use JavaScript to ensure that the HTTP method is DELETE -- use the button_to method, which will create a form with a hidden input element which tells Rails to treat the HTTP method as DELETE rather than POST. If necessary, you can then use CSS to style the button in the form so that it looks like a link.
The only chance you have is define a form. A link can't be a POST with _method="delete" without Javascript or by form.
It is not possible without javascript.
I make a small jQuery plugin for converting data-method link attribute to pseudo hidden forms (used in laravel project for example).
If you want to use it : https://github.com/Ifnot/RestfulizerJs

gmail-like popup checkbox list in rails?

I need to build something like what gmail does for it's labels... It has a button that when pressed pops up a scrolling list displaying the labels with checkboxes for selection.
I'd like to hear about approaches to do the popup and how to place it right under the button.
Also, I'd like to be able to observe the checkbox select/deselect events and take action, so advice on that part would also be appreciated... otherwise, I guess I'll have to put a form with a submit button and handle the new selections when the user submits.
If the checkbox list is static, you can do all this directly in the rendered action. Otherwise, two approaches are possible:
Use button_to_remote to retrieve an action displaying the popup and also serving the necessary js;
Use button_to_function to retrieve some XML or json (at your option) from an action, with the necessary labels and values for checkboxes, then render the popup.
The first may be easier to do if you're not familiar with all this, while the second is way more efficient, as only data is passed through the asynchronous call, and not markup nor javascript.
About your last question, if (un)checking the checkbox must result in a server side action, prototype_helper provides a convenient observe_field function, to be used like this:
<%= check_box "foo", "bar" %>
<%= observe_field "foo_bar", :url => {:action => :some_action, :controller => :some_controller} %>
If the (un)checking can be managed on client side, you can simply use:
<%= check_box "foo", "bar", { :onclick => "someFunctionToDoWhatINeed(someArg);"} %>
Just two notes:
JavascriptHelper and PrototypeHelper are just this, helpers: they allow you to do some things with a very simple syntax and are great, as long as they are helping; when they are no more, feel free to drop them and go for plain javascript.
I've used prototype for a while, but then I fell in love with jquery; you may want to take a look at it.
Please edit your question or comment my answer if I didn't understand your question and/or was unhelpful.

Resources