I'm failing to understand the correlation between routes, controllers, and views (and how they connect to each other).
So, I've got my controller with the index,show,new,create,destroy methods. And the corresponding
GET /entries(.:format) entries#index
POST /entries(.:format) entries#create
GET /entries/new(.:format) entries#new
GET /entries/:id/edit(.:format) entries#edit
GET /entries/:id(.:format) entries#show
PUT /entries/:id(.:format) entries#update
DELETE /entries/:id(.:format) entries#destroy
How come if I add a new method vote_up or vote_down, for example, and the views with matching action names, it doesn't work.
1) What is the proper way to add new actions and connect them to views?
2) Bonus What is the proper way to make these methods ajax-compatible (render a partial with ajax)? What happens if the user doesn't have js enabled?
I may expand/evolve this question based on the answers I get.
I'm tired of googling things like custom action route rails and the like to hodge-podge my apps. It's draining and poor form and I'm finally getting to the level to comprehend the lingo--I've been self taught 100%...so please try to be understanding if you can for a young padawan.
Here's how to think of it, from the beginning:
1) The one and only thing your app does is respond to HTTP REQUESTS.
The most typical kinds of requests are:
GET - the user types something into the URL bar of their browser and hits enter.
POST - the user submits a form.
There are also other kinds of HTTP requests, most importantly PUT, PATCH and DELETE. Rails follows the REST pattern, which means it assigns specific meanings to these HTTP verbs.
2) When any request comes into your app, it has to be routed to a Controller Action.
Your routes.rb file is a set of instructions for the Rails Router (ActionDispatch) that tells the router where to send requests. The "standard" rails resource is given as a shortcut, like this:
resources :things
This means the following:
GET /things => things#index
GET /things/:id => things#show
GET /things/new => things#new
GET /things/edit/:id => things#edit
POST /things => things#create
PUT /things/:id => things#update
DELETE /things/:id => things#destroy
These are considered the standard RESTful actions - nothing else is set by your resources :things declaration. So, if you want the controller to perform other non-standard actions, you have to add them manually.
If you want to perform an action on a specific record, the best way is to use:
resources :things do
member do
get 'vote_up'
end
end
This tells the router that if someone makes a GET request to /things/123/vote_up that it should trigger the ThingsController vote_up action.
All of this is spelled out in great detail in the Rails Guide, you should read the whole thing.
3) Your controller's job is to send a response to the request.
Normally this means something like loading a record from the database and rendering the view for that record.
Each controller action ends by sending the response back to the incoming request. This response can be either a render call - which means send back some data in some format - or a redirect call - which basically makes a new request for you and therefore you get the response of that other request.
In Rails a redirect is effectively sending the request to a different controller action.
A Render call sends data as a response to the request.
When you call render :new, this is a shortcut to render :template => :new, which loads the app/views/things/new.html.erb (or whatever) template, sends it the data from the controller (normally your instance variables) and evaluates this using the template language (erb, haml, etc.) This results in a big string of HTML, which the controller then delivers to the browser.
Want to see what this for yourself? Try ending a controller with render :text => 'Hello World', or even:
render :inline => '<!DOCTYPE html><head><title>Inline Wow!</title></head><body>Mind blown.</body></html>'
See what happens.
When responding (rendering) you can send "normal" HTML templates, with a whole page worth of information in it (head, body, etc.), or a partial that is used by Ajax. You can also send raw data such as JSON or XML. It's all actually just text, and depending on the content of that text (and the HTTP headers that come with it) the browser, script, or client application handles it accordingly.
Again, see the Rails Guide.
4) When the request is made by a browser you probably want to send back HTML. If the request is made by Ajax you probably want to send back JSON.
In the case of a custom action like vote_up you might not want to show a template at all, but just redirect. So, you might have something like this:
ThingsController < ApplicationController
def vote_up
#thing = Thing.find(params[:id])
#thing.vote_up
redirect_to #thing
end
end
Now, one of the benefits of the router is it will give you URL helpers. If you've created the route and action as shown before, on your "show thing" page you could have a URL like this:
link_to 'Vote up this thing!', vote_up_thing_path(#thing)
That would create a link to things/123/vote_up, and if someone clicked on it it would run the code in the vote_up action on the ThingsController, and then redirect back to the show thing view.
5) Your templates send messages to the controllers using links and forms. Links make GET requests, forms make POST requests.
If you want to start having AJAX requests, that's fine. In that case, you just need to make the request in Javascript, and handle the response. So, for instance, you could put something like this in your template:
= link_to 'Vote up this thing', vote_up_thing_path(#thing), :id => 'vote-up-button'
Then in Javascript (with jQuery) you could have a function like this:
$(function(){
$('a#vote-up-button').click( function(event){
event.preventDefault();
$.ajax({
url: this.attr('href'),
type: 'GET',
success: function(){...},
error: function(){...}
});
});
});
In this case the jQuery Ajax method is just making a get request, and then running a callback function based on the response it got.
6) The structure of your controller/routes does not affect what kind of requests you can make, only what action will respond to what HTTP method on what URL.
What you do INSIDE your controller action determines whether you are ready to respond to javascript or html requests etc.
While rails is certainly able to handle multiple request formats in a single controller action, using the respond_to block, as a matter of practicality I find things work much more smoothly when you choose to have routes only respond to one format or another.
IE: I would make your normal page load requests (index, show, new, edit) just HTML requests, and then I would make any additional AJAX actions you want to add be Javascript only -- ie. they respond with JSON instead of HTML. You don't have to do this, of course, but your life will be easier if you do.
I hope this gives you a clearer sense of what is happening in your app. Welcome to Rails, you're joining a great community!
Welcome to the ROR world. :)
Routes, controllers, and views work together to translate a HTTP request into a response of some kind (be it HTML, JSON, or XML). Each attacks a different piece of the problem.
Starting from the end, views are the templates in the rails world and they typically are ERB. ERB is just one templating system, others can be used as well, like haml. Their job is to take some data given to them by the controller and produce formatted output, again typically HTML, JSON, or XML.
But how do you know which view to render for a particular request? How do you get data into your view so that it can do all the fancy dynamic stuff you need? This is where controllers come in. Controllers are ruby classes with the job of examining the parsed HTTP request and any related parameters, fetching data from the database (or wherever), and then passing that data to a view. A controller will typically have several different methods, each corresponding to a different task (e.g. create, show, update, etc).
Lastly, Routes are a DSL for parsing HTTP requests and dispatching a parsed HTTP request to a particular controller method. They are the glue that Rails uses to route URLs to methods, hence the name, routes.
Regarding your specific questions:
1) To create a new action, you have to add a route, a controller method, and a view. So for instance if you wanted to get a count of your entries, you could add a route like:
get '/entries/count' => "entries#count"
This tells ROR to call the count method in the entries controller when that URL is received. Your controller method in this case would be something simple like:
def count
#cnt = Entries.count
end
Then last, you'd make a view in app/views/entries/count.html.erb that had something like:
<p>Count: <%= #cnt %></p>
2) Ajax compatible code really just a way of asking "what if the requested format for this request is JSON?". For this, you'll want to use respond_to method. In a way, respond_to is a formal way of specifying a different view to handle the formatting of the request. To continue with the example above, you could do:
def count
#cnt = Entries.count
respond_to do |fmt|
fmt.html # This just renders the default template
fmt.json { render json: { count: #cnt } }
end
end
N.b. - All code above freehanded. :)
If you have the RESTful controller:
resources :entries do
collection do
get :vote_down
get :vote_up
end
end
this will generate 2 additional routes:
GET /entries/:id/vote_up(.:format) entries#vote_up
GET /entries/:id/vote_down(.:format) entries#vote_down
You can easily change HTTP method to GET, POST, PUT and DELETE.
Also, check "Adding More RESTful Actions" in the routes documentation.
Related
From what I understand, these actions are usually triggered after a form is submitted. I can't imagine any reason why a form would generate json, in other words, (assuming a hypothetical controller is named 'UsersController') I can't imagine when or how a form would take my browser to:
localhost:3000/users.json
wouldn't post requests automatically take the user to:
localhost:3000/users
...and hence automatically to html? And furthermore, if they arrived here, at:
localhost:3000/users
and typed in:
localhost:3000/users.json
wouldn't this just be a GET request back to index.json? And hence back to the index action?...rendering json in that particular action via a GET request (not the create action, via POST)?
I'm confused and can't understand how anyone could ever end up at users.json from a POST request, and hence I can't imagine why a respond_to block that renders json makes sense in these actions. What am I missing?
Rails assumes that the controller actions might also be accessed as an API and not just via the browser. In such cases it makes sense to respond to those requests differently instead of redirecting the client (browser) to the index or show action.
When you create a resource from an API client, it might not make sense to redirect the user to the index or show action instead of just responding to the client that the resource was created (or not). Same applies for the update and destroy actions.
When I submit a form, a number of parameters are set without showing up in the url.
I would like to do the same thing with link_to:
<%= link_to((purchase.paid ? 'yes' : 'no'), {action: :index, hidden_id: purchase.id}) %>
produces the url 'http://localhost:3000/purchases?hidden_id=1'. I would like to link to the url 'http://localhost:3000/purchases' while still setting params[:hidden_id] so I can access it in the controller, as if I had submitted a form.
My routes.rb file is as follows:
root to: 'products#index'
resources :products
resources :purchases
match ':controller/(:action/(:id))', controller: :shop, via: [:get,:post]
In answering this, is there anything I should know here about the difference in the way these two things are handled? Is it something about get vs post requests or is there some other principle involved which I'm not grasping?
Yes, it's to do with Get vs Post requests.
A Get request can only send parameters in the URL itself. A post request can also be sent to a URL that includes parameters in the URL itself, but it can also send parameters 'under the hood' so to speak.
So if your routes were set up to allow it, you could send either a get or a post request to http://localhost:3000/purchases?hidden_id=1, but only the post request could include additional parameters under the hood.
Anything else you should know about the difference in the way these two are handled? Yes. In most web frameworks, when you see the parameters server-side, they will be split up into GET params and POST params. Rails doesn't make this distinction, and puts them both in the same params hash. (I think this is silly, but whatever).
Also, a get request can be sent simply by entering the URL in your browser and hitting enter. A post request will generally only be executed by a user submitting a form on a web page. For this reason, get requests are not meant to change any content in your database. They should be for viewing information only. So, eg, if you have a button to delete a resource (eg. a blog post or something) it should be submitted via post. (more info on that at Why shouldn't data be modified on an HTTP GET request?)
Lastly, Rails provides an option in it's link_to helper to allow you to easily make the 'link' use a post request. See the method option at http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to. This basically uses javascript to prevent the normal action of clicking the link (which would be a get request), and submit a post request instead.
I am currently using a link_to helper in View to pass parameters like title , author ,image_url and isbn back to controller
<%= link_to 'Sell this item',new_item_path(:title => title, :author => authors, :image_url=>image, :image_url_s=>image_s, :isbn=>isbn, :isbn13=>isbn13 ) %>
Controller will then assign the parameters to an object to be used by a form in View later(in new.html.erb)
def new
#item = Item.new
#item.title = params[:title]
#item.author = params[:author]
#item.image_url = params[:image_url]
#item.image_url_s = params[:image_url_s]
#item.isbn = params[:isbn]
#item.isbn13 = params[:isbn13]
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #item }
end
end
new.html.erb will then be called.
This is all working fine but the url shows all the parameters
http://localhost:3000/items/new?author=Michael+Harvey&image_url=http://ecx.images-amazon.com/images/I/51vt1uVjvLL._SL160_.jpg&image_url_s=http://ecx.images-amazon.com/images/I/51vt1uVjvLL._SL75_.jpg&isbn13=9780307272508&isbn=0307272508&title=The+Third+Rail
Is there any way I can make the parameters not show up on the URL?
Maybe you could encode the parameters and decode them in the controller to deter users who may want to modify the url? Might be overkill but...
>> author=ActiveSupport::Base64.encode64("author=jim")
=> "YXV0aG9yPWppbQ==\n"
>> ActiveSupport::Base64.decode64(author)
=> "author=jim"
A POST can be used to move the parameters out of the URL and into the request, but this is not the "correct" or best practice. HTTP standards are such that non-GET requests are meant to be used only for requests that change state on the server. This is why you get a warning when you refresh a page that was generated in response to a POST.
There is nothing wrong with having parameters in the URL. So much focus should not be made on what appears to the URL bar, let alone what's after the ?. If however you have some need (i.e. insistence of a client) to remove them, you have several options, two of which John mentions.
I'm assuming your "new" action is REST-style, in that it's generating a form that would have to be submitted to change state on the server. Therefore your options might be:
Use POST, even though it's not standard compliant. Not recommended.
Use AJAX GET. This requires javascript, and ajax handling does add requirements such as the use of a JS framework and testing.
Use GET (or POST), but capture the parameters and store them, the redirect the user back to another clean URL that displays those stored value. You could store those in the session hash, or create a database record of them. Actually you really should use POST in this case, since you are effectively changing state on the server by storing those parameters. In this case, if the user refreshes the page he is directed to, those parameters will be preserved. This effectively removes the browser warning on refresh, something I can certainly appreciate.
There are two options that I can see and both involve JavaScript:
Have the link populate hidden form fields for the parameters and then submit the form using an HTTP POST request
Have the link submit an AJAX request to the controller action (using an HTTP GET unless clicking the link changes server-side state, in which case a POST should be used)
I think I would go with the second approach.
Why not write them to the session? It looks like you might have less than 4k in data there. Just remember to wipe it.
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.
As a long-time Ruby and Rails user, it never struck me until today to really think about the get-and-redirect pattern in Rails. The typical example of this would be calling a create() action, and then redirecting the user to a show() action to display the newly-created item:
class JournalEntries
def index
#entries = JournalEntry.all
end
def create
#entry = JournalEntry.new( :name => "to-do list" )
#entry.save
redirect_to :action => "index"
end
end
However, this has the inherent disadvantage that you are doubling your network traffic. This both slows down your users' site experience, as well as increasing your bandwidth charges.
So why not just do this instead:
def create
#entry = JournalEntry.new( :name => "to-do list" )
#entry.save
index
Same output, and no extra overhead required. But in addition to this, there is an even more substantial problem: redirect_to can only redirect using GET. This causes major problems for RESTful apps that use four different HTTP methods.
In my case, I wanted a user to be able to call /journals/8 and retrieve the Journal with that ID. If it wasn't found, I wanted to create a new, empty Journal object. In either case, the Journal object would then be sent to the caller.
Note that the create() method in RESTful Rails is routed from "POST /players". But since redirect_to (and the underlying HTTP redirect) can only send GET requests, it actually redirects to "GET /players", which is the index() method. This behavior is clearly wrong.
The only solution I could think of was to simply call create() instead of redirect_to() as in my example above. It seems to work fine.
Any thoughts on why redirect_to is preferred to calling actions directly?
If they do a page refresh they don't get that annoying "Resend data?" popup
It's not just that the popup is annoying (and makes no sense to most users) -- if the user clicks "yes, re-do the POST", he'll end up creating another Journal Entry (or whatever).
Also, it's annoying for the URL to read /posts/create instead of /posts since the user cannot copy / re-use it.
The reason for it is as you point out. You redirect to a GET request, which is correct when it comes to REST (only do updates with POST/PUT, only get data with GET).
A redirect surely gives a little overhead with the redirect, but since no data is actually being sent between the browser and the server except for the POST data and the redirect (which is only sending the new url to the browser) I don't think that the issue of bandwith is of concern.
But on another point, you should not redirect to /journals (by calling redirect_to :index), you should redirect it to the newly created journal entry (by calling redirect_to #entry) which will work if you set up the routes correctly by, for instance map.resources :journals
Update:
I think, for creating the Journal when one doesn't exist, you should ask the user for more input. What is the reason for you to create the entry? The entry should have some text or some other input from the user, so I think from a REST (and rails) perspective you should actually redirect it to the new() method (with a GET request) where the user can input the additional information, that one will then POST the input and create the entry and after redirect to the newly created entry.
If you don't have any extra information that needs to put in, I'm not sure how to do it in a RESTful way, but I would probably have done it by putting the creation logic in a separate method that I would call from the create() and the show() method and then just continue with the show(), not redirecting at all, but also not calling the resource method.
I'm a Python/Django person, but the reasons for the redirect is language agnostic:
If they do a page refresh they don't get that annoying "Resend data?" popup.
This gives you a completely clean, RESTful URL for the page they are looking at. If you used POST it might not matter that much, but if GET was used for the update then you definitely want to get rid of any dangling params.