«

»

Jan 11

Ruby on Rails 101 – Encurtador de URLs = Novo Blog

Ok, o titulo do post não ficou legal, mas a idéia é que tem tanto encurtador de URLs por ai que eu resolvi fazer um em rails também para brincar um pouco, e como a implementação ficou muito simples, vou tentar transformar isto em um tutorial bem básico de Rails.
Mas vejam bem, a idéia é só mostrar o básico, não vou colocar mais um no ar, já tem um excelente feito pelo nosso amigo Manoel Lemos, o zapt.in onde ele esta adicionando recursos muito legais. Só peguei a idéia por que achei que se tornaria um tutorial mais divertido do que o famoso blog em rails :D

Primeiro, você vai precisar do Rails[bb] instalado, e para ter o Rails instalado você vai precisar do interpretador Ruby instalado, tem diversos posts sobre isto por ai, mas basicamente numa maquina windows, sugiro instalar o “Instant Rails”, num linux instale o Ruby e depois o Ruby Gems e logo depois execute o comando “gem install rails”.

Deste ponto em diante vou considerar que você já tem o rails instalado e funcionando.

Agora com o Rails instalado, vamos começar a desenvolver a aplicação, vou chamar de “us” para “URL Shortener”, como qualquer projeto rails, vamos começar digitando:

1
2
rails us
cd us

Uma aplicação rails tem inicialmente a seguinte estrutura de diretórios:

  • app
    • controllers
    • helpers
    • models
    • views
      • layouts
  • config
    • environments
    • initializers
    • locales
  • db
  • doc
  • lib
    • tasks
  • log
  • public
    • images
    • javascripts
    • stylesheets
  • script
    • performance
  • test
    • fixtures
    • functional
    • integration
    • performance
    • unit
  • tmp
    • cache
    • pids
    • sessions
    • sockets
  • vendor
    • plugins

