Ir para o conteúdo
ou

Software livre Brasil

 Voltar a Blogosfera d...
Tela cheia Sugerir um artigo

Thiago Freire: Como fazer um plugin - Parte 1

29 de Maio de 2009, 0:00 , por Software Livre Brasil - 0sem comentários ainda | Ninguém está seguindo este artigo ainda.
Visualizado 1147 vezes

Bom, todo mundo fica dizendo que os plugins são a melhor parte de rails, que a comunidade participa muito, bla bla bla. Decidi então mostrar como fazer um plugin. Aliás, decidi aprender. Não tem muito material sobre isso, principalmente em português. Esse post foi escrito depois de ter lido:

Como é um assunto meio grande, decidi dividir em algumas partes. Nessa primeira, vou mostrar como estender uma classe de Ruby, criar um generator pra gerar migrations e adicionar um acts_as nos models, para adicionar métodos e outras coisas. Como exemplo, vou criar um plugin que adicionará algumas funcionalidades simples de loja à aplicação, chamado little_store. Para isso, ele precisa de um resource Product. Não vou utilizar testes, para que o post não fique muito grande. Talvez eu fale sobre testar plugins em outra oportunidade. É importante notar que algumas coisas que vou mostrar não são as melhores formas de se fazer, como estender BigDecimal pra criar um método que gere uma saída formatada em Real. Já existem helpers prontos que fazem isso. Os exemplos são só pra ilustrar o que estou dizendo, esse plugin não é útil :)

[Continue lendo]

Bom, todo mundo fica dizendo que os plugins são a melhor parte de rails, que a comunidade participa muito, bla bla bla. Decidi então mostrar como fazer um plugin. Aliás, decidi aprender. Não tem muito material sobre isso, principalmente em português. Esse post foi escrito depois de ter lido:

Como é um assunto meio grande, decidi dividir em algumas partes. Nessa primeira, vou mostrar como estender uma classe de Ruby, criar um generator pra gerar migrations e adicionar um acts_as nos models, para adicionar métodos e outras coisas. Como exemplo, vou criar um plugin que adicionará algumas funcionalidades simples de loja à aplicação, chamado little_store. Para isso, ele precisa de um resource Product. Não vou utilizar testes, para que o post não fique muito grande. Talvez eu fale sobre testar plugins em outra oportunidade. É importante notar que algumas coisas que vou mostrar não são as melhores formas de se fazer, como estender BigDecimal pra criar um método que gere uma saída formatada em Real. Já existem helpers prontos que fazem isso. Os exemplos são só pra ilustrar o que estou dizendo, esse plugin não é útil :)

Iniciando

A primeira coisa a se fazer é gerar a aplicação, e verificar se está tudo certo:

  
    rails littlestore
    cd littlestore
    script/generate scaffold product name:string, description:text
    rake db:migrate
    script/server
  

Depois, é preciso gerar o plugin:

  
    script/generate plugin littlestore --with-generator
  

Essa opção, --with-generator, irá criar um modelo para os seus generators. Se seu plugin não for ter um generator, ela é desnecessária. Essa é a estrutura gerada:

  
    littlestore
    |-- generators/
        |-- littlestore
            |-- littlestore_generator.rb
        |-- templates/
        |-- USAGE
    |-- init.rb
    |-- install.rb
    |-- lib/
    |   |-- littlestore.rb
    |-- MIT-LICENSE
    |-- Rakefile
    |-- README
    |-- tasks/
    |   |-- littlestore_tasks.rake
    |-- test
    |   |-- littlestore_test.rb
    |-- uninstall.rb
  
  • init.rb Esse arquivo é carregado toda vez que a aplicação inicia. Você precisa fazer alguns require aqui :) . Bom notar que nem sempre ele é utilizado. O plugin do RSpec, por exemplo, não o usa, pois não faz sentido o plugin ser carregado antes de você ter usado o generator.
  • install.rb Carregado somente no momento da instalação.
  • lib Nesse diretório fica o código base do plugin. Esse é o padrão, podem ser criadas outras pastas. É sempre bom dividir em alguns arquivos. Ou até pastas, a depender do tamanho do plugin.
  • MIT-LICENSE Ele gera um modelo de licensa pra você. Obviamente, você não precisa usá-la, pode fazer o que quiser! Ao contrário da GPL :(
  • tasks As rake tasks ficam nesse diretório.
  • test Os testes ficam nesse diretório. Se quiser usar RSpec, tem o rspec-plugin-generator

Estendendo uma classe

É fácil estender uma classe em Ruby. Criarei um método em BigDecimal, que é a classe correspondente no rails pra o :decimal das migrations. O método vai apenas formatar o valor pra que ele saia com duas casas decimais e um R$ na frente. Pra isso, criamos um arquivo no diretório lib.

vendor/plugins/littlestore/lib/core_extensions.rb

  
    BigDecimal.class_eval do
      def to_real
        "R$ #{self.to_f.round(2)}".gsub(".", ",")
      end
    end
  

É recomendável usar class_eval ao invés de redefinir com class diretamente. Também é preciso adicionar o arquivo no init.rb

vendor/plugins/littlestore/init.rb

  
    require 'core_extensions'
  

Pode-se testar com o console do Rails:

  
    script/console
    >> d = BigDecimal.new(73.3492.to_s)
    => #<bigdecimal:23e2b44>
    >> d.to_real
    => "R$ 73,35"
  

Generator que gera migrations

Novamente, o diretório dos generators:

  
    |-- generators/
        |-- littlestore
            |-- littlestore_generator.rb
        |-- templates/
        |-- USAGE
  

O diretório templates/ contém templates feitos pelo usuário, que podem ser utilizados pra criar generators. E o arquivo USAGE serve pra definir o que irá aparecer quando o usuário chamar o generator sem parâmetros, ou seja, como utilizar o plugin, como o nome já diz :)

