Ruby on Rails 2.2 O QUE HÁ DE NOVO? Primeira Edição
Ruby on Rails 2.2 O QUE HÁ DE NOVO? Primeira Edição por Carlos Brando traduzido por Carl Youngblood
Copyright 2008 Carlos Brando. Todos os direitos reservados. Primeira Edição: Outubro 2008 Carlos Brando http://www.nomedojogo.com Carl Youngblood http://blog.youngbloods.org
Ruby on Rails 2.2 INTRODUÇÃO O Ruby on Rails 2.2 está cheio de novidades, incluindo novas funcionalidades, melhorias e correções de bugs antigos. Neste livro você encontrará uma breve descrição, acompanhada de um exemplo (na maioria dos casos) de cada uma das principais novidades deste versão. Escrever este livro foi um trabalho árduo, por isto espero que ele lhe seja muito útil, ajudando-o a usar mais plenamente cada novo recurso incluído no Ruby on Rails. À partir desta versão o Rails passa a ser políglota. Fazo uso do novo sistema de internacionalização (i18n) podemos construir aplicativos para usuários do mundo com pouquíssimo esforço. Muito trabalho foi feito para deixar o Rails thread-safety e o mais preparado possível para o futuro Ruby 1.9. Também houve uma preocupação grande para deixá-lo mais compatível com o JRuby. Embora o recurso de thread-safe ainda não esteja disponível para todos nós, já que ele apenas funcionará em máquinas virtuais com suporte a threads nativas, como o JRuby por exemplo, ele é uma grande adição ao framework. Se no passado houve muita reclamação quanto a documentação do Rails, agora ninguém poderá mais reclamar. Um grande trabalho foi feito para documentar o código e as funcionalidades do Rails. Quer um exemplo? Execute no terminal: rake doc:guides Esta tarefa criará uma pasta doc/guides na raiz do seu projeto com vários guias para auxiliá-lo durante o aprizado do Rails. 7
Ruby on Rails 2.2 - O que há de novo? Capitulo 1 ActiveRecord NOVA OPÇÃO PARA ASSOCIAÇÕES: :VALIDATE Uma nova opção foi acrescentado às associações do Rails. A opção validação de objetos associados ao modelo. Veja um exemplo: :validate class AuditLog < ActiveRecord::Base belongs_to :developer, :validate => false log = AuditLog.create(:developer_id => 0, :message => "") log.developer = Developer.new puts log.developer.valid? # => false 8 pode ser usada para ligar ou desligar a
Capitulo 1: ActiveRecord puts log.valid? # => true puts log.save # => true Como você pode ver no exemplo acima, embora o objeto associado ( Developer) não seja válido, ainda assim o objeto principal (AuditLog) foi salvo no banco de dados. Este não era o comportamento normal nas versões anteriores do Rails, onde um objeto pai só poderia ser gravado se todos os filhos fossem válidos. Embora no exemplo acima estamos desligando a validação de associações, para demostrar o novo recurso, este é o novo valor padrão para este tipo de relacionamento de agora em diante. Ou seja, todas as validações em associações belongs_to estarão desligadas (como no exemplo) e para habilitarmos o comportamento antigo devemos usar a expressão :validate => true. UMA NOVA FORMA DE ESPECIFICAR CONDITIONS USANDO HASH Ao realizar buscas no banco de dados, por vezes temos de fazer uso da opção :joins a fim de melhorar a performance de nosso aplicativo, em outros casos precisamos simplesmente recuperar algum tipo de informação que depe do resultado de duas tabelas. Por exemplo, se desejássemos recuperar todos os usuários do sistema que compraram itens da cor vermelha, faríamos algo assim: User.all :joins => :items, :conditions => ["items.color =?", 'red'] Este tipo de sintaxe parece incomodar já que você precisa incluir o nome da tabela (no caso items) dentro de uma string. O código parece estranho. 9
Ruby on Rails 2.2 - O que há de novo? No Rails 2.2 encontraremos uma novidade nesta questão, nos permitindo fazer a mesma coisa de uma forma um pouco diferente, usando uma chave dentro do Hash para identificar a tabela: User.all :joins => :items, :conditions => { :age => 10, :items => { :color => 'red' } } # um outro exemplo que talvez deixe o código mais claro User.all :joins => :items, :conditions => { :users => { :age => 10 }, :items => { :color => 'red' } } Na minha opinião, desta forma o código fica muito mais claro, principalmente se temos de condicionar muitos campos de várias tabelas. Só tenha em mente que a chave usada é o nome da tabela (você percebe pelo nome pluralizado) ou um alias caso você o tenha especificado na query. NOVA OPÇÃO :FROM PARA MÉTODOS DE CÁLCULO DO ACTIVERECORD Uma nova opção foi incluída aos métodos de cálculos do ActiveRecord (count, sum, average, minimum e maximum). Ao fazer uso da opção :from, podemos sobrecarregar o nome da tabela na query gerada pelo ActiveRecord, o que não parece muito útil em um primeiro momento. Mas algo interessante que esta opção nos permite fazer é forçar o MySQL a usar um índice especifico ao realizar o cálculo desejado. Veja alguns exemplos de uso: 10
Capitulo 1: ActiveRecord # Forçando o MySQL a usar um índice para realizar o cálculo Edge.count :all, :from => 'edges USE INDEX(unique_edge_index)', :conditions => 'sink_id < 5') # Realizando o cálculo em uma tabela diferente da classe associada Company.count :all, :from => 'accounts' MÉTODO MERGE_CONDITIONS DO ACTIVERECORD AGORA É PÚBLICO O método merge_conditions do ActiveRecord agora é um método público, o que significa que ele estará presente em todas os seus modelos. Este método faz exatamente o que o nome diz. Você pode informar várias e ele junta tudo em uma condition só. Por exemplo: conditions separadas em seus parâmetros class Post < ActiveRecord::Base a = { :author => 'Carlos Brando' } b = [ 'title =?', 'Edge Rails' ] Post.merge_conditions(a, b) # => "(\"posts\".\"author\" = 'Carlos Brando') AND (title = 'Edge Rails')" Note que ele une as conditions com um AND, sempre. 11
Ruby on Rails 2.2 - O que há de novo? DEFININDO COMO O MÉTODO VALIDATES_LENGTH_OF DEVE FUNCIONAR O método validates_length_of faz parte dos muitos métodos de validação contidos no ActiveRecord. Este método em particular serve para garantir que o valor gravado em uma determinada coluna no banco de dados terá um tamanho máximo, mínimo, exato, ou até mesmo se está em um intervalo de valores. Mas o termo "tamanho" é relativo. Hoje quando dizemos "tamanho" estamos nos referindo a quantidade de caracteres no texto. Mas imagine um caso onde eu tenha um campo em um formulário onde a limitação não seja definida pela quantidade de caracteres e sim pela quantidade de palavras, algo como "escreva um texto com no mínimo 100 palavras". Imagine uma página onde o usuário tenha de redigir uma redação, por exemplo. Hoje, para validar isto não teríamos outra escolha senão criarmos um novo método que faça esta validação. Mas à partir do Rails 2.2 poderemos personalizar o método validates_length_of para funcionar da forma como desejamos usando a opção :tokenizer. Veja um exemplo que resolveria o problema citado acima: validates_length_of :essay, :minimum => 100, :too_short => "Sua redação deve ter no mínimo %d palavras."), :tokenizer => lambda { str str.scan(/\w+/) } Este é apenas um exemplo do que podemos fazer com esta nova opção. Além disso podemos usa-lá para contar apenas a quantidade de dígitos, menções de uma única palavra, etc.. 12
Capitulo 1: ActiveRecord TRATANDO A OPÇÃO :LIMIT COMO BYTES À partir desta versão do Rails quando usarmos a opção :limit para colunas com números inteiros, em nossas migrations, estaremos nos referindo ao número de bytes, no MySQL e no PostgreSQL (no sqlite sempre foi assim). O tipo da coluna no banco de dados deperá da quantidade de bytes especifica. Veja o trecho de código que identifica o tipo da coluna para o MySQL: when when when when when else 1; 'tinyint' 2; 'smallint' 3; 'mediumint' nil, 4, 11; 'int(11)' # compatibility with MySQL default 5..8; 'bigint' raise(activerecorderror, "No integer type has byte size #{limit}") E para o PostgreSQL: when 1..2; 'smallint' when 3..4, nil; 'integer' when 5..8; 'bigint' INFORMANDO OUTRO PRIMARY_KEY EM ASSOCIAÇÕES Uma nova opção foi acrescentado aos métodos has_many e has_one: a opção :primary_key. Fazo uso desta opção podemos definir qual método do modelo associado retornará a chave primária que será usada na associação. Obviamente o método padrão é o id. Veja um exemplo de uso: 13
Ruby on Rails 2.2 - O que há de novo? has_many :clients_using_primary_key, :class_name => 'Client', :primary_key => 'name', :foreign_key => 'firm_name' O método has_one funciona exatamente como no exemplo acima. NOVO HELPER PARA TESTES (ASSERT_SQL) Talvez você já conheça o método assert_queries que ajuda a validar nos testes a quantidade de queries executadas. Por exemplo: assert_queries(firm.partial_updates?? 0 : 1) { firm.save! } No teste acima estou afirmando que se houver caso contrário nenhuma deve ser executada. partial_updates uma query deve ser executada no banco de dados, Agora ganhamos mais um helper para ajudar a testar o tipo de query executada, o método assert_sql. Um exemplo: def test_empty_with_counter_sql company = Firm.find(:first) assert_sql /COUNT/i do assert_equal false, company.clients.empty? No exemplo acima estou confirmando que no bloco informado ao método pelo menos uma query deve conter a palavra COUNT. Obviamente você pode ser mais especifico na expressão regular que estiver usando. Vamos pegar um outro exemplo: 14
Capitulo 1: ActiveRecord assert_sql(/\(\"companies\".\"id\" IN \(1\)\)/) do Account.find(1, :include => :firm) MIGRATIONPROXY Imagine que ao rodar uma série de migrations um determinado modelo seja renomeado. Agora imagine que antes disto acontecer uma outra migration faça referencia a este modelo. Isto causará um erro feio e parará a execução de suas migrations. Para evitar este tipo de problema foi criado uma nova classe chamada MigrationProxy que armazena o nome, a versão e o nome do arquivo de cada migration e usa estas informações para carregar a migration somente quando ela for necessária, evitando carregar todas elas de uma vez. POOL DE CONEXÕES NO RAILS Algo que muita gente reclama sobre o Rails é que ele é lento. Sabemos que isto não é totalmente verdade, mas também sabemos que muita coisa pode ser feita para melhorar a performance dele. Uma destas coisas acabou de se feita. Foi incluído ao Rails um Pool de Conexões com o banco de dados. Toda vez que uma requisição ao banco de dados é feita, perde-se algum tempo criando uma nova conexão e só depois é que a pesquisa ou gravação é realizada. Olhando superficialmente pode parecer algo muito rápido e simples, mas abrir uma conexão com o banco de dados não é tão simples assim. É necessário estabelecer uma conexão física com o servidor do banco, autenticar a conexão e realizar mais uma série de validações. Tudo isto consome recursos e tempo. Após criar esta conexão, o Rails a usa para todas as requisições necessárias, e queries mais pesadas podem atrasar a 15
Ruby on Rails 2.2 - O que há de novo? execução de outras requisições. Isto explica muito bem o porque de o banco de dados se tornar o vilão de alguns grandes projetos criados em Rails. A solução para este tipo de problema é criar um pool de conexões com o banco de dados e distribuir as requisições feitas entre estas conexões. O processo é o seguinte: Uma conexão com o banco de dados é aberta e utilizada para realizar uma pesquisa. Depois disso, ao invés de fecha-la, ela é armazena no pool de conexões. Quando uma outra requisição é feita, ao invés de abrir uma nova conexão, o sistema reaproveita uma que já foi aberta, diminuindo o tempo e os recursos necessários para realizar a tarefa. Várias conexões podem ser armazenadas no pool ao mesmo tempo e as requisições serão distribuídas entre elas. Isto significa que mesmo com uma querie lenta so executada no banco de dados, a aplicação continuará recebo e executando outras queries usando as demais conexões armazenadas no pool. No Rails será criado um novo pool para cada execução do método establish_connection. Em outras palavras, cada banco de dados cadastrado no arquivo database.yml terá seu próprio pool de conexões. O pool começa vazio e vai cresco até o limite de 5 conexões (padrão), mas você pode aumentar este limite acrescentando a opção pool na configuração do banco de dados. development: adapter: mysql host: localhost username: myuser password: mypass database: somedatabase pool: 10 wait_timeout: 15 16
Capitulo 1: ActiveRecord Se nenhuma das conexões estiver disponível, uma thread irá esperar durante 5 segundos (padrão) antes de desistir de esperar por uma conexão. Este tempo também pode ser configurado adicionando a chave wait_timeout na configuração do banco de dados. Se você desejar usar o pool de conexões fora do ActionPack, existe o método ActiveRecord::Base.connection_pool que permite que você manualmente faça o checkout/checkin das conexões. Não esqueça de fazer o checkin quando terminar de usar a conexão. connection = ActiveRecord::Base.connection_pool.checkout # faz alguma coisa no banco de dados ActiveRecord::Base.connection_pool.checkin(connection) Você também pode usar o método ActiveRecord::Base.connection_pool.with_connection que já faz o checkout/ checkin automaticamente para você, tornando-se uma opção mais segura. ActiveRecord::Base.connection_pool.with_connection do connection # faz alguma coisa no banco de dados MIGRATIONS TRANSACIONAIS NO POSTGRESQL Quando uma migration está em execução e um erro ocorre, tudo que já foi executado será aplicado ao banco de dados, mas tudo que estiver após o erro, não será aplicado. Além disso a migration será marcada como concluída. Isto pode dar uma certa dor de cabeça para corrigir. 17
Ruby on Rails 2.2 - O que há de novo? Mas, se o banco de dados que você estiver usando tiver suporte a DDL rollbacks em transações, então ele pode fazer uso deste recurso para desfazer tudo que foi feito antes do erro. O problema é que nem todos os bancos de dados possuem este recurso. O MySQL, por exemplo não possuí. Mas o PostgreSQL, SQL Server e outros bancos possuem. Neste caso o código do Rails foi atualizado para permitir o uso de transações em migrations quando você estiver usando estes bancos de dados. Embora o Rails permita este recurso, o adapter do banco deve ser atualizado (apenas fazo com que o método supports_ddl_transactions? retorne true) para fazer uso de transações. Até o lançamento deste livro somente o do PostgreSQL parecia ter sido atualizado. NOVA VERSÃO DESTRUTIVA DOS MÉTODOS DE PESQUISA DO ACTIVERECORD Os métodos dinâmicos de pesquisa do ActiveRecord receberam uma versão destrutiva, que dispara um erro do tipo RecordNotFound caso nenhum registro seja encontrado, ao invés de apenas retornar nil como acontece com a versão original. Para usar esta versão destrutiva, basta adicionar o sinal de exclamação no final do método. Veja um exemplo: Topic.find_by_title!("The First Topic!") # => ActiveRecord::RecordNotFound MELHORANDO A PERFORMANCE NOS MÉTODOS ASSOCIATION_IDS Se você tiver dois modelos: Post e Comment. Onde um post tem muitos (has_many) comentários. Se você executar: 18
Capitulo 1: ActiveRecord Post.first.comment_ids O Rails usará a seguinte query para recuperar os ids: SELECT * FROM `comments` WHERE (`comments`.post_id = 1) Mas neste caso, não precisamos dos objetos inteiros. A seguinte query seria mais do que suficiente para o funcionamento deste método, além de possuir uma performance melhor: SELECT `comments`.id FROM `comments` WHERE (`comments`.post_id = 1) Tanto para associações has_many, como para associações has_many incluir esta melhora de performance a partir desta versão. :through o código do Rails foi alterado para MAIS UM FINDER DINÂMICO Aumentando o número de finders dinâmicos do find_by e o find_all_by. ActiveRecord, agora temos o find_last_by. Já tínhamos os famosos Além de simplificar, ficou muito mais elegante recuperar o último comentário feito por um usuário, por exemplo. Veja: Comment.find_last_by_author("Carlos Brando") # é a mesma coisa que Comment.last(:conditions => { :author => "Carlos Brando" }) 19
Ruby on Rails 2.2 - O que há de novo? SUPORTE À OPÇÃO :LIMIT NO MÉTODO UPDATE_ALL O método update_all agora também funciona com a opção :limit. Isto é muito bom porque garante que ao usar o update_all em associações has_many que façam uso da opção :limit tudo funcionará adequadamente. NOVAS OPÇÕES PARA O MÉTODO COMPOSED_OF O método composed_of recebeu duas novas opções: :constructor e :converter. A opção :constructor pode receber um símbolo com o nome de um método ou um Proc. Por padrão, a classe de composição é criada através do método new, recebo todos os atributos mapeadas como parâmetros, exatamente na ordem que em foram mapeados. Se por algum motivo a sua classe não aceitar esta convenção, você deve fazer uso da opção :constructor. Com ela você pode alterar a forma como sua classe deve ser criada. Veja um exemplo retirado da própria documentação do Rails: composed_of :ip_address, :class_name => 'IPAddr', :mapping => %w(ip to_i), :constructor => Proc.new { ip IPAddr.new(ip, Socket::AF_INET) } No exemplo, como você pode ver, ao criar uma nova instancia da classe IPAddr é necessário informar mais um parâmetro ao construtor. Fazo uso da opção :constructor isto se torna bem simples. Quanto a opção :converter, ela também aceita um símbolo que represente um método da classe informada na opção :class_name ou um Proc, e é disparado quando um valor diferente de uma instância da classe informada for passado para a propriedade criada, o que torna necessário uma conversão. Mais um exemplo: 20
Capitulo 1: ActiveRecord composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new { balance Money.parse(balance) } No exemplo acima o método balance= sempre estará esperando por uma instancia da classe Money, mas caso um outro tipo de objeto seja informado ele deverá ser convertido usando o método parse do objeto Money. Com esta nova opção não devemos mais usar o bloco de conversão que o método permitia antes, a conversão deve ser feita através do uso da opção :converter. ATUALIZANDO UMA ASSOCIAÇÃO ATRAVÉS DE SUA FOREIGN KEY Não sei dizer se isto é um bug ou não, mas na minha opinião isto representava um problema. Veja o código abaixo, onde tento alterar a conta de um usuário usando sua foreign key em um projeto Rails 2.1 ou anterior: class User < ActiveRecord::Base belongs_to :account user = User.first # => #<User id: 1, login: "admin", account_id: 1> user.account # => #<Account id: 1, name: "My Account"> user.account_id = 2 # => 2 21
Ruby on Rails 2.2 - O que há de novo? user.account # => #<Account id: 1, name: "My Account"> Note que estou alterando a conta do usuário, mas a associação não foi atualizada. Mesmo depois de salvar o objeto user, se ele não for recarregado, a associação continuará mostrando a conta errada. No Rails 2.2 este problema foi corrigido. Veja: class Comment < ActiveRecord::Base belongs_to :post comment = Comment.first # => #<Comment id: 1> >> comment.post # => #<Post id: 1> >> comment.post_id = 2 # => 2 >> comment.post # => #<Post id: 2> Veja que ao alterar o post por meio de sua foreign key, automaticamente a associação foi atualizada. ALIAS_ATTRIBUTE FUNCIONANDO COM DIRTY OBJECTS Para enter esta alteração, vamos precisar analisar o mesmo código so executado em uma versão mais antiga do Rails e depois nesta nova versão. Vamos pegar um modelo como exemplo: 22
Capitulo 1: ActiveRecord class Comment < ActiveRecord::Base alias_attribute :text, :body Note que estou usando o método alias_attribute para criar um alias para o atributo body com o nome de text. Na teoria este método deveria replicar todos os métodos de leitura, escrita, pesquisa e qualquer outro que envolva o atributo body. Mas vejamos um exemplo so executado no Rails 2.1 ou anterior: c = Comment.first # => #<Comment id: 1, body: "my comment"> c.body # => "my comment" c.text # => "my comment" c.body = "a new message" # => "a new message" c.body_changed? # => true c.text_changed? # => NoMethodError: undefined method `text_changed?'... Ao executar o método text_changed? temos um erro, porque o alias_attribute não estava replicando os métodos de rastreamento, mas isto já foi corrigido. Veja o mesmo código executado agora em um projeto Rails 2.2: c = Comment.first # => #<Comment id: 1, body: "my comment"> c.body 23
Ruby on Rails 2.2 - O que há de novo? # => "my comment" c.text # => "my comment" c.body = "a new message" # => "a new message" c.body_changed? # => true c.text_changed? # => true c.text_change # => ["my comment", "a new message"] NOVO MÉTODO DE INSTÂNCIA MODEL#DELETE Para tornar o ActiveRecord mais consistente foi adicionado o método de instância Model#delete. Ele é similar ao método de classe com o mesmo nome. O método delete, diferente do método destroy, apaga o registro do banco de dados sem disparar callbacks, como o before_destroy e o after_destroy. Este método também não aplicará nenhuma das regras impostas na associação através de cláusulas como client = Client.find(1) client.delete 24 :depent.
Capitulo 1: ActiveRecord TORNANDO ATRIBUTOS DO ACTIVERECORD PRIVADOS No Rails 2.2 você poderá definir atributos do ActiveRecord como private. Como estes atributos são criados via metaprogramação, até agora isto era impossível. Para enter como isto funcionará, vamos tornar o atributo name da classe User privado: class User < ActiveRecord::Base private def name "I'm private" Agora ao tentar recuperar o valor do atributo name: user = User.first # => #<User id: 1, name: "teste"> user.name # => NoMethodError: undefined method `NoMethodError' for #<User:0x234df08> Veja que uma exceção NoMethodError foi disparada ao executar o método que agora é privado. Por outro lado eu posso alterar o nome do usuário, já que o método name= é ainda público. user.name = "Carlos" # => "Carlos" 25
Ruby on Rails 2.2 - O que há de novo? Capitulo 2 ActiveSupport ARRAY#SECOND ATÉ ARRAY#TENTH No objeto Array já tínhamos o método first e last, então porque não ter também os métodos second, third, fourth e assim por diante? É isso mesmo, foram acrescentados ao objeto Array os métodos que vão do second (segundo) até o tenth (décimo), que servem para retornar o objeto especifico dentro do Array (o terceiro objeto do array, por exemplo). Vamos aos exemplos: array = (1..10).to_a array.second array.third array.fourth 26 # => array[1] # => array[2] # => array[3]
Capitulo 2: ActiveSupport array.fifth array.sixth array.seventh array.eighth array.ninth array.tenth # # # # # # => => => => => => array[4] array[5] array[6] array[7] array[8] array[9] NOVO MÉTODO ENUMERABLE#MANY? Um novo método foi adicionado ao módulo Enumerable: many?. E como o nome mesmo diz, ele verifica se a coleção possui mais de um objeto, ou em outras palavras se tem muitos objetos associados. Este método é um alias para collection.size > 1. Vamos ver alguns exemplos: >> [].many? # => false >> [ 1 ].many? # => false >> [ 1, 2 ].many? # => true Além deste formato dado nos exemplos, este método também recebeu uma nova implementação permitindo que ele aceite blocos, que funciona exatamente como o método any?. Vamos aos exemplos: >> x = %w{ a b c b c } # => ["a", "b", "c", "b", "c"] 27
Ruby on Rails 2.2 - O que há de novo? >> x.many? # => true >> x.many? { y y == 'a' } # => false >> x.many? { y y == 'b' } # => true # um outro exemplo... people.many? { p p.age > 26 } Apenas para relembrar e reforçar, este método só retornará true se mais de um objeto passar nas condições quando usado o bloco, e quando a coleção tiver mais de um objeto quando usado sem condicionais. Só uma curiosidade, o método inicialmente se chamaria several?, mas foi alterado para many? depois. CRIE REGRAS PARA O STRING#HUMANIZE Já faz um certo tempo que Pratik Naik estava tentando colocar este patch no Rails e parece que finalmente conseguiu. No arquivo config/initializers/inflections.rb você tem a opção de acrescentar novas inflexões para pluralização, singularização e outros: Inflector.inflections do inflect inflect.plural /^(ox)$/i, '\1en' inflect.singular /^(ox)en/i, '\1' inflect.irregular 'person', 'people' 28
Capitulo 2: ActiveSupport inflect.uncountable %w( fish sheep ) No Rails 2.2 você também pode incluir inflexões para o método exemplos: humanize da classe String. Vamos aos famosos 'jargon_cnt'.humanize # => "Jargon cnt" 'nomedojogo'.humanize # => "Nomedojogo" ActiveSupport::Inflector.inflections do inflect inflect.human(/_cnt$/i, '\1_count') inflect.human('nomedojogo', 'Nome do Jogo') 'jargon_cnt'.humanize # => "Jargon count" 'nomedojogo'.humanize # => "Nome do jogo" INTRODUZINDO MEMOIZABLE PARA CACHE DE ATRIBUTOS Performance é coisa séria, e um dos métodos mais usados para aumentar a velocidade de execução em códigos é o uso de cache. Quem nunca fez algo assim? class Person < ActiveRecord::Base def age @age = um_calculo_muito_complexo Nesta versão do Rails temos uma forma mais elegante de fazer isto usando o método memoize (é memoize mesmo e não memorize). Vamos alterar o exemplo acima para funcionar com esta nova funcionalidade: 29
Ruby on Rails 2.2 - O que há de novo? class Person < ActiveRecord::Base def age um_calculo_muito_complexo memoize :age O método age será executado apenas uma vez e o seu retorno será armazenado e retornado em futuras chamadas ao método. Só existe uma diferença entre os dois códigos acima. No primeiro, como o método é executado todas as vezes, se o valor armazenado na variável @age for nil ou false o cálculo (muito complexo) será executado novamente até termos a idade da pessoa. No segundo exemplo, o método age só será executado uma vez e o valor retornado será sempre devolvido nas próximas chamadas, mesmo que seja nil ou false. Se em algum momento você precisar desligar ou religar o cache em propriedade marcadas com o memoize, você pode fazer uso dos métodos unmemoize_all e memoize_all. @person = Person.first # Para desligar o cache do método age @person.unmemoize_all # Para ligar novamente o cache do método age apenas @person.memoize_all 30
Capitulo 2: ActiveSupport NOVO MÉTODO OBJECT#PRESENT? Um novo método foi acrescentado à classe Object. O método present? é o equivalente a!object#blank?. Em outras palavras um objeto está presente se ele não for vazio. Mas o que é um objeto vazio? class EmptyTrue def empty?() true; a b c d e g g h = = = = = = = = EmptyTrue.new nil false '' ' ' " \n\t \r " [] {} a.present? b.present? c.present? d.present? e.present? f.present? g.present? h.present? # # # # # # # # => => => => => => => => false false false false false false false false Todos estes objetos são vazios ou não estão presentes. Mas, muito cuidado, algumas pessoas tem confundido as coisas. Veja alguns exemplos de objetos que NÃO estão vazios, ou seja, estão presentes: 31
Ruby on Rails 2.2 - O que há de novo? class EmptyFalse def empty?() false; a b c d e f g h = = = = = = = = EmptyFalse.new Object.new true 0 1 'a' [nil] { nil => 0 } a.present? b.present? c.present? d.present? e.present? f.present? g.present? h.present? # # # # # # # # => => => => => => => => true true true true true true true true Qualquer objeto que contenha um valor, está presente, isto vale até mesmo para um porque o Array não está vazio. Array preenchido com um nil, STRINGINQUIRER Uma nova classe foi incluída ao Rails, a classe StringInquirer. Para enter como funciona, vou ter de explicar usando alguns exemplos. Vamos criar uma classe chamada Cliente que contém um método que retorna o status do cliente: 32
Capitulo 2: ActiveSupport class Cliente def status "ativo" c = Cliente.new c.status # => "ativo" c.status == "ativo" # => true c.status == "inativo" # => false Ok, até aqui tudo normal. Agora vou modificar a implementação do método status usando a classe StringInquirer, sempre lembrando que o retorno do método status pode vir de uma coluna do banco de dados (claro), isto é apenas um exemplo. class Cliente def status ActiveSupport::StringInquirer.new("ativo") c = Cliente.new c.status # => "ativo" # Agora vem a grande diferença: c.status.ativo? # => true 33
Ruby on Rails 2.2 - O que há de novo? c.status.inativo? # => false Para verificar se o status do cliente é o esperado, ao invés de comparar status e o sinal de interrogação. Strings, eu uso um método com o valor do Claro que isto já começou a ser usado no próprio Rails. Por exemplo, caso você precise verificar se o Rails foi carregado em ambiente de produção, você pode substituir o velho Rails.env == "production", por: Rails.env.production? NOVA SINTAXE PARA TESTES Uma nova forma de se declarar testes foi adicionada ao Rails, usando declarações test/do. Veja: test "verify something" do #... Este é o novo padrão para testes do Rails, veja como ficou um arquivo de teste unitário recém criado nesta versão: require 'test_helper' class PostTest < ActiveSupport::TestCase # Replace this with your real tests. test "the truth" do assert true 34
Capitulo 2: ActiveSupport A forma convencional, usando métodos, também continuará funcionando, então nossos testes antigos não quebrarão. LIGANDO E DESLIGANDO CARGA DE DEPENDÊNCIAS Um novo parâmetro de inicialização foi adicionado ao Rails, a fim de ligar ou desligar a carga de novas classes durante uma requisição. config.depency_loading = true # ou config.depency_loading = false Se depency_loading for verdadeiro, durante uma requisição o Rails estará apto a carregar em memória qualquer classe que não tenha sido inicialmente carregada durante a inicialização do projeto. Caso ele seja falso, estas classes serão ignoradas. Se você for executar seu projeto em um ambiente de threads concorrentes deve desabilitar esta opção e carregar todas estas classes usando eager load ou através do método require na inicialização do sistema. FILE.ATOMIC_WRITE COPIA AS PERMISSÕES DO ARQUIVO ORIGINAL Talvez alguns não conheçam o método File.atomic_write. Ele serve para escrever arquivos de forma atômica. Isto pode ser muito útil em situações onde você não quer que outros processos ou threads vejam um arquivo escrito pela metade. File.atomic_write("important.file") do file file.write("hello") 35