Nesta aula iremos aprender como definir classes e métodos em runtime, veremos onde podemos utilizar esta técnica para automatizar mais ainda o trabalho de um programador.

Metaprogramming - Classes e métodos dinâmicos


Hoje falaremos sobre:

  • eval
  • define_method
  • object.const_get
  • send

Nós já falamos sobre em Ruby ser um objeto, e sobre utilizarmos o PascalCase.


Aula Prática

Primeiro vamos aprender como criar uma classe dinâmica, para isso criaremos um arquivo classe_dinamica.rb, escreva o código abaixo dentro deste arquivo.

        
        require 'byebug'
        debugger
        # Definir Classe em runtime
        classe = "danilo"
        classe = classe.capitalize
        eval("class #{classe} end")
        classe = Object.const_get(classe)
        puts classe
        
      

No exemplo acima vimos como utilizar o método .capitalize necessário para atribuirmos as classes dinamicamente com o nome certo. Ao invés de classe = classe.capitalize você pode fazer classe.capitalize! onde o ! já executa na referência da classe.

Na linha com eval é onde criamos a nossa classe dinamicamente, abaixo disso temos agora a variável classe que recebe um objeto criado, todas classes são constantes, por causa da letra maiúscula no começo.

Crie um arquivo metodo_dimamica.rb, já vimos este assunto em aulas anteriores, mas vamos rever sobre runtime, aqui definimos uma classe e um método estático, que dentro dele está definindo um método em tempo de execução que será recebido como parâmetro do método estático.

        
        require 'byebug'

        # Definir Metodo em runtime
        class Teste
          def self.definir(nome_metodo)
            define_method(nome_metodo) do 
              puts "metodo em runtime"
            end
          end
        end

        Teste.definir("teste")

        Teste.new.teste

        debugger

        x = ""
        
      

Agora veremos a definição de várias classes em runtime, crie um arquivo classes_dimamica.rb, escreva o código abaixo e execute.

        
        require 'byebug'

        def definir_classe(classe)
          classe = classe.capitalize
          eval("class #{classe} end")
          classe = Object.const_get(classe)
        end

        ["Classe1", "Classe2", "Classe3"].each do |classe|
          definir_classe(classe)
        end

        debugger

        x = ""
        
      

Conseguimos também definir métodos em lote, através de um array por exemplo, em cada loop será definido um método novo.

      
      require 'byebug'

      # Definir Vários métodos em runtime
      class Teste
        def self.definir(*metodos)
          metodos.each do |metodo|
            define_method(metodo) do 
              puts "metodo em runtime"
            end
          end
        end
      end

      Teste.definir("metodo1", "metodo2", "metodo3")
      a = Teste.new
      puts a.methods - Class.methods

      
      

Juntando tudo que aprendemos até agora podemos ver como fica a definição de classes e métodos em runtime. Note que temos um método definir_classe que recebe como parâmetro um nome de classe e um método

      
      require 'byebug'

      #definir classe e metodos em runtime
      def definir_classe(classe, nome_metodo)
        if(classe.is_a?(String))
          classe = classe.capitalize
          eval("class #{classe} end");
          classe = Object.const_get(classe)
        end
        classe.class_eval do
          define_method(nome_metodo) do |*params|
            puts "o valor dos parametros é: #{params.join(", ")}"
          end
        end
      end
      # debugger
      ["mostrar", "exibir", "visualizar"].each do |metodo|
        definir_classe("danilo2", metodo)
      end

      ["mostrar", "exibir", "visualizar"].each do |metodo|
        Danilo2.new.send(metodo, 1,2,3)
      end

      # chamando todos dinamicos
      ["Danilo", "Sheila", "Lana"].each do |classe|
        ["mostrar", "exibir", "visualizar"].each do |metodo|
          definir_classe(classe, metodo)
          Object.const_get(classe).new.send(metodo, 1,2,3)
        end
      end

      
    

Observe que quando for testar usando o debugger é necessário criar uma instância com o new.

Definindo classes classes e métodos para essas classes dinamicamente

        
        require 'byebug'

        #definir classe e metodos em runtime
        def definir_classe(classe, nome_metodo)
          if(classe.is_a?(String))
            classe = classe.capitalize
            eval("class #{classe} end");
            classe = Object.const_get(classe)
          end
          classe.class_eval do
            define_method(nome_metodo) do |*params|
              puts "o valor dos parametros é: #{params.join(", ")}"
            end
          end
        end
        ["mostrar", "exibir", "visualizar"].each do |metodo|
          definir_classe("danilo2", metodo)
        end
        ["mostrar", "exibir", "visualizar"].each do |metodo|
          Danilo2.new.send(metodo, 1,2,3)
        end
        # chamando todos dinamicos
        ["Danilo", "Sheila", "Lana"].each do |classe|
          ["mostrar", "exibir", "visualizar"].each do |metodo|
            definir_classe(classe, metodo)
            Object.const_get(classe).new.send(metodo, 1,2,3)
          end
        end

        
        

