Ruby Metaprogramming ==================== Aqui estão os exercícios do workshop de Ruby Metaprogramming. Para executá-los, você precisará ter o Ruby instalado. Utilize, preferencialmente, a versão 1.9; afinal, não queremos que o Ruby se torne o PHP4 pelos próximos 5 anos. Todos os exercícios possuem testes, que foram divididos por dia de workshop. O arquivo test_day_1.rb se refere ao dia 1, o arquivo test_day_2.rb se refere ao dia 2, e assim sucessivamente. Os testes podem ser executados com o comando rake spec Cada um dos arquivos pode ser executado individualmente com rake spec:day_1 rake spec:day_2 rake spec:day_3 rake spec:day_4 rake spec:day_5 Faça os testes passarem, criando arquivos equivalentes como files/day_1.rb files/day_2.rb e assim sucessivamente. Você pode ver os testes para fazer a sua implementação. As soluções propostas dos exercícios estão no branch `solution`. ----------------------------------------------- Atualizando para a última versão dos exercícios ----------------------------------------------- Você não deve editar nenhum arquivo para fazer os testes passarem. Apenas adicione as respostas aos arquivos `files/day_*.rb`. Primeiro, faça um commit com suas respostas. Execute os comandos abaixo: git add files git commit -m "Uma mensagem de commit" Depois, para aplicar as últimas atualizações dos exercícios à sua cópia local, execute o comando abaixo à partir da raíz do diretório dos exercícios. git pull --rebase Rode os testes e corrija sua implementação caso algum deles falhe. -------------------------- Instalando as dependências -------------------------- Para instalar as dependências, instale o Bundler (http://rubygems.org/gems/bundler) e execute o comando bundle install ------------------------------------- Dia 1: self, classe Singleton e Class ------------------------------------- 1. O Ruby 1.9 implementa o método BasicObject#singleton_class, que retorna a classe Singleton de um objeto. Adicione o método Object#metaclass de modo que ele faça a mesma coisa. 2. Defina um módulo que implemente os métodos App.description e App.description= utilizando a classe Singleton. 3. Implemente um método no namespace global chamado new_class, que deve retornar uma classe criada dinamicamente. Esta classe deve herdar de uma outra classe chamada Person. O comportamente seria semelhante a class SomeClass < Person end 4. Crie uma nova classe dinâmica, cujo nome deve ser "Awesome". 5. Crie um método no namespace global chamado new_method, que deve adicionar um método chamado hello à instância do objeto que ele receberá como argumento. Este método hello deve retornar uma string como "Hello from <CLASSE> instance". A string <CLASSE> deve retornar o nome da classe do objeto. Você deve utilizar apenas o receiver; não use os métodos instance_eval, class_eval, eval ou define_method. A assinatura deste método é def new_method(object) end -------------- Dia 2: Methods -------------- 1. Crie um método no namespace global chamado send_message. Este método deverá instanciar a classe Message e executar o método Message#deliver dinamicamente. Esta classe é definida pelo teste. A assinatura deste método é def send_message end 2. Crie um método no namespace global chamado send_message_with_args. Este método deverá executar dinamicamente o método Message.send, que sobrescreveu a implementação original. O método Message.send espera dois argumentos; uma instância da classe Message e um número inteiro de 1 a 3, que indica a prioridade da mensagem. A assinatura deste método é def send_message_with_args end 3. Crie um método no namespace global chamado send_private_method. Este método deverá executar dinamicamente o método privado Message#prepare. A assinatura deste método é def send_private_method end 4. Crie um método no namespace global chamado send_public_method. Este método deverá executar dinamicamente apenas métodos públicos; você deve executar o método Message#prepare e esta chamada deverá lançar a exceção `NoMethodError`. A assinatura deste método é def send_public_method end 5. Crie uma classe chamada Song, que possui 3 atributos: - title - artist - duration O método construtor desta classe pode receber um hash e cada um dos itens desse hash deve ser definir o atributo de mesmo nome. Um exemplo de uso desta classe pode ser Song.new( :title => "Linoleum", :artist => "NOFX", :duration => "2:10" ) 6. Adicione um método String#to_leet, que irá converter uma string para sua versão Leet (http://en.wikipedia.org/wiki/Leet). Utilize o mapeamento abaixo para substituir cada um dos caracteres pelo seu equivalente. LEET = { "a" => "4", "e" => "3", "i" => "1", "o" => "0", "u" => "μ" } Ao executar `"banana".to_leet` devemos receber como resultado a string `b4n4n4`. 7. Adicione o método Object.leet_attr, que permitirá criar atributos como os exemplos abaixo. class Person leet_attr :name, :blog, :twitter end seria o equivalente a definir os atributos como class Person attr_accessor :name, :blog, :twitter def n4me name.to_s.to_leet end def bl0g blog.to_s.to_leet end def tw1tt3r twitter.to_s.to_leet end end Você deve utilizar o método define_method para adicionar o método que retorna a string em leet. 8. Semelhante ao exercício anterior, adicione o método Object.reverse_attr, que permitirá criar atributos como os exemplos abaixo. class Person reverse_attr :name, :blog, :twitter end seria o equivalente a definir os atributos como class Person attr_accessor :name, :blog, :twitter def eman name.to_s.reverse end def golb blog.to_s.reverse end def rettiwt twitter.to_s.reverse end end Você deve utilizar o método class_eval para adicionar o método que retorna a string reversa. 9. Remova o método Car.useless, já que ele não faz nada. A classe Car foi definida pelo teste. 10. A classe Ferrari, que herda da classe Car, implementa o método color, sobrescrevendo a implementação original. Remova o método Ferrari#color de modo que o retorno seja a implementação original. 11. Assim como o exercício anterior, a classe Ferrari também implementa o método Ferrari#engine. Remova o método Ferrari#engine de qualquer chamada ao método, mesmo que presente em uma superclasse, lance a exceção NoMethodError. 12. O método Calc.sum, implementado pelo teste, soma dois números. No entanto, este método lança uma exceção se passando um valor nil. Redefina este método convertendo qualquer valor não numérico 0 antes de enviar estes números para a implementação original. Utilize o método define_method em conjunto com os métodos instance_method e bind. ---------------------------------------- Dia 3: Code evaluation, Modules & Mixins ---------------------------------------- 1. Adicione dinamicamente um novo método de instância usando o método Module.class_eval. Este método deve receber uma string e retornar uma versão em letras maiúsculas. Você deve definir o nome do arquivo e linha onde esse método foi criado. A assinatura deste método é module Helper def upcase(string) end end 2. Crie um método chamado extend_object que deve receber um objeto e um módulo. Este módulo deve ser usado para estender o objeto. A assinatura deste método é def extend_object(object, mod) end 3. Adicione dinamicamente um novo método de classe no módulo Helper usando o método Module.class_eval e a classe Singleton. Este método deve receber uma string e retornar uma versão em letras minúsculas. A assinatura deste método é module Helper def self.downcase(string) end end 4. Crie um método chamado execute_block que deve receber um objeto e um bloco. Este bloco deve ser executado no contexto do objeto. Qualquer objeto pode ser passado para este método: classes, módulos, instâncias de classes... A assinatura deste método é def execute_block(object, block) end 5. Crie um método chamado execute_private que irá executar o método privado Tools.prepare sem utilizar o método send. A assinatura deste método é def execute_private end module Tools private def self.prepare end end 6. Crie um módulo Multipler que implementa um único método multiply. Este método deve multiplicar cada um dos itens de um array por um número. A assinatura deste método é module Multiplier def multiply(multiplier) end end Se eu injetar este módulo na classe Array, posso utilizá-lo como [1,2,3].multiply(2) e o resultado será [2,4,6] ------------------------------------------ Dia 4: Constantes, Callable Object e Hooks ------------------------------------------ 1. Crie um método set_constant que receberá um símbolo. Este símbolo deverá ser normalizado como uma constante e adicionando ao namespace Object. A assinatura do método é def set_constant(name, value) end Uma chamada como set_constant(:my_const, 1) Deverá criar a constante MY_CONST com o valor 1. Se eu tentar atribuir esta constante com o mesmo valor, o retorno do método deverá ser false. Se eu tentar atribuir com um valor diferente, a constante deverá receber este novo valor, sem lançar nenhuma mensagem de warning. 2. Crie um mapeamento para o hash abaixo, onde a chave é o nome do método. Caso uma chave inexistente seja passada, a exceção NoMethodError deverá ser lançada. module Color COLORS = { :red => "ff0000", :blue => "0000ff", :green => "00ff00", :black => "000000", :white => "ffffff" } end Os métodos deverão estar disponíveis como sendo de classe. Color.red #=> "ff0000" Lembre-se de atualizar o método respond_to?. 3. Rastreie todas as subclasses que herdaram da classe Monster. Os nomes destas subclasses devem estar disponíveis através do método Monster.ugly_monsters. 4. Toda vez que um método de instância contendo a palavra "fuck" for adicionado à classe Monster, lance a exceção Monster::NoBadWordsError. 5. Implemente a DSL à seguir. car = Car.new do price 225_000 name "Ferrari 458 Italia" wheels 4 color :red brand "Ferrari" maximum_speed 321 end car.name #=> "Ferrari 458 Italia" car.respond_to?(:doors) #=> false car.doors = 2 car.respond_to?(:doors) #=> true car.doors #=> 2 A DSL acima não deve possuir nenhum atributo pré-definido. Você deve interceptar o hook method_missing e adicionar cada um dos atributos em tempo de execução.