Não vou explicar para que serve cada um deles, mas os mais importantes para este mini tutorial são:

  • app/controllers – onde vão ficar os controladores, o código que faz o meio de campo entre a lógica e a view.
  • app/models – onde vão ficar os models, a interface da aplicação com o banco de dados e toda a lógica
  • app/views – onde vamos renderizar os dados para os usuários
  • public/* – onde ficam os recursos estáticos, como imagens, estilos, javascripts
  • config – onde ficam as configurações da aplicação
  • scripts – scripts para poupar trabalho, gerar código, rodar servidores, …

A nossa aplicação vai ser composta de dois controladores, um model e algumas views.

O ideal seria começar escrevendo testes, mas como este é um post estilo “introdução ao rails” vou deixar os testes de lado.

A primeira coisa que vamos fazer é criar um cadastro básico de URLs, para isto vamos utilizar o gerador do rails, com o seguinte comando:

1
ruby script/generate scaffold url_info href:string clicks:integer

Este comando vai gerar uma série de arquivos, vamso dar uma olhada em alguns deles:

1
2
class UrlInfo < ActiveRecord::Base
end

Este é o conteúdo do arquivo app/models/url_info.rb, toda a implementação do nosso model para um cadastro simples e, por enquanto, sem validações.

E já podemos inclusive criar o banco de dados padrão da aplicação, o rails veio configurado por padrão para utilizar o banco de dados sqlite3, mas isto pode ser facilmente alterado, mas por enquanto vamos aceitar esta configuração e executar o comando:

1
rake db:migrate

Isto vai executar as migrations da aplicação, uma migration foi criada no último comando, vamos dar uma olhada rápida nela:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CreateUrlInfos < ActiveRecord::Migration
  def self.up
    create_table :url_infos do |t|
      t.string :href
      t.integer :clicks
 
      t.timestamps
    end
  end
 
  def self.down
    drop_table :url_infos
  end
end

Esta migration possui o código para criar uma tabela de nome “url_infos”, com um campo de nome “href” de tipo “string” e um campo “clicks” de tipo “integer”, o mapeamento do tipo ruby para o tipo SQL vai depender do banco de dados, do driver que o rails utilizar para acessar o banco.

Em uma migration é importatne sempre implementar os dois métodos, o self.up cria coisas no banco de dados, e o self.down apaga coisas do banco de dados, tudo o que for criado no self.up tem que ser apagado no self.down, desta forma permitindo que voltemos a qualquer versão da aplicação para corrigir algum bug se necessário.

No exemplo estamos utilizando os métodos create_table e drop_table da migration, mais informações sobre estes métodos podem ser obtidas nesta página da documentação do Rails.

A configuração de qual banco a aplicação esta acessando fica no arquivo config/database.yml que podemos ver abaixo com o conteúdo padrão:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# SQLite version 3.x
#   gem install sqlite3-ruby (not necessary on OS X Leopard)
development:
  adapter: sqlite3
  database: db/development.sqlite3
  pool: 5
  timeout: 5000
 
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  adapter: sqlite3
  database: db/test.sqlite3
  pool: 5
  timeout: 5000
 
production:
  adapter: sqlite3
  database: db/production.sqlite3
  pool: 5
  timeout: 5000

Este arquivo tem 3 sessões, correspondentes aos ambientes de desenvolvimento, testes e produção, ou seja, já podemos deixar estes 3 bancos de dados[bb] configurados, o que pode facilitar bastante a vida, ou complicar as vezes :D

Mas a idéia básica é que em cada ambiente é possível configurar qual o driver do banco de dados “adapter”, e os parâmetros deste driver, neste caso apenas o nome do banco é o suficiente, não vou entrar em maiores detalhes aqui por que não é a idéia deste post, quero fazer o encurtador de URLs funcionar antes de você dormir ou cansar de ler …

Então vamos lá, o rails criou um cadastro completo, que se você digitar o seguinte comando para inicializar o servidor, já pode acessar:

1
ruby script/server

Agora abra o seu browser preferido e acesse o endereço: http://localhost:3000/url_infos

Você vai ver uma listagem de informações de URLs, e quantos clicks cada URL já recebeu, você já pode até cadastrar algumas URLs ai, não vamos mexer muito neste controlador que foi criado, vamos alterar só um pouquinho, não faz sentido na hora do cadastro de uma URL ser necessário informar o número de clicks, então vamos abrir o arquivo app/views/url_infos/edit.html.erb e deixe ele como o que esta abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<h1>Editing url_info</h1>
<% form_for(@url_info) do |f| %>
  <%= f.error_messages %>
  <p>
    <%= f.label :href %><br />
    <%= f.text_field :href %>
  </p>
  <p>
    <%= f.submit 'Update' %>
  </p>
<% end %>
 
<%= link_to 'Show', @url_info %> |
<%= link_to 'Back', url_infos_path %>

(Dica, eu removi o parágrafo que continha o campo “clicks”)

Nesta página podemos ver alguns dos helpers do rails para a geração de formulários HTML, e para tratamento de mensagens, a idéia do helper “form_for” é que a variável passada como argumento para o bloco representa um formulário para “aquele elemento”, isto torna possível utilizar os outros helpers “formulário.text_field”.
O Rails tem diversos helpers, tanto para formulários, para options, para AJAX e diversos outros.

Agora vamos duplicar a mudança no outro formulário no arquivo app/views/url_infos/new.html.erb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<h1>New url_info</h1>
 
<% form_for(@url_info) do |f| %>
  <%= f.error_messages %>
 
  <p>
    <%= f.label :href %><br />
    <%= f.text_field :href %>
  </p>
  <p>
    <%= f.submit 'Create' %>
  </p>
<% end %>
 
<%= link_to 'Back', url_infos_path %>

Claro que estes dois arquivos são bem parecidos, e que poderíamos juntar todo o código repetido dos dois, mas vamos deixar isto para depois, por enquanto isto não nos interessa muito.

Agora vamos editar o controlador no arquivo app/controllers/url_infos_controller.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
class UrlInfosController < ApplicationController
  # GET /url_infos
  # GET /url_infos.xml
  def index
    @url_infos = UrlInfo.all
 
    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @url_infos }
    end
  end
 
  # GET /url_infos/1
  # GET /url_infos/1.xml
  def show
    @url_info = UrlInfo.find(params[:id])
 
    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :xml => @url_info }
    end
  end
 
  # GET /url_infos/new
  # GET /url_infos/new.xml
  def new
    @url_info = UrlInfo.new
 
    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @url_info }
    end
  end
 
  # GET /url_infos/1/edit
  def edit
    @url_info = UrlInfo.find(params[:id])
  end
 
  # POST /url_infos
  # POST /url_infos.xml
  def create
    @url_info = UrlInfo.new(params[:url_info])
    @url_info.clicks = 0
    respond_to do |format|
      if @url_info.save
        flash[:notice] = 'UrlInfo was successfully created.'
        format.html { redirect_to(@url_info) }
        format.xml  { render :xml => @url_info, :status => :created, :location => @url_info }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @url_info.errors, :status => :unprocessable_entity }
      end
    end
  end
 
  # PUT /url_infos/1
  # PUT /url_infos/1.xml
  def update
    @url_info = UrlInfo.find(params[:id])
 
    respond_to do |format|
      if @url_info.update_attributes(params[:url_info])
        flash[:notice] = 'UrlInfo was successfully updated.'
        format.html { redirect_to(@url_info) }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @url_info.errors, :status => :unprocessable_entity }
      end
    end
  end
 
  # DELETE /url_infos/1
  # DELETE /url_infos/1.xml
  def destroy
    @url_info = UrlInfo.find(params[:id])
    @url_info.destroy
 
    respond_to do |format|
      format.html { redirect_to(url_infos_url) }
      format.xml  { head :ok }
    end
  end
end

A única alteração do código gerado foi a adição da linha “@url_info.clicks = 0″ no método create, que informa aquele parâmetro que removemos do formulário.

Neste arquivo podemos também ver alguns métodos interessantes do rails, veja na lista abaixo a explicação de alguns deles:

  • UrlInfo.all – A classe ActiveRecord::Base do rails tem diversos métodos para pesquisa, o all é um alias para find(:all) e aceita os mesmos parâmetros
  • respond_to – O bloco respond_to informa ao rails como responder para diferentes tipos mime
  • render – Este é o método que realmente envia a resposta para o cliente, ele possui diversos parâmetros que permitem o envio de respostas AJAX, XML, HTML e diversas outras de forma normalmente transparante, a documentação pode ser encontrada aqui.
  • @url_inf.save – save é o método da classe ActiveRecord::Base que insere ou altera o conteúdo de um registro de uma tabela.
  • @url_info.update_attributes – este é o método da classe ActiveRecord::Base que atualiza os atributos de um registro alterados e salva as alterações no banco de dados
  • params[...] – params é um hash que permite acesso aos parâmetros enviados pelo usuário
  • @url_info.destroy – este é o método utilizado para apagar um registro do banco de dados
  • format. – format. dentro de um bloco “respond_to” informa ao rails quais tipos mime este método sabe retornar, assim o rails decide qual o mais adequado a solicitação do usuário
  • UrlInfo.find – A classe ActiveRecord::Base do rails tem diversos métodos para pesquisa, o método find é a base para a maioria deles
  • redirect_to – este método permite enviar ao browser um código de redirecionamento HTTP

Acho que por enquanto já esta bom de alterações no cadastro, vamos fazer o encurtador de URLs funcionar.

Para isto vamos criar mais um controlador, execute no console o seguinte comando:

1
ruby script/generate controller redirector index

Como o nome diz, este é o controlador que vai fazer os redirecionamentos, depois deste comando executado, o arquivo app/controllers/redirector_controller.rb foi criado, vamos editar este arquivo para que ele fique mais ou menos assim:

1
2
3
4
5
6
7
class RedirectorController < ApplicationController
  def index
	  ui = UrlInfo.find params[:id]
	  redirect_to ui.href if ui
  end
 
end

Isto já faz o redirecionador funcionar, mas não exatamente da maneira que gostaríamos :D

Por enquanto para ele funcionar precisamos acessar http://localhost:3000/redirector?id=… a idéia é que funcione acessando http://localhost:3000/[id]

Quando o id for passado o redirecionamento deve ocorrer automagicamente, quando não for passado devemos ver a lista de links conhecidos com quantos clicks cada um já teve.

Para que isto funcione vamos editar o arquivo config/routes.rb como no exemplo abaixo (vou apagar todos os comentários para facilitar a leitura do arquivo, comentários em Ruby são as linhas começadas por “#”).

1
2
3
4
5
ActionController::Routing::Routes.draw do |map|
  map.resources :url_infos
 
  map.connect ':id', :controller => 'redirector', :action => 'index'
end

A linha map.resources :url_infos foi gerada automaticamente com o scaffold, ela configura todas as rotas para o cadastro de URLs.
Esta linha configura as seguitnes rotas na aplicação:

Nome Método HTTP Caminho Mapeamento
url_infos GET /url_infos(.:format) {:action=>”index”, :controller=>”url_infos”}
  POST /url_infos(.:format) {:action=>”create”, :controller=>”url_infos”}
new_url_info GET /url_infos/new(.:format) {:action=>”new”, :controller=>”url_infos”}
edit_url_info GET /url_infos/:id/edit(.:format) {:action=>”edit”, :controller=>”url_infos”}
url_info GET /url_infos/:id(.:format) {:action=>”show”, :controller=>”url_infos”}
  PUT /url_infos/:id(.:format) {:action=>”update”, :controller=>”url_infos”}
  DELETE /url_infos/:id(.:format) {:action=>”destroy”, :controller=>”url_infos”}

A segunda linha configura a aplicação para quando receber apenas um parâmetro passar isto para o controlador de nome “redirector” para a action “index”, agora se acessarmos o endereço http://localhost:3000/1 a aplicação vai nos redirecionar para a primeira URL cadastrada, mas ainda não esta legal, precisamos contar os clicks tabém.

Para contar o clicks vamos alterar o redirector controller que editamos antes, o código dele vai ficar assim:

1
2
3
4
5
6
7
8
9
10
class RedirectorController < ApplicationController
  def index
        ui = UrlInfo.find params[:id]
        if ui
                ui.clicks += 1
                ui.save
                redirect_to ui.href
        end
  end
end

Agora antes de redirecionar a quantidade de clicks é incrementada e a informação é salva no banco de dados.

Agora vamos mudar a página inicial para a listagem de URLs, temos duas formas de fazer isto, forma chinelona:
Editar o arquivo public/index.html e configurar um meta refresh, o conteúdo fica como abaixo (HTML padrão).

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
  <head>
    <meta http-equiv="refresh" content="0;url=url_infos" />
    <title>Mega URL Shortener Sample</title>
  </head>
  <body>
  <a href="url_infos">Link List</a>
  </body>
</html>

Ou apagar o arquivo public/index.html, e editar novamente o arquivo config/routes.rb, verifique abaixo o conteúdo alterado.

1
2
3
4
5
ActionController::Routing::Routes.draw do |map|
  map.resources :url_infos
  map.root :controller => 'url_infos'
  map.connect ':id', :controller => 'redirector', :action => 'index'
end

Foi adicionada a linha “map.root :controller => ‘url_infos’” que informa qual a ação padrão da aplicação.

E com isto já temos o encurtador de URLs quase pronto, faltam alguns detalhes, primeiro no arquivo app/views/redirector/index.html.erb vamos adicionar uma mensagem dizendo que a URL não esta cadastrada.

1
<b>A URL informada não esta cadastrada no sistema</b>

Isto vai funcionar por que o controlador “redirector” só chama o redirect se a “UrlInfo” for encontrada, caso contrário ele executa a ação default, que é renderizar a “view” correspondente ao método.

Agora vamos apagar alguns arquivos no diretório app/views/url_infos/, siga a lista:

  • edit.html.erb
  • new.html.erb
  • show.html.erb

Vamos editar o arquivo index.html.erb no mesmo diretório:

1
2
3
4
5
6
7
<h1>Shortened URLs</h1>
<div id="form">
<%= render :partial => 'editor_form' %>
</div>
<div id="table">
<%= render :partial => 'urls_table' %>
</div>

Todo o conteúdo deste arquivo foi movido para dois partials, partials são uma forma de reutilizar código de views no rails, mas neste caso estaremos utilizando partials para implementar um pouco de AJAX.

A idéia é que o formulário do topo da página seja submetido via ajax e que atualize apenas o pedaço da página que for necessário, veja abaixo como ficaram os dois partials:
_editor_form.html.erb

1
2
3
4
5
6
7
8
<% form_remote_for(@url_info) do |f| %>
  <%= f.error_messages %>
  <p>
    <%= f.label :href, 'URL:' %>
    <%= f.text_field :href %>
    <%= f.submit 'Create' %>
  </p>
<% end %>

Neste formulário estamos utilizando o helper “form_for_remote” que cria um formulário que sera submetido via AJAX, não fazendo refresh da página toda de uma só vez.
_urls_table.html.erb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<table width="100%">
  <tr>
    <th>Original URL</th>
    <th>Shortened URL</th>
    <th>Clicks</th>
  </tr>
<% @url_infos.each do |url_info| %>
  <tr>
    <td><%=h url_info.href %></td>
    <td><%=h url_for(:controller => 'redirector', :id => url_info.id, :only_path => false) %></td>
    <td><%=h url_info.clicks %></td>
    <td><%= link_to 'Go To', :controller => 'redirector', :id => url_info.id %></td>
    <td><%= link_to 'Destroy', url_info, :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
<% end %>
</table>

A tabela não sofreu alterações grandes, apenas foi colocado mais um campo para mostrar qual a URL no sistema correspondente a uma URL cadastrada, e para que isto ficasse dinâmico, o helper “url_for” for utilizado, com o parâmetro “:only_path” setado para false, desta forma a URL completa seria impressa.

Para que este formulário via AJAX[bb] funcione, algumas alterações precisaram ser feitas no controlados “url_infos”, como pode ser visto abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class UrlInfosController < ApplicationController
  # GET /url_infos
  # GET /url_infos.xml
  def index
    @url_infos = UrlInfo.all
    @url_info = UrlInfo.new
 
    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @url_infos }
    end
  end
 
  # POST /url_infos
  # POST /url_infos.xml
  def create
    @url_info = UrlInfo.new(params[:url_info])
    @url_info.href = params[:href] if params[:href]
    @url_info.clicks = 0
    respond_to do |format|
      if @url_info.save
        format.html  do
          flash[:notice] = 'UrlInfo was successfully created.'
          redirect_to root_url
        end
        format.xml  { render :xml => @url_info, :status => :created, :location => @url_info }
	format.js  do
          @url_infos = UrlInfo.all
          @url_info = UrlInfo.new
          render :update do |page|
            page.replace_html 'form', :partial => 'editor_form'
            page.replace_html 'table', :partial => 'urls_table'
            page.alert 'UrlInfo was successfully created.'
          end
        end
      else
        format.html { render :action => "index" }
        format.xml  { render :xml => @url_info.errors, :status => :unprocessable_entity }
        format.js do
          render :update do |page|
            page.alert @url_info.errors.full_messages.join 'n'
          end
        end
      end
    end
  end
 
  # DELETE /url_infos/1
  # DELETE /url_infos/1.xml
  def destroy
    @url_info = UrlInfo.find(params[:id])
    @url_info.destroy
 
    respond_to do |format|
      format.html { redirect_to(url_infos_url) }
      format.xml  { head :ok }
    end
  end
end

Alguns métodos foram removidos, e o metodo create sofreu algumas alterações dentro do bloco “respond_to” adicionando suporte a respostas tipo “javascript”. E tem mais um detalhe no mesmo método, a segunda linha foi adicionada para quebrar todo o suporte “REST” do rails, como este é um encurtador de URLs eu quero que seja possível adicionar uma URL via uma chamada a uma URL do sistema, neste caso vai ser “/add/” e para isto na segunda linha do método “create” se o parâmetro “href” existir este é utilizado como valor da URL sendo criada no sistema, mas para isto funcionar a alteração abaixo é necessária no arquivo config/routes.rb:

1
2
3
4
5
6
7
ActionController::Routing::Routes.draw do |map|
  map.resources :url_infos
 
  map.root :controller => 'url_infos'
  map.connect '/add/:href', :controller => 'url_infos', :action => 'create', :href => /http[s]{0,1}://.*/
  map.connect ':id', :controller => 'redirector', :action => 'index'