Agora vamos aprender como definir várias classes e metodos em runtime utilizando hash, pode criar um arquivo novo para testar o código abaixo.

        
        require 'byebug'

        #definir várias classes e metodos em runtime utilizando hash
        def definir_classe(classe, nome_metodo)
          if(classe.is_a?(String))
            eval("class #{classe} end");
            classe = Object.const_get(classe)
          end
          classe.class_eval do
            define_method(nome_metodo) do |*params|
              puts "o valor dos parametros é: #{params.join(", ")}"
            end
          end
        end
        {
          "Danilo" => ["mostrar", "exibir", "visualizar"],
          "Sheila" => ["mostrar", "exibir", "visualizar"],
          "Lana" => ["mostrar", "exibir", "visualizar"]
        }.each do |classe, metodos|
          metodos.each do |metodo|
            definir_classe(classe, metodo)
          end
        end
        puts Danilo.new.methods - Class.methods
        puts Sheila.new.methods - Class.methods
        puts Lana.new.methods - Class.methods
        # Danilo.new.mostrar 1,2,3
        # Danilo.new.exibir 1,2,3
        # Danilo.new.visualizar 1,2,3
        
        debugger
    
        x = ""

        
      
Conclusão

Como visto nessa série de metaprogramação conseguimos gerar código facilmente com o Ruby, todas estratégias aqui visam otimizar o tempo e aumentar a nossa produtividade enquanto programadores.


Arquivo aula.rb final:

        
          require 'byebug'
          #==================================
          # Definir Classe em runtime
          classe = "danilo"
          classe = classe.capitalize
          eval("class #{classe} end")
          classe = Object.const_get(classe)
          puts classe
          #==================================
          # Definir Metodo em runtime
          class Teste
            def self.definir(nome_metodo)
              define_method(nome_metodo) do
                puts "metodo em runtime"
              end
            end
          end
          Teste.definir("teste")
          Teste.new.teste
          #==================================
          # Definir Várias classes em runtime
          def definir_classe(classe)
            classe = classe.capitalize
            eval("class #{classe} end")
            classe = Object.const_get(classe)
          end
          ["Classe1", "Classe2", "Classe3"].each do |classe|
            definir_classe(classe)
          end
          #==================================
          # Definir Vários metodos em runtime
          class Teste
            def self.definir(*metodos)
              metodos.each do |metodo|
                define_method(metodo) do
                  puts "metodo em runtime"
                end
              end
            end
          end
          Teste.definir("metodo1", "metodo2", "metodo3")
          a = Teste.new
          puts a.methods - Class.methods
          #==================================
          #definir classe e metodos em runtime
          def definir_classe(classe, nome_metodo)
            if(classe.is_a?(String))
              classe = classe.capitalize
              eval("class #{classe} end");
              classe = Object.const_get(classe)
            end
            classe.class_eval do
              define_method(nome_metodo) do |*params|
                puts "o valor dos parametros é: #{params.join(", ")}"
              end
            end
          end
          ["mostrar", "exibir", "visualizar"].each do |metodo|
            definir_classe("danilo2", metodo)
          end
          ["mostrar", "exibir", "visualizar"].each do |metodo|
            Danilo2.new.send(metodo, 1,2,3)
          end
          # chamando todos dinamicos
          ["Danilo", "Sheila", "Lana"].each do |classe|
            ["mostrar", "exibir", "visualizar"].each do |metodo|
              definir_classe(classe, metodo)
              Object.const_get(classe).new.send(metodo, 1,2,3)
            end
          end
          #==================================
          #definir várias classes e metodos em runtime utilizando hash
          def definir_classe(classe, nome_metodo)
            if(classe.is_a?(String))
              eval("class #{classe} end");
              classe = Object.const_get(classe)
            end
            classe.class_eval do
              define_method(nome_metodo) do |*params|
                puts "o valor dos parametros é: #{params.join(", ")}"
              end
            end
          end
          {
            "Danilo" => ["mostrar", "exibir", "visualizar"],
            "Sheila" => ["mostrar", "exibir", "visualizar"],
            "Lana" => ["mostrar", "exibir", "visualizar"]
          }.each do |classe, metodos|
            metodos.each do |metodo|
              definir_classe(classe, metodo)
            end
          end
          puts Danilo.new.methods - Class.methods
          puts Sheila.new.methods - Class.methods
          puts Lana.new.methods - Class.methods
          # Danilo.new.mostrar 1,2,3
          # Danilo.new.exibir 1,2,3
          # Danilo.new.visualizar 1,2,3
          # debugger
      
          x = ""
        
      

Arquivo classe_dimamica.rb final:

        
          require 'byebug'
          #==================================
          # Definir Classe em runtime
          classe = "danilo"
          debugger
          classe.capitalize!
          eval("class #{classe} end")
          classe = Object.const_get(classe)
          puts classe
        
      

Arquivo classes_dimamica.rb final:

        
          require 'byebug'
          # Definir Várias classes em runtime
          def definir_classe(classe)
            classe = classe.capitalize
            eval("class #{classe} end")
          end
          ["Classe1", "classe2", "Classe3"].each do |classe|
            definir_classe(classe)
          end
          debugger
          x = ""
        
      

Arquivo classes_e_metodos_dinamicos.rb final:

        
          require 'byebug'
          #definir classe e metodos em runtime
          def definir_classe(classe, nome_metodo)
            if(classe.is_a?(String))
              classe = classe.capitalize
              eval("class #{classe} end");
              classe = Object.const_get(classe)
            end
            classe.class_eval do
              define_method(nome_metodo) do |*params|
                puts "o valor dos parametros é: #{params.join(", ")}"
              end
            end
          end
          ["mostrar", "exibir", "visualizar"].each do |metodo|
            definir_classe("danilo2", metodo)
          end
          ["mostrar", "exibir", "visualizar"].each do |metodo|
            Danilo2.new.send(metodo, 1,2,3)
          end
          # chamando todos dinamicos
          ["Danilo", "Sheila", "Lana"].each do |classe|
            ["mostrar", "exibir", "visualizar"].each do |metodo|
              definir_classe(classe, metodo)
              Object.const_get(classe).new.send(metodo, 1,2,3)
            end
          end
          debugger
          x = ""
        
      

Arquivo classes_e_metodos_dinamicos_com_hash.rb final:

        
          require 'byebug'
          #definir várias classes e metodos em runtime utilizando hash
          def definir_classe(classe, nome_metodo)
            if(classe.is_a?(String))
              eval("class #{classe} end");
              classe = Object.const_get(classe)
            end
            classe.class_eval do
              define_method(nome_metodo) do |*params|
                puts "o valor dos parametros é: #{params.join(", ")}"
              end
            end
          end
          {
            "Danilo" => ["show1", "show2", "show3"],
            "Sheila" => ["print1", "print2", "print3"],
            "Lana" => ["puts1", "puts2", "puts3"]
          }.each do |classe, metodos|
            metodos.each do |metodo|
              definir_classe(classe, metodo)
            end
          end
          puts Danilo.new.methods - Class.methods
          puts Sheila.new.methods - Class.methods
          puts Lana.new.methods - Class.methods
          debugger
          x = ""
        
      

Arquivo metodo_dimamica.rb final:

        
          require 'byebug'
          # Definir Metodo em runtime
          class Teste
            def self.definir(nome_metodo)
              define_method(nome_metodo) do
                puts "metodo em runtime"
              end
            end
          end
          Teste.definir("teste")
          Teste.new.teste
          debugger
          x = ""
        
      

Arquivo metodos_dimamica.rb final:

        
          require 'byebug'
          # Definir Vários metodos em runtime
          class Teste
            def self.definir(*metodos)
              metodos.each do |metodo|
                define_method(metodo) do
                  puts "metodo em runtime"
                end
              end
            end
          end
          Teste.definir("metodo1", "metodo2", "metodo3")
          a = Teste.new
          puts a.methods - Class.methods
          debugger
          x = ""
        
      




Próximas Aulas


Metaprogramming - Alias para métodos e atributos

Nesta aula iremos aprender a renomear ou duplicar métodos de forma din...

Metaprogramming - Hooks

Nesta aula iremos aprender os conceitos de hooks, veremos como utiliza...

Metaprogramming - Missings

Nesta aula iremos aprender a utilizar os comandos const_missing e meth...

Instalando Rails

Nesta aula iremos aprender a instalar e configurar o Rails que é um do...

Rails Generators

Nesta aula iremos aprender um pouco sobre os generators do Rails, util...

Rails - Routes

Nessa aula iremos iniciar uma aplicação web do zero, agora passo a pas...

Rails - Partials

Nesta aula iremos aprender como dividir o conteúdo de nossos templates...

Rails - Migrations

Nesta aula iremos aprender a diferença de fazer um modelo de forma man...



Danilo

Arquiteto de software, analista, programador, professor. Danilo criou o projeto torne-se um programador, para passar o seu conhecimento para a nova geração. Com o intuito de ser um bom pai, Danilo trabalha muito motivado para garantir o futuro de sua filha.

ELEVE SEUS GANHOS E POTENCIALIZE SUA PERFORMANCE

Receba nossa Ebook de LÓGICA DE PROGRAMAÇÃO

© Didox Business & Technology - CNPJ: 12.127.195/0001-14