Blog do Urubatan
msgbartop
Desenvolvedor, Palestrante, Escritor, Nerd Assumido e Pai do Marcus :D
msgbarbottom

10 May 10 Personalizando o código gerado pelo Rails 3


Eu sempre digo, tanto no meu livro, como em palestras ou conversas por ai, que o código gerado pelo Scaffold do rails (e praticamente qualquer outro gerador de código por ai) só serve para fazer um quick start do desenvolvimento.

Uma excessão a isto, na minha opinião pelo menos, é o código gerado pelo “scaffold” do Grails, pelo simples fato que ele sempre permitiu que a aplicação alterasse os templates que seriam utilizados para gerar o código. No rails isto sempre foi mais complicado, era preciso criar um generator e duplicar todo o generator que você queria personalizar para algumas vezes mudar quase nada no código gerado, ou seja, na maior parte das vezes isto não valeria a pena mesmo …

Mas com o Rails 3 seus problemas acabaramse! (alusão sem graça nenhuma ao seu Creiçom do Casseta e Planeta)

O Rails 3 permite que você personalize de forma bastante fácil os templates utilizados pelos generators do rails, basta criar uma pasta templates dentro do diretório lib do projeto, e neste diretório copiar para lá o template original do rails e fazer as alterações que você achar interessante para o seu projeto.

Os templates do rails ficam dentro da Ruby Gem “railties”, dentro de lib/generators.

Neste diretório existem os grupos de generators (erb, rails e test_unit) e dentro destes, um diretório para cada generator e dentro deste um diretório templates. O conteúdo deste diretório templates deve ser copiado para RAILS_ROOT/lib/templates/<grupo>/<nome do generator>.

E pronto, as alterações que você fizer na copia do seu projeto vão refletir no resultado da próxima vez que você executar o generator …

Acho que isto ficou meio confuso certo?

Então é o seguinte, vamos supor que queiramos alterar o código das views geradas pelo generator “scaffold”. O código original vai estar no diretório
RAILTIES_GEM/lib/rails/generators/erb/scaffold/templates
Este diretório contem os templates que vão gerar os arquivos .html.erb, para personalizar o código gerado para o seu projeto, basta copiar o conteúdo deste diretório para
RAILS_ROOT/lib/templates/erb/scaffold (isto mesmo, sem o diretório templates)
Alterar o que você quiser, e pronto, tudo vai funcionar …

Mas vamos combinar que ficar copiando estes arquivos da trabalho né? Isto me faz sentir falta de uma task de nome “install-templates” do grails, que copiava todos os templates de geração de código para o diretório da aplicação para que fossem personalizados. E como sou preguiçoso demais para ficar copiando arquivos a mão, criei uma task rake que faz isto. Não empacotei em um plugin por que achei muito simples, mas se alguem achar interessante posso fazer isto :D
Enquanto isto, quem quiser copiar o código desta task, esta disponível no gist, é só copiar o código e colocar em um arquivo .rake no diretório lib/tasks da aplicação.

E para usar a task é só rodar: rake templates:copy

Ahh, mas pra que serve isto?
Bom, para diversas coisas, como por exemplo fazer com que o código gerado pelo scaffold chegue mais próximo de um cadastro real da sua aplicação, ou para fazer um exemplo simples, eu personalizei o controller.rb do scaffold_generator para utilizar o novo Responder do Rails 3, o template ficou assim:

E o controlador gerado, mantem exatamente a mesma funcionalidade, mas em vez das 84 linhas padrão de um controlador gerado pelo scaffold do rails, ele tem apelas 51 linhas. O código gerado ficou assim:

Se você quiser saber mais sobre o Responder, o Akita publicou um post sobre isto hoje no blog dele.

Espero que este post ajude vocês a trabalhar menos daqui pra frente.

PS.: Achei legal testar o Gist, mas se vocês não gostaram e preferirem o código embedded nos posts do blog como eu sempre faço, é só avisar que volto a colar o código por aqui mesmo :D

Tags: , , , , , ,

14 Feb 08 Escrevendo plugins para o Rails – dicas e truques – plugins com generators

Seguindo a seqüencia que eu iniciei aqui, mas alterando a ordem proposta, Vamos conversar um pouco sobre os Geradores de código do Rails.