Quero criar um generator que será chamado por script/generate littlestore Product, onde Product é o model que sofrerá as mudanças (na verdade a tabela desse model que será modificada, certo?). Para isso eu edito o arquivo que foi gerado pelo generator que criou o plugin:

vendor/plugins/littlestore/generators/littlestore_generator.rb

  
    class LittlestoreGenerator < Rails::Generator::NamedBase
      def manifest
        record do |m|
          m.migration_template "migration:migration.rb", "db/migrate", {:assigns => littlestore_assigns, 
            :migration_file_name => "add_littlestore_fields_to_#{custom_file_name}"}
        end
      end

      private
        def custom_file_name
          custom_name = class_name.underscore.downcase
          custom_name = custom_name.pluralize if ActiveRecord::Base.pluralize_table_names
        end

        def littlestore_assigns
          returning(assigns = {}) do
            assigns[:migration_action] = "add"
            assigns[:class_name] = "add_littlestore_fields_to_#{custom_file_name}"
            assigns[:table_name] = custom_file_name
            assigns[:attributes] = [Rails::Generator::GeneratedAttribute.new("price", "decimal")]
            assigns[:attributes] << Rails::Generator::GeneratedAttribute.new("quantity", "integer") 
          end
        end
    end
  

Todo generator precisa precisa de um Manifest. E o método record é usado pra criar esse Manifest. Geralmente se cria usando templates. Nesse caso, utilizei o migration_template, já que quero que ele gere uma migration. É bom testar se o pluralize está ativo, para não haver erros com nomes de tabelas. Note também que utilizo um método pra definir o hash assigns, apenas pra ficar mais organizado.

Como dá pra ver, essa migration irá adicionar as colunas price e quantity na tabela do model que foi dado como parâmetro. No meu caso, criei com Product:

  
    script/generate littlestore Product
  

Esses campos adicionais irão servir pra que o acts_as_buyable funcione.

Criando um acts_as

vendor/plugins/littlestore/lib/acts_as_buyable.rb

  
    module LittleStore
      module ClassMethods
        def acts_as_buyable
          send :include, InstanceMethods
        end
      end

      module InstanceMethods
        def buy(qt)
          return "We don't have enough items to fullfill your order" if qt > self.quantity
          self.quantity -= qt
        end
      end

      def self.included(receiver)
        receiver.send :extend, ClassMethods
      end
    end
  

Simples, não? self.included é chamado quando o módulo LittleStore é incluído, e o parâmetro é a classe ou módulo que incluiu o LittleStore. Então, receiver irá herdar o módulo ClassMethods, e o método acts_as_buyable estará disponível :) . Tendo em mente que ClassMethods são os métodos de classe (duh!), como Product.currency, e InstanceMethod os das instâncias, como @product.price, é possível implementar várias coisas aqui. Dá também pra definir atributos, usando cattr_accessor e write_attribute.

É preciso adicionar o arquivo ao init.rb:

vendor/plugins/littlestore/init.rb

  
    require 'acts\_as_buyable'
    ActiveRecord::Base.send :include, LittleStore
  

Essa linha do ActiveRecord não é necessária, mas pra cada model onde o acts_as_buyable será utilizado funcione, é preciso incluir o módulo com include LittleStore.

Na próxima parte mostrarei como fazer routes e helpers customizadas nos plugins. Além de como gerar um RDoc e alguns outros assuntos :)


Fonte: http://feedproxy.google.com/~r/freireag/~3/rHN3MHnXHk4/como-fazer-um-plugin-parte-1

0sem comentários ainda

Enviar um comentário

Os campos são obrigatórios.

Se você é um usuário registrado, pode se identificar e ser reconhecido automaticamente.