end

A alteração feita foi a adição da linha “map.connect ‘/add/…”, preste atenção na utilização de uma expressão regular na especificação do parâmetro “href” no final da linha, isto permite que a URL completa seja utilizada como parâmetro, se isto não for utilizado o parâmetro vai terminar na primeira “/” da URL e o roteamento não vai funcionar corretamente.

Agora para que o AJAX funcione vamos alterar o layout gerado quando executamos o primeiro comando “script/generate scaffold …”, naquele momento foi gerado também o arquivo app/views/layouts/url_infos.html.erb.

Como este arquivo de layout[bb] tem o nome de um controlador, ele é utilizado apenas por este controlador, se o nome do arquivo fosse “application.html.erb” ele seria utilizado por todos os controladores da aplicação que não tivesse um layout próprio.

O conteúdo do arquivo é semelhante a qualquer outra view, uma coisa interessante de se reparar no nome do arquivo é que ele contem o “mime type” no nome, então se quisermos criar um “layout” para respostas XML vale a mesma lógica (application.xml.erb).

Vamos ver o conteúdo deste layout:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
  <title>UrlInfos: <%= controller.action_name %></title>
  <%= stylesheet_link_tag 'scaffold' %>
  <%= javascript_include_tag :defaults %>
</head>
<body>
 
<p style="color: green"><%= flash[:notice] %></p>
 
<%= yield %>
 
</body>
</html>

Foi adicionada a linha “<%= javascript_include_tag :defaults %>” que adiciona na página gerada a chamada para o javascript padrão do rails (que utiliza por padrão a biblioteca prototype).

O conteúdo das views é renderizado no lugar em que se encontra “<%= yield %>” neste arquivo de layout.

A última alteração que falta é impedir que sejam cadastradas URLs duplicadas ou em branco, para isto vamos voltar ao arquivo do model app/models/utl_info.rb

1
2
3
4
class UrlInfo < ActiveRecord::Base
	validates_uniqueness_of :href
	validates_presence_of :href
end

O suporte a validações do rails é bem flexível, e possui helpers para diversas validações, neste caso estamos garantindo que a URL seja única e esteja preenchida.

Bom, acho que era isto, temos um encurtador de URLs bem simples pronto. Esepro que o exemplo tenha sido útil para mostrar alguns dos recursos do Rails fugindo um pouco do exemplo padrão do blog.

Siga os links para a documentação do Rails, e lembre-se de programar sempre com o site da API do rails aberto em um browser.

PS.: o código completo para este exemplo esta disponível no github.