I'm trying to determine the best way to handle form submissions and routing.
I have the standard _form.html.erb, as well as new.html.erb and edit.html.erb - each renders form, subscription = #subscription, which is declared in the new and edit controller action.
The model is subscription, and this is my form element in _form.html.erb (it's in the checkout namespace) -
<%= form_for([:checkout, subscription]) do |f| %>
/checkout/subscriptions/new is the URL for a new subscription. When you submit the form, it posts to /subscriptions.
The problem is that if there are errors, and you refresh the browser (I know users will do this for one reason or another), you get an error message because there is no view for /subscriptions/index (I don't need one).
Same applies for editing. If you go to (as an example) /checkout/subscriptions/14/edit, everything's great. But if you submit the form, it posts to /subscriptions - same problem as with new - if you refresh the page, you get the no view error. This is the way Rails handles it with a new scaffold.
So my questions are:
what's the url I should use in the form_for tag?
what should I do for routing?
and how can I avoid this error when refreshing the page?
This is how the rails version of REST works (and should work). The key here is the HTTP method.
Reloading the page creates a GET request. While you controller should create resources with POST and update with PATCH/PUT.
GET requests should be idempotent (not create, alter or delete resources) so a GET request for /subscriptions or /subscriptions/:id is very different.
What can you do:
Use the window.beforeunload event in javascript to warn the user that they will lose data.
Route the GET /subscriptions path to subscriptions#new.
Route the GET /subscriptions/:id path to subscriptions#edit.
Related
I am new to writing rails applications. I am using Rails 4.2.0 with Ruby 2.0.0p598.
I have modified the cascading select implementation found here and used it to create new Products (has model and main controller) linked to Productgroups and Productsubgroups (both have their models, but no controllers) as the cascade levels. The implementation employs some javascript which upon a change to the first dropdown select, the second dropdown is cleared of data, and then an AJAX call is made to a specific URL to update the tags for the productsubgroups dropdown using a partial (_productsubgroup.html.erb).
To do this, the following route set up in routes.rb
get '/update_productsubgroups' => 'products#update_productsubgroups'
so that i can map to the appropriate controller and action. This is all quite fine. I can see the AJAX request in the development.log as where it is querying the database for productgroup #2 (ignore the trailing "&_1422575676984"):
Started GET "/products/update_productsubgroups?productgroup_id=2&_=1422575676984"
The problem, occurs when I use the same content from new.html.erb within the context of editing a product in edit.html.erb. Based on the fact that the routes.rb uses the "resouces :products" directive, I end up with a url for editing products in the form /product/:id/edit (e.g. /product/2/edit to edit the product with ID 2). When I try to use my cascading dropdowns, everytime I make a change to the first selection, I don't get a change in the second dropdown. I can see the request going to the development.log file as:
Started GET "/products/1/update_productsubgroups?productgroup_id=2&_=1422578544393"
and the error that comes up immediately after the request is:
ActionController::RoutingError (No route matches [GET] "/products/1/update_productsubgroups"):
Q1 - Why does the partial just tack on to the existing URL rather than simply make a pure request to /products/update_productsubgroups as is the case when I create a product from the product controller's 'new' action?
Q2 - Is there any way for me to create a rule in the routes.rb to map things correctly?
Q3 - When I try to create a new route as
get '/products/:id/update_productsubgroups', as 'products#update_productsubgroups'
I get an error in the webserver log as:
ArgumentError ('products/:id' is not a supported controller name
I have dug around and I am not certain how to interpret this. Can anyone help explain this so that the last couple of hours can prove a useful learning experience? Unfortunately, most pages either discuss routing for Rails 3 or just refer to the 'Routing from the outside in' page and that seems to confuse me even more because I'm specifying things as expected, namely :controller/:id/:action.
DOH!
I had a relative path specified for my AJAX call! I looked everywhere except the coffeescript file.
$.ajax 'update_productsubgroups',
should be
$.ajax '/update_productsubgroups',
A copy action and copy.html.erb are defined in our rails 3.2 app. This copy action is to copy from the current record, allowing user modify slightly and submit for creation just like new. Here is the header of the copy.html.erb:
<%= form_for #engine_config, :as => :engine_config, :url => engine_configs_path do |f| %>
After clicking save, it hits create in controller and this is what we wanted.
Our question here is what the engine_configs_path stands for? Usually engine_configs_path is for index. Here the form is for create and is not index. What's the reasoning of this index path on create form?
When you do bundle exec rake routes you will see something similar to this;
engine_configs GET /engine_configs(.:format) engine_configs#index
POST /engine_configs(.:format) engine_configs#create
Which means engine_configs_path works with both GET for index controller action and POST for create controller action.
Therefore engine_configs_path refers to the url the form is posted to in the controller.
Form
You generally don't need to define the url argument in your form - Rails' form_for helper takes an initialized class object & extracts the required data for it automatically:
From the docs:
Typically, a form designed to create or update a resource reflects the
identity of the resource in several ways:
(i) the url that the form is
sent to (the form element's action attribute) should result in a
request being routed to the appropriate controller action (with the
appropriate :id parameter in the case of an existing resource)
(ii)
input fields should be named in such a way that in the controller
their values appear in the appropriate places within the params hash,
(iii) for an existing record, when the form is initially
displayed, input fields corresponding to attributes of the resource
should show the current values of those attributes.
In Rails, this is usually achieved by creating the form using form_for
and a number of related helper methods. form_for generates an
appropriate form tag and yields a form builder object that knows the
model the form is about. Input fields are created by calling methods
defined on the form builder, which means they are able to generate the
appropriate names and default values corresponding to the model
attributes, as well as convenient IDs, etc. Conventions in the
generated field names allow controllers to receive form data nicely
structured in params with no effort on your side.
--
Route
Your specific question is regarding the route your form_for method will use
As per w3c standards, HTML forms are defaulted to POST data. This means whenever you use a form_for helper in Rails, it will automatically use the POST variant of your routes
As described by Acacia, if you're using resourceful routes, you'll get a series of routes like this:
This means you can call the same route (engine_configs_path) with the HTTP Verb of POST to send to a specific controller#action, which is what's happening for you
The behaviour of engine_configs_path depends on the HTTP verb. That is, an HTTP GET to engine_configs_path will show the index; an HTTP POST to engine_configs_path will create a new one. form_for can check whether #engine_config exists in the DB or not via #engine_config.new_record?, so it knows to do a POST instead of a PUT.
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.
I have a search resource, the user can perform searches by filling out a form and submitting it, the create action is called, the Search is saved, the show action is called, and the results are displayed. This all happens with the default POST, and all works fine.
The user may want to save his search in the saved_search table (i don't use the Search table for this purpose as this table stores all searches for the purpose of compiling statistics, and gets cleared on a regular basis). Once the Search is saved, it can be re-run by clicking a hyperlink, this is where i start to get problems.
I see no way of getting my hyperlink to run the create action of Search, with a POST request, and the necessary data.
I then decided to try to get both form submission and the hyperlink to perform a search using a GET request, i was unable to get form_for to run my Search create action using a GET request, it always seems to get routed to my index action.
Can someone suggest a good restful solution to this problem please.
Many thanks
I'm not quite sure what you're trying to do here. If you want to have a form submit with a GET request, you can override the HTML attribute on the form_for helper:
<% form_for blarg, :html => { :method => 'get' } %>
blabla
<% end %>
Rails also supports a way of "faking" the HTTP method by using a "magic" parameter (called "_method"), which makes Rails behave as if it had seen the HTTP method in that parameter.
If you send the form as "get", you must make sure that none such parameter is set. If you wanted to let a hyperlink send a "POST", tweaking this would be the way (a browser will not send a real POST on a click on a link)
Jon,
If I understood right, if the search is already saved, you could just make a get on the resource of the saved search like you did the first time and use the show action to display the result.
Anyway, if you still wants to do a post with a link, the helper method link_to does it for you. Check it out:
http://www.51773.com/tools/api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#M001597
With a :method => :post option it will create a hidden form and post your data.
Hope it helps.
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.