Since you know the abstract of REST (otherwise read "Take A REST" first), we'll start to drive the train to REST. Fasten your seat belts!
I will use a simple sample to demonstrate the way REST works in Rails. We will talk about a person/ people to manipulate. As I mentioned in the previous article the Controller maps the resource/ object of class ActiveRecord::Base.
I assume you have a database running. I skip that part, because it depends on your preferences which DMS you use. I don't care if it's a sqlite, mysql or whatever. I use postgres.
All In One (Line)
As you already know:~/blogspot$ rails traincreates a rails application named train. And who goes by train? Correctly, it's all about people:
~/blogspot$ script/generate scaffold person name:string surname:string age:integerOur People have a name and a surname the same as an age. This one liner creates the model Person in the a app/models/person.rb:
class Person < ActiveRecord::Base endDon't forget to
~/blogspot$ rake db:migrateto have the appropriate table in the database after you set up your config/database.yml (you know how). The extra benefit is the line
map.resources :peoplein the config/routes.rb to create the routes and their URL helper. I will touch it later on once again. But that's not all. The extra extra benefit is the app/controllers/people_controller.rb (I compressed the comments a bit; you can compare with your own generated):
The Controller
class PeopleController < ApplicationController
def index
@people = Person.find(:all)
respond_to do |format|
format.html # GET /people --> index.html.erb
format.xml{render :xml => @people} # GET /people.xml
end
end
def show
@person = Person.find(params[:id])
respond_to do |format|
format.html # GET /people/1 --> show.html.erb
format.xml{render :xml => @person} # GET /people/1.xml
end
end
def new
@person = Person.new
respond_to do |format|
format.html # GET /people/new --> new.html.erb
format.xml{render :xml => @person} # GET /people/new
end
end
def edit
@person = Person.find(params[:id]) # GET /people/1/edit
end
def create
@person = Person.new(params[:person])
respond_to do |format|
if @person.save
flash[:notice] = 'Person was successfully created.'
format.html{redirect_to(@person)} # POST /people
format.xml{render :xml => @person, :status => :created, :location => @person} # POST /people.xml
else
format.html{render :action => "new"} # failed validation
format.xml { render :xml => @person.errors, :status => :unprocessable_entity }
end
end
end
def update
@person = Person.find(params[:id])
respond_to do |format|
if @person.update_attributes(params[:person])
flash[:notice] = 'Person was successfully updated.'
format.html{redirect_to(@person)} # PUT /people/1
format.xml{head :ok} # PUT /people/1.xml
else
format.html{render :action => "edit"} # failed validation
format.xml{render :xml => @person.errors, :status => :unprocessable_entity}
end
end
end
def destroy
@person = Person.find(params[:id])
@person.destroy
respond_to do |format|
format.html{redirect_to(people_url)} # DELETE /people/1
format.xml{head :ok} # DELETE /people/1.xml
end
end
end
Gosh! Can you believe that one single script/generate does such an incredible job for you? It definitely takes more time to explain all these lines and what it means for you than typing it. Not to mention how long it would take these lines. It's unbelievable but that's not all yet...
The extra extra extra benefit are some new templates generated in app/views/people:The Templates
- index.html.erb
- show.html.erb
- edit.html.erb
These templates are for rendering the actions "index", "show" and "edit".
The template app/views/people/index.html.erb:Listing people
| Name | Surname | Age | |||
|---|---|---|---|---|---|
| <%=h person.name %> | <%=h person.surname %> | <%=h person.age %> | <%= link_to 'Show', person %> | <%= link_to 'Edit', edit_person_path(person) %> | <%= link_to 'Destroy', person, :confirm => 'Are you sure?', :method => :delete %> |
<%= link_to 'New person', new_person_path %>
Please be patient. I will go through it in a moment.
The template app/views/people/show.html.erb:The template app/views/people/edit.html.erb:Name: <%=h @person.name %>
Surname: <%=h @person.surname %>
Age: <%=h @person.age %>
<%= link_to 'Edit', edit_person_path(@person) %> | <%= link_to 'Back', people_path %>
And at least the new.html.erb:Editing person
<% form_for(@person) do |f| %> <%= f.error_messages %><%= f.label :name %>
<%= f.text_field :name %><%= f.label :surname %>
<%= f.text_field :surname %><%= f.label :age %>
<%= f.text_field :age %><%= f.submit "Update" %>
<% end %> <%= link_to 'Show', @person %> | <%= link_to 'Back', people_path %>
New person
<% form_for(@person) do |f| %> <%= f.error_messages %><%= f.label :name %>
<%= f.text_field :name %><%= f.label :surname %>
<%= f.text_field :surname %><%= f.label :age %>
<%= f.text_field :age %><%= f.submit "Create" %>
<% end %> <%= link_to 'Back', people_path %>
... man, the Rails train is an incredible fast thingie (The Concorde wouldn't be faster, if it still flew). And it does all for you, including RESTful routes. This standardized controller and its templates handle the CRUD of every person in the train.
Right now the more tedious job to explain about what's going on is waiting for the poor author (me).
REST URLs vs. Granny URLs
Let's start with the created PeopleController. It contains the seven actions to process the people of the resource class Person (model). The list below abstracts them in short manner.| Action | HTTP | Route helper | URL | Response |
|---|---|---|---|---|
| index | GET | people_path | /people | Collection of all people |
| show | GET | person_path(@person.id) | /people/1 | Person with id 1 (to show) |
| new | GET | new_person_path | /people/new | New Person with nil attributes for creation |
| edit | GET | edit_person_path(@person.id) | /people/1/edit | Person with id 1 for update |
| create | POST | people_path | /people | Person with new created id and attributes (sent by form) |
| update | PUT | person_path(@person.id) | /people/1 | Person with updated attributes (sent by form) |
| destroy | DELETE | person_path(@person.id) | /people/1 | Collection of all people except the person with id |
~/blogspot$ rake routesComparing the RESTful routes and the old fashioned way:
| Action | RESTful URL | old fashioned URL |
|---|---|---|
| index | /people | /people/index |
| show | /people/1 | /people/show/1 |
| new | /people/new | /people/new |
| edit | /people/1/edit | /people/edit/1 |
| create | /people | /people/create |
| update | /people/1 | /people/update/1 |
| destroy | /people/1 | /people/destroy/1 |
Do you see the differences? I mean, do you realize the sweetness of the REST URLs? The URL doesn't declare anything about the action. They are stripped to their resource they represent. Who knows what to do with the resource (URL) "/people/1"? Show, update or destroy it? Nobody knows. The HTTP verb declares it. And that's how it should be. No resource-action mixup anymore, anytime!
The Special Requests
Testing the application creates logs according to the requests.
Show request:
Processing PeopleController#show (for 127.0.0.1 at 2009-01-26 19:40:52) [GET]
Parameters: {"action"=>"show", "id"=>"1", "controller"=>"people"}
Update request:
Processing PeopleController#update (for 127.0.0.1 at 2009-01-26 19:41:01) [PUT]
Parameters: {"commit"=>"Update", "person"=>{"name"=>"Destra", "surname"=>"Garcia", "age"=>"29"},
"authenticity_token"=>"87b9c2baa4e1114ca0b97384253ac6c283f19cee", "_method"=>"put",
action"=>"update", "id"=>"1", "controller"=>"people"}
Please survey the parameters hash sent by the form. First it contains the data of person called Destra Garcia (btw. the best female soca artist ever). But more interesting is the "_method"=>"put", which is responsible for pointing to update action instead of show (remember the equal URL). Who caused it? I will mention it, when I go into detail of the edit.html.erb template. For now it's sufficent to just know it.
Destroy request:
Processing PeopleController#destroy (for 127.0.0.1 at 2009-01-26 20:07:57) [DELETE]
Parameters: {"authenticity_token"=>"87b9c2baa4e1114ca0b97384253ac6c283f19cee",
"_method"=>"delete", "action"=>"destroy", "id"=>"1", "controller"=>"people"}
Once again the "_method" parameter with its value "destroy" this time. The "authenticity_token" helps to prevent XSRF server side and is part of all create, update and destroy requests.
Now let's take a deeper look into the sent headers with the help of curl (nice tool to send/ receive data to/ from a server using amongst others HTTP).~/blogspot$ curl -H "Accept: text/html" -i -X GET http://localhost:3000/people/1It returns the header and the HTML response (I omitted the uninteresting stuff):
HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8So the show action responded to the HTML request. It also does when requesting XML:
~/blogspot$ curl -H "Accept: application/xml" -i -X GET http://localhost:3000/people/1The response:
HTTP/1.1 200 OK Content-Type: application/xml; charset=utf-8
succeed with the requested resource in a XML styleee. You also can test it in your browser with URL localhost:3000/people/1.xml (assumed that you run your server on port 3000 and there is a resource people/1). Why does the server can respond to different requests using the same action? Sugar, sugar, Rails. Yep! Rails evaluates the header and returns the appropriate resource in the requested mode. HTML? XML (Webservice via ActiveResource gives a broad hint)? No problem. You will use the same action and you're done. Lucky guy.
It's time to render the response, isn't it? By default rails searches for the HTML template to render (when there is no respond_to call). Since 2.0 we use ERB for rendering HTML. The files are marked with the extension html.erb; remember the automatically generated templates when I used scaffold to generate the person resource.
The Links In Detail
Now let's elaborate to the index.html.erb, just to pick up the first one.
It renders the collection of people. Basically it contains a table with the people our table can deliver. As by former scaffolding, there are also the three links to work the single entity. Show, Edit and Delete, just to name it. "Show" shows the entity in detail. The URL is generated by assigning the object itself. Rails inspects the object and assumes the controller by its class (a controller represents a resource!). This way to generate the URL is a polymorphic one. But that doesn't matter at this point. I just wanted to mention it, because I explain it in a later article ;) . The URL "/people/1" will call the show action of PeopleController. You also could have used the explicit version "person_path(person)" instead of just assigning simply the object. The next link for calling the edit action named "Edit" is an example of an explicit URL helper: edit_person_path(person). It's one of the dynamically generated helper. Rails simply extracts the id of the passed object. So the call also could look like edit_people_path(person.id). You better save your energy for typing the longer version and watch the last link ("Destroy") in the line. As you already know, the HTTP verb "delete" needs to be sent to call the destroy action. The browser is unable to do it, so let Rails do the job. I set the :method option of the link_to helper to :delete. So Rails realizes that it has to generate a form. For obvious reasons it is not a HTML form. Right now it's sufficient to know that the link establish a form on click:
Destroy
Before sending the form you will have to confirm, if you really are sure to destroy the item. If so, the form will be sent with the already mentioned attributes, "_method" => "delete" amongst others. Please compare the href attribute of the "Destroy" link with the "Show" href.
The "New person" link is described briefly. It uses the explicit helper new_person_path to generate "/people/new".
The show.html.erb template contains two links. One to "Edit" the chosen item and another one ("Back") to return to the complete collection. I already declared the "Edit" link before. And to return to the people is pretty easy. The URL helper people_path does it.
O.K. the next template (edit.html.erb) is more interesting, I swear. I suppose I don't need to describe the both links "Show" and "Back" once again. So I start with form. It is generated by the form_for helper. And by the way I advise you to make extensive use of it. It's a sweet one, believe me. It expects an object and instantiates a form object of it, you conveniently can use to access the attributes to fill the form input and select fields as I describe in another article. The HTML of the form looks like:
You're right. The URL is equal to the one calling the show action ("Show"). And it does POST as every form should. But it also sends a parameter called "_method" with the value "put", affiliated by a hidden field. And that reveals the request to be PUT (update action). The same trick used for calling the destroy action
The form of the new.html.erb doesn't need any trick. It just uses the form's POST to be POST:
Here you also can admire the fancy form_for helper. I really appreciate it.
Oh lord, to write these lines took a lot more time than creating the life cycle of the person resource itself. I hope you enjoyed it. So do yourself a favour and take a REST and relax.
Nice up your App!
Keine Kommentare:
Kommentar veröffentlichen