Tagview Tecnologia

    Soluções web

    Browsing Posts in Ruby

    Recentemente, vi uma discussão no grupo de emails “rails-br” sobre como extender a classe String em uma aplicação Ruby on Rails. Na Tagview, costumamos utilizar muito este recurso em nossos projetos e acredito que fazemos isto de uma maneira bastante limpa e legível. Neste post vou detalhar este processo.

    Vou utilizar no exemplo um código que adiciona um método à classe String. Mas, gostaria de ressaltar que podemos extender qualquer classe do Ruby ou do Rails com este mesmo procedimento. Suponha que queremos adicionar um método de instância à classe String. Este método retornará a própria string, mas sem acentos. Vamos chamo-lo de “no_accent”. Lembrando que nem Ruby nem Rails implementam algum método que faça isto.

    Primeiramente, vamos definir um módulo que contenha este nosso método. Vamos chamá-lo de “StringExtensions”.

    module StringExtensions
      def no_accent
        self.
            gsub(/á/,  'a').     # à => a
            gsub(/á/,  'a').     # á => a
            gsub(/â/,  'a').     # â => a
            gsub(/ã/,  'a').     # ã => a
            gsub(/é/,  'e').     # é => e
            gsub(/ê/,  'e').     # ê => e
            gsub(/í/,  'i').       # í => i
            gsub(/ó/,  'o').     # ó => o
            gsub(/ô/,  'o').     # ô => o
            gsub(/ã/,  'o').     # õ => o
            gsub(/ú/,  'u').     # ú => u
            gsub(/ü/,  'u').     # ü => u
            gsub(/ç/,  'c').      # ç => c
            gsub(/À/,  'A').     # À => A
            gsub(/Á/,  'A').     # Á => A
            gsub(/Â/,  'A').     # Â => A
            gsub(/Ã/,  'A').     # Ã => A
            gsub(/É/,  'E').      # É => E
            gsub(/Ê/,  'E').      # Ê => E
            gsub(/Í/,  'I').        # Í => I
            gsub(/Ó/,  'O').     # Ó => O
            gsub(/Ô/,  'O').     # Ô => O
            gsub(/Õ/,  'O').     # Õ => O
            gsub(/Ú/,  'U').      # Ú => U
            gsub(/Ü/,  'U').      # Ü => U
            gsub(/Ç/,  'C')       # Ç => C
      end
    end
    

    Agora, basta incluir este módulo na classe String, certo? Sim, porém isto não é tão simples. O método de classe “include” é privado. Uma técnica interessante para burlar este problema é o uso do método “send” em cima do objeto que representa a classe String. Este método não é privado e aceita como argumentos: um método a ser chamado e os parametros a serem passados para este método. Assim, podemos incluir nosso módulo StringExtensions a classe String com o seguinte código:

    String.send :include, StringExtensions

    Para adicionar toda esta lógica à nossa aplicação basta criarmos um arquivo string_extensions.rb na pasta “config/initializers”. Neste arquivos adicionaremos nosso módulo e na última linha o código acima. Quando a aplicação for executada os arquivos “.rb” desta pasta serão executados em ordem alfabética e para toda string de nossa aplicação poderemos chamar o método “no_accent”.

    Esta é uma dica bastante simples porém muito útil. Extender classes pode ser um ótimo começo para um código mais clean, facil de manter e componentizável (imagine que em um próximo projeto RoR você pode reutilizar seus initializers).

    Abraços e até a próxima!

    Um método bem interessante do rails é o returning. Apesar de ser bem simples, ele ajuda a manter seu código mais limpo e mais fácil de entender (depois de pegar costume :P ).
    Funciona mais ou menos assim:

    Dado 2 argumentos, um objeto qualquer (arg1) e um bloco, o método repassa o objeto arg1 como parâmetro do bloco e ao final da execução do bloco, retorna o arg1 que pode ter sofrido alteração ou não.

    Por exemplo, o seguinte método:

    def published_titles(posts)
      titles = {}
    
      posts.each do |p|
        titles.merge!(p.id => p.title) if p.published?
      end
    
      titles
    end
    

    Poderia ser reescrito assim:

    def published_titles(posts)
      returning(Hash.new) do |titles|
        posts.each { |p| titles.merge!(p.id => p.title) if p.published? }
      end
    end
    

    É isso. ;)


    UPDATE
    O Ceolin comentou comigo hoje que o returning seria “deprecated” no Rails 3. Isso é porque a partir do Ruby 1.9, a classe Object ganha um novo método (tap) que permite o desenvolvedor interagir em uma chamada de métodos em cadeia, por exemplo:

    Users
      .all .tap {|users| puts "Usuários: #{users.map(&id)}"}
      .last.tap {|user| puts "Último usuário: #{user.name}"}
      .posts
    

    Então, o nosso primeiro método poderia ser escrito, no Ruby 1.9, assim:

    def published_titles(posts)
      Hash.new.tap do |titles|
        posts.each { |p| titles.merge!(p.id => p.title) if p.published? }
      end
    end