Fernando Luizão

Desenvolvimento de software e nerdices em geral

Archive for January 2009

Rails: Autocompletes à moda RESTful

with one comment

Muita gente tem reclamado que o padrão RESTful do Rails é ruim, que limita, dificulta, etc. Eu não concordo, acho que limitações nos forçam a pensar em soluções mais simples. Realmente, manter-se RESTful não é tão simples quanto sair criando um monte de actions para qualquer coisa, principalmente nos casos de uso de ajax. Uma das reclamações que eu ouvi foi sobre dificuldade em usar autocompletes, por isso nesse artigo vamos ver como resolver esse “problema” e fazer um auto complete RESTful de forma simples e elegante.

Vamos lá, criando o projeto para teste e um resource Pessoa

rails restful_autocomplete
cd restful_autocomplete
script/generate scaffold pessoa nome:string
rake db:migrate

Instalando o plugin auto_complete:

script/plugin install git://github.com/rails/auto_complete.git

É necessário ter o git instalado, caso vc não tenha, ou não esteja com vontade de instalar, baixe o plugin aqui e descompacte em vendor/plugins.

Esse plugin possui uma macro para gerar a action de resposta do auto complete, mas como estamos usando um controller RESTful, não vamos usá-lo dessa maneira. Em vez disso, vamos criar uma mini-API para lidar com o problema. A idéia central é que TODAS as requisições que necessitem recuperar uma coleção de pessoas usem como ÚNICO ponto de acesso a action index.

O plugin espera os registros formatados dentro de uma lista não ordenada, por exemplo:

<ul>
	<li>Item 1</li>
	<li>Item 2</li>
        ...
	<li>Item N</li>
</ul>

Com isso em mente, vamos criar um novo mime-type para que nosso controller se encarregue de identificar a requisição do auto complete e devolver o formato correto. Adicione a seguinte linha ao arquivo config/initializers/mime_types.rb:

Mime::Type.register_alias "text/html", :autocomplete

Estamos registrando o tipo customizado “autocomplete” como HTML. Lembre-se de reiniciar o servidor depois de alterar o arquivo.

Agora criaremos um controller qualquer que fará a requisição pelo auto complete:

script/generate controller testes new

Na view testes/new.html.erb, coloque o seguinte:

<%=
    text_field_with_auto_complete :bla, :ble, { :size => 25 }, {
      :url => formatted_pessoas_path(:autocomplete),
      :method => :get,
      :param_name => 'nome'
    }
%>

Passamos algumas opções ao helper text_field_with_auto_complete fornecido pelo plugin:

  • Na linha 3 indicamos a url (ou melhor, o RECURSO) à qual a requisição será ser feita e o mime-type que desejamos (o “autocomplete” que nós criamos). A url gerada será /pessoas.autocomplete
  • Na linha 4 estamos dizendo que queremos que a requisição seja feita pelo método GET (LEMBRE-SE: A action index só é acessivel por GET em um controller RESTful)
  • Finalmente na linha 5 especificamos com qual nome o conteúdo do text_field será enviado. O valor do parâmetro depois poderá ser acessado com params[:nome].

A action index do controller pessoas (nossa “API”), responsável pelo trabalho sujo, ficará assim:

def index
  conditions = ["nome LIKE ?", "%#{params[:nome]}%"] if params[:nome]
  @pessoas = Pessoa.all :conditions => conditions

  respond_to do |format|
    format.html # index.html.erb
    format.xml  { render :xml => @pessoas }
    format.autocomplete  { render :inline => "<%= auto_complete_result @pessoas, :nome %>" }
  end
end

A idéia básica de uma API, é que ela receba parâmetros, processe-os, e devolva um resultado. Nossa API é extremamente simples, se receber um parâmetro “nome”, faz a pesquisa pelos nomes que forem similares, senão retorna todos os registros. Podemos incrementar nossa API e adicionar várias outras coisas, como mais opções para pesquisa (pesquisa por idade, por CPF…), limite de registros a serem retornados, paginação, e o que mais for preciso. Essa é a maior vantagem do padrão RESTful, em vez de uma action, você ganha uma API :).

A linha 7 é que faz a mágica de responder ao auto complete. Ela devolve os resultados da pesquisa no formato que o auto complete espera, na forma de lista não ordenada, usando o helper auto_complete_result.

Inicie o servidor, e acesse a url http://localhost:3000/testes/new e teste o auto complete.

Pronto, temos um auto complete RESTful! =)

Lembre-se, o modelo RESTful veio para facilitar nossa vida, não para complicar. RESTful restringe visando simplificar o design de nossas aplicações e nos forçar a construir APIs que nós mesmos consumimos (e obviamente, também ser consumidas por terceiros). Limitações são impostas apenas por nós mesmos, por isso, pare de se limitar :).

NOTA 1: Não esqueça de incluir os javascripts necessários no seu layout, senão não vai funcionar:

<%= javascript_include_tag :defaults %>

NOTA 2: Os helpers de rotas formatados serão removidos nas próximas versões do Rails. Em vez de:

formatted_pessoas_path(:autocomplete)

Ficará:

pessoas_path(:format => :autocomplete)

Então atenção à versão do Rails que vc está usando. Mais detalhes aqui.

Written by fernandoluizao

January 24, 2009 at 1:53 pm

Posted in Rails

Tagged with , ,

FormBuilders: Padronizando seus formulários no Rails

with 3 comments

A um ano atrás, o Urubatan escreveu um artigo sobre como diminuir a repetição nas views e padronizar formulários. A abordagem dele foi escrever helpers para encapsular o formulário dentro de uma tabela (não vou entrar no mérito do uso de tabelas para formatação, não sou xiita :)). Se você não leu o artigo dele, recomendo que leia. Vou revisitar o artigo dele e apresentar um recurso do Rails que muita gente desconhece (ou ignora): FormBuilders.

O que é um FormBuilder?

Como o nome sugere, um FormBuilder é uma classe para auxiliar a construção de formulários. É essa classe que nos oferece vários métodos que usamos diariamente em nossas views, como text_field, check_box, e vários outros. A maioria das pessoas usa esses métodos sem saber de onde eles vem. Para entender melhor a estrutura do form builder padrão do Rails, recomendo a leitura desse artigo.

Criando nosso FormBuilder

Para criarmos um builder personalizado, temos duas alternativas: implementar os métodos da classe ActionView::Helpers::FormBuilder OU estender essa classe. A segunda é obviamente mais fácil e rápida, e para atingirmos nosso objetivo vamos usá-la. Queremos que nosso FormBuilder nos permita trocar isso (retirado do exemplo do Urubatan):

<% form_for(@example) do |f| %>
  <table>
    <tr>
      <td>Name</td>
      <td><%= f.text_field :name %></td>
    </tr>
    <tr>
      <td>Url</td>
      <td><%= f.text_field :url %></td>
    </tr>
    <tr>
      <td colspan="2"><%= f.submit "Update" %></td>
    </tr>
</table>
<% end %>

Por isso:

<% form_for(@example) do |f| %>
  <%= f.text_field :name %>
  <%= f.text_field :url %>
  <%= f.submit "Update" %>
<% end %>

Vamos ao trabalho!

class MyFormBuilder < ActionView::Helpers::FormBuilder

  # vamos embulhar esses helpers em uma tr
  %w&#91;text_field password_field text_area&#93;.each do |method_name|
    define_method(method_name) do |field_name, *args|
      options = args.extract_options!

      # cria o label dentro de uma td
      label = @template.content_tag(:td, label(field_name, options&#91;:label&#93; || field_name.to_s.humanize))

      # cria o elemento dentro de uma td
      input = @template.content_tag(:td, super)

      # cria uma tr com o label e o input criados
      @template.content_tag(:tr, label + input)
    end
  end

  def submit(value = "Save changes", options = {})
    button = @template.content_tag(:td, super, :colspan => 2)
    @template.content_tag(:tr, button)
  end

end

Essa classe pode ser colocada em um arquivo dentro do diretório lib e requerida no environment. Implementei apenas alguns helpers apenas para demonstração, mas outros helpers (check_box, select, etc) também podem ser personalizados a gosto. Agora podemos testar a renderização do formulário usando nosso builder:

<% form_for(@example, :builder => MyFormBuilder) do |f| %>
  <%= f.text_field :name %>
  <%= f.text_field :url %>
  <%= f.submit "Update" %>
<% end %>

Humm, nada de diferente… olhando o html gerado podemos ver que esta correto, os campos estão dentro de tr’s, porém não estão dentro de uma tabela. Vamos sobrescrever o helper form_for para conseguir isso:

module ActionView
  module Helpers
    module FormHelper

      def form_for(record_or_name_or_array, *args, &proc)
        raise ArgumentError, "Missing block" unless block_given?

        options = args.extract_options!

        case record_or_name_or_array
        when String, Symbol
          object_name = record_or_name_or_array
        when Array
          object = record_or_name_or_array.last
          object_name = ActionController::RecordIdentifier.singular_class_name(object)
          apply_form_for_options!(record_or_name_or_array, options)
          args.unshift object
        else
          object = record_or_name_or_array
          object_name = ActionController::RecordIdentifier.singular_class_name(object)
          apply_form_for_options!([object], options)
          args.unshift object
        end

        concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}))
        concat('<table>')
        fields_for(object_name, *(args << options), &proc)
        concat('</table>')
        concat('</form>')
      end

    end
  end
end

Monkey patching não é a melhor coisa do mundo, mas nesse caso não vi outra solução melhor e achei válido usar. No artigo do Urubatan ele adicionou as tags “table” dentro do próprio formulário. Se tivesse que alterar a marcação no futuro, teria que ser alterado em vários formulários para tirar as tags table. O monkey patch introduz risco de quebrar o sistema em caso de mudanças no Rails, mas acho que vale mais a pena alterar apenas um arquivo do que um monte de formulários :).

Testando novamente… funcionou :). Muito bom, agora vamos criar um plugin para reusar esse form builder em outros projetos:

script/generate plugin my_builder
      create  vendor/plugins/my_builder/lib
      create  vendor/plugins/my_builder/tasks
      create  vendor/plugins/my_builder/test
      create  vendor/plugins/my_builder/README
      create  vendor/plugins/my_builder/MIT-LICENSE
      create  vendor/plugins/my_builder/Rakefile
      create  vendor/plugins/my_builder/init.rb
      create  vendor/plugins/my_builder/install.rb
      create  vendor/plugins/my_builder/uninstall.rb
      create  vendor/plugins/my_builder/lib/my_builder.rb
      create  vendor/plugins/my_builder/tasks/my_builder_tasks.rake
      create  vendor/plugins/my_builder/test/my_builder_test.rb
      create  vendor/plugins/my_builder/test/test_helper.rb

No arquivo vendor/plugins/my_builder/lib/my_builder.rb coloque o a nossa classe MyFormBuilder e também a reimplementação do form_for. No arquivo vendor/plugins/my_builder/init.rb, coloque o seguinte conteúdo:

require 'my_builder'

# define como o FormBuilder padrao
ActionView::Base.default_form_builder = MyFormBuilder

A última linha serve justamente para não termos que especificar o builder em cada form que fizermos, definindo nosso builder como o padrão.

Conclusões

FormBuilders são relativamente simples para personalizar e permitem remover muito código repetido de forma elegante, além de facilitar a manutenção. Pena que muita gente nem sabe que eles existem… espero que esse artigo tenha mostrado as possibilidades de uso dos builders e incentive o pessoal a usá-los mais. Quem quiser exemplos mais concretos, basta pesquisar no github, tem alguns plugins por lá :).

Bons estudos!

Written by fernandoluizao

January 19, 2009 at 7:43 pm

Posted in Rails

Dica: Retornando vários valores em Ruby

with one comment

Em algumas situações precisamos retornar mais de um valor em um método. Já cheguei a ver gente usando OpenStruct, ou pior ainda, montando uma estrutura à parte só pra retornar alguns valores. Nesses casos, é mais rápido e limpo retornar os valores em um array (ou um hash, dependendo da situação). Um exemplo:

def metodo
  [valor1, valor2]
end

Pegando os valores no retorno do método:

valor1, valor2 = metodo

É uma dica simples, mas que ajuda a limpar o código em algumas situações

Written by fernandoluizao

January 16, 2009 at 3:45 pm

Posted in Ruby

Criei um blog. E agora?

leave a comment »

Olá, meu nome é Fernando Luizão. Trabalho como desenvolvedor e ultimamente tenho trabalhado bastante com Ruby, Rails e desenvolvimento web em geral. Pretendo escrever sobre isso e algumas outras coisas que me interessam, como metodologias ágeis de desenvolvimento e outras linguagens de programação também. Espero que seja útil para alguém =).

Written by fernandoluizao

January 7, 2009 at 11:58 pm

Posted in Uncategorized