Generators são uma das coisas mais legais do Rails!
Sim, você ja viu eles, lembra do primeiro screencast sobre rails que você viu? Sim, aquele mesmo em que um cara cria um CRUD em 1 minuto …

Generators são a “forma rails” de gerar código[bb] :D

Mas o código gerado não precisa ser apenas aquele CRUD, ele pode ser o que você quiser, e o Rails ja tem um suporte excelente para isto, pois este mesmo suporte ja é utilizado na base do Rails.

Beleza, mas por que eu iria querer escrever um gerador de código?
Pense sobre o projeto que você estaria começando hoje, lembre de todos aqueles formulários para CRUD que você precisara criar, ou aquela parte quase igual de todas as páginas da aplicação …
Agora pense em todas as aplicações da empresa que você trabalho …
Entendeu agora?

Sim, eu concordo que utilizar o “scaffold” padrão não é realmente útil, mas se esta geração de código inicial for feita seguindo os padrões da sua empresa, ela pode aumentar muito a velocidade de desenvolvimento inicial das aplicações …

Agora eu imagino que você esteja pensando: Legal, generators são show de bola, mas para de falar besteira e me mostre o código!

Então, vamos brincar um pouco[bb]
Vamos iniciar criando um novo projeto rails: rails plugins102
agora de dentro do diretório do projeto execute: script\generate plugin my_generator
(Se você esta pensando que você ja viu isto antes, provavelmente você leu o post anterior sobre escrita de plugins para o rails :D )

Agora que você a criou a estrutura para um novo plugin, vamos escrever algum código!
Para criar um novo generator, você precisa criar um diretório de nome “generators” dentro do diretório do plugin, um diretório com o nome do seu generator dentro deste, e um diretório de nome “templates” dentro do diretório do seu generator, por exemplo a minha estrutura ficou assim:

  • my_generator
    • lib
    • tasks
    • test
    • generators
      • test_gen
        • templates

Na verdade, você não precisa de um plugin para criar um generator, você pode colocar o seu novo generator dentro de qualquer um dos seguintes diretórios:

  • RAILS_ROOT/lib/generators
  • RAILS_ROOT/vendor/generators
  • RAILS_ROOT/vendor/plugins/plugin_name/generators
  • USER_HOME/.rails/generators
  • gems com nome terminado em _generator

Mas eu acho que um plugin é a orma mais fácil de começar, e vai ser mais fácil de utilizar nas suas aplicações também, mas claro que se você for utilizar isto em diversas aplicações um GEM seria melhor pois você teria apenas uma cópia do código para todo o servidor, mas como eu nunca criei um GEM vou continuar com o plugin pelo menos para este exemplo …

para o código do generator nos vamos criar um arquivo no diretório do generator de nome [nome_do_generator]_generator.rb, no meu caso o nome do arquivo ficou: my_generator/generators/test_gen/test_gen_generator.rb

Agora dentro deste arquivo precisamos criar a classe que define o generator, esta classe deve ter o nome de acordo com o nome do arquivo (seguindo os padrões do ruby), no meu caso o nome da classe precisa ser TestGenGenerator, e ela precisa extender uma das classes padrão para generators do rails (na verdade eu poderia não extender nada e implementar um monte de métodos, mas assim é bem mais fácil :D ), estas classes são:
Rails::Generator::Base ou Rails::Generator::NamedBase, eu vou utilizar NamedBase (A base para o controller generator do Rails), e vou criar um modelo de generator para uma migration apenas para mostrar como isto funciona …

NamedBase é uma ótima base para geradores que esperam parametros no formato: Nome [parametro1] [parametro2] …
Para todos os outros Base é uma melhor pedida …

O nosso código inicial é este:

1
2
3
4
5
6
7
class TestGenGenerator < Rails::Generator::NamedBase
  def manifest
    record do |m|
 
    end
  end
end

Nesta classe precisamos configurar o manifesto do generator e configurar quaisquer variáveis locais que utilizemos nos templates.

Mas com o código que nos temos, você ja pode executar: ruby script\generate test_gen asdas_dasda asd:ash (letras aleatórias como parâmetros por hora).
NamedBase ja vai configurar algumas variáveis para nós quando executarmos este comando:

  • class_name -> AsdasDasda
  • class_nesting ->
  • class_nesting_depth -> 0
  • class_path ->
  • file_path -> asdas_dasda
  • name -> asdas_dasda
  • plural_name -> asdas_dasdas
  • singular_name -> asdas_dasda
  • table_name -> asdas_dasdas
  • attributes -> #<Rails::Generator::GeneratedAttribute:0×3716418>
  • args -> asd:ash

Claro que por enquanto não estamos gerando absolutamente nada, por que o manifesto do gerador esta em branco, então vamos criar um exemplo simples para ver como isto realmente funciona …
Vamos criar um diretório de nome “dummy” dentro do diretório “template” e um arquivo em branco de nome “log.log” dentro deste, e vamos fazer as seguintes alterações no manifesto:

1
2
3
4
5
  def manifest
    record do |m|
      m.file 'dummy/log.log', "log/#{file_path}.log"
    end
  end

Este código vai dizer para o gerador copiar o nosso arquivo em branco para $APP_ROOT/log/asdas_dasda.log se executarmos o gerador com os mesmos parâmetros que antes …
Mas apenas copiar arquivos de um lugar para outro não é uma coisa muito divertida, então vamos brincar um pouco com o ERB, e vamos criar uma migration para o nosso plugin, para isto vamos alterar novamente o manifesto como no seguinte exemplo:

1
2
3
4
5
6
7
8
  def manifest
    @migration_name = "Create#{class_name}"
    @migration_action = "add"
    record do |m|
      m.file 'dummy/log.log', "log/#{file_path}.log"
      m.migration_template 'lib/mymigration.rb',"db/migrate", :migration_file_name => "create_#{file_path}"
    end
  end

Como você pode ver no código, estou informando ao gerador que eu tenho um arquivo de nome mymigration.rb dentro do diretório templates/lib, este template vai ser processado pelo ERB e o código gerado vai ser colocado no diretório “db/migrate”e vai ser chamado XXX_create_asdas_dasda.rb (XXX vai ser o número da migration gerado automaticamente pelo rails)

O conteúdo do arquivo mymigration.rb é o seguinte:

1
2
3
4
5
6
7
8
9
10
11
class <%= migration_name.underscore.camelize %> < ActiveRecord::Migration
  def self.up<% attributes.each do |attribute| %>
    <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type %><% end -%>
  <%- end %>
  end
 
  def self.down<% attributes.reverse.each do |attribute| %>
    <%= migration_action == 'add' ? 'remove' : 'add' %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'remove' %>, :<%= attribute.type %><% end -%>
  <%- end %>
  end
end

este é um template ERB que vai gerar código ruby.

Agora que ja temos um plugin razoavelmente útil , vamos ver o que mais é possível fazer …
para responder esta pergunta, vou copiar parte da documentação da classe Rails::Generator::Commands::Create, ou seja, todas as possibilidades de comandos para o manifesto do gerador:

  • class_collisions – Check whether the given class names are already taken by Ruby or Rails. In the future, expand to check other namespaces such as the rest of the user‘s app.
  • directory – Create a directory including any missing parent directories. Always directories which exist.
  • file – Copy a file from source to destination with collision checking.
  • identical? – Checks if the source and the destination file are identical. If passed a block then the source file is a template that needs to first be evaluated before being compared to the destination.
  • migration_template – When creating a migration, it knows to find the first available file in db/migrate and use the migration.rb template.
  • readme – Display a README.
  • route_resources – add a route to the routes.rb
  • template – Generate a file for a Rails application using an ERuby template. Looks up and evaluates a template by name and writes the result.

Acho que era isto, você pode fazer perguntas nos comentários se quiser, vou tentar responder todas (se é que alguem vai perguntar :D )

Para mais documentação sobre generators do rails siga os seguintes links:
http://wiki.rubyonrails.org/rails/pages/UnderstandingGenerators
http://www.aidanf.net/node/33
http://api.rubyonrails.org/classes/Rails/Generator/Base.html
http://api.rubyonrails.org/classes/Rails/Generator/NamedBase.html
http://api.rubyonrails.org/classes/Rails/Generator/Commands/Create.html

Espero que este passo a passo ajude alguem!
O próximo vai ser sobre testar o seu plugin! e o quarto eu ainda não pensei sobre o que vai ser, sugestões são bem vindas :D

Tags: , , , , , ,