Freitag, 23. Januar 2009

The Very BEST Of REST (The Praxis)

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 train
creates 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:integer
Our 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
end
Don't forget to
~/blogspot$ rake db:migrate
to 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 :people
in 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

<% for person in @people %> <% end %>
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:

Name: <%=h @person.name %>

Surname: <%=h @person.surname %>

Age: <%=h @person.age %>

<%= link_to 'Edit', edit_person_path(@person) %> | <%= link_to 'Back', people_path %>
The template app/views/people/edit.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 %>
And at least the new.html.erb:

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
The route helpers are generated by the routes.rb dynamically. You can ask for them by
~/blogspot$ rake routes
Comparing 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/1
It returns the header and the HTML response (I omitted the uninteresting stuff):
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
So 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/1
The 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