Ruby on Rails 2.2 - Blog do Fernando Quadro
Transcrição
Ruby on Rails 2.2 - Blog do Fernando Quadro
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. Fazendo 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 aprendizado 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 end 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 depende 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 end 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 dependerá 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. Fazendo 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? end end 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) end 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 sendo executada no banco de dados, a aplicação continuará recebendo 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 crescendo 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 end 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 fazendo 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, recebendo 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. Fazendo 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 end 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 end 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 entender esta alteração, vamos precisar analisar o mesmo código sendo 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 end 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 sendo 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 :dependent. 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 entender como isto funcionará, vamos tornar o atributo name da classe User privado: class User < ActiveRecord::Base private def name "I'm private" end end 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 ) end 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') end '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 end end 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 end memoize :age end 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; end end 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; end end 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 entender 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" end end 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") end end 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 # ... end 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 end end 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.dependency_loading = true # ou config.dependency_loading = false Se dependency_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") end 35 Ruby on Rails 2.2 - O que há de novo? O que este método faz é criar um arquivo temporário enquanto você escreve nele, e quando terminar ele substitui o arquivo antigo pelo novo. A novidade no Rails 2.2 é que agora este método copia todas as permissões do arquivo original antes de substitui-lo. CAMELIZE(:LOWER) Por padrão o método camelize do Rails é usado para converter string para o formato UpperCamelCase. Mas também podemos converter para o formato lowerCamelCase se usarmos o argumento :lower. Porém, tente executar o código abaixo no terminal de um projeto Rails (menor ou igual ao 2.1.1): 'Capital'.camelize(:lower) # => "Capital" Como você pode ver, a letra ‘C’ no início da palavra não retornou minúscula como deveria. Isto foi corrigido. Veja o retorno do mesmo trecho de código, agora executado no Rails 2.2: 'Capital'.camelize(:lower) # => "capital" TROCA DE BIBLIOTECA GERADORA DE CHAVES SECRETAS A classe Rails::SecretKeyGenerator, usada para gerar chaves secretas aleatórias como as usadas para armazenar a sessão do usuário em cookies, foi marcada para ser removida do Rails (Deprecate). Em seu lugar o Rails passou a usar a nova classe ActiveSupport::SecureRandom que foi feita para o Ruby 1.9. A biblioteca SecureRandom faz a mesma coisa que a anterior, mas um pouco melhor. 36 Capitulo 2: ActiveSupport Esta nova biblioteca suporta os seguintes geradores de números aleatórios: • openssl • /dev/urandom • Win32 Vejamos alguns exemplos de chaves geradas com esta nova biblioteca: # random hexadecimal string. ActiveSupport::SecureRandom.hex(10) ActiveSupport::SecureRandom.hex(10) ActiveSupport::SecureRandom.hex(11) ActiveSupport::SecureRandom.hex(12) ActiveSupport::SecureRandom.hex(13) #=> #=> #=> #=> #=> # random base64 string. ActiveSupport::SecureRandom.base64(10) ActiveSupport::SecureRandom.base64(10) ActiveSupport::SecureRandom.base64(10) ActiveSupport::SecureRandom.base64(11) ActiveSupport::SecureRandom.base64(12) ActiveSupport::SecureRandom.base64(13) "52750b30ffbc7de3b362" "92b15d6c8dc4beb5f559" "6aca1b5c58e4863e6b81b8" "94b2fff3e7fd9b9c391a2306" "39b290146bea6ce975c37cfc23" #=> #=> #=> #=> #=> #=> "EcmTPZwWRAozdA==" "9b0nsevdwNuM/w==" "KO1nIU+p9DKxGg==" "l7XEiFja+8EKEtY=" "7kJSM/MzBJI+75j8" "vKLJ0tXBHqQOuIcSIg==" # random binary string. ActiveSupport::SecureRandom.random_bytes(10) #=> "\016\t{\370g\310pbr\301" ActiveSupport::SecureRandom.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337" 37 Ruby on Rails 2.2 - O que há de novo? NOVO MÉTODO EACH_WITH_OBJECT O método each_with_object do Ruby 1.9 foi adicionado ao Rails, caso você ainda não esteja usando a nova versão do Ruby. Este método é bem interessante, pois ele funciona como o conhecido método inject, com um pequeno diferencial. Cada iteração além de receber um elemento da coleção, recebe também um objeto que chamamos de memo. Por exemplo, vamos dizer que eu pretenda preencher um hash com valores de uma coleção: %w(first second third).each_with_object({}) do |str, hash| hash[str.to_sym] = str end # => {:second=>"second", :first=>"first", :third=>"third"} Note que no exemplo acima o memo é um hash vazio ({}). Dentro do bloco eu preencho este hash com os itens da minha coleção. Apenas um alerta: Não podemos usar objetos imutáveis como memo, tais como números, abaixo o retorno sempre será 1, já que o número um não pode ser alterado: true e false. No exemplo (1..5).each_with_object(1) { |value, memo| memo *= value } # => 1 INFLECTOR#PARAMETERIZE SIMPLIFICA A CRIAÇÃO DE URLS ELEGANTES Um novo inflector foi incluído no Rails, e particularmente acho este é muito útil. O texto qualquer em um formato ideal para o uso em URLs. Por exemplo: Model: 38 parameterize transforma um Capitulo 2: ActiveSupport class User def to_param "#{id}-#{name.parameterize}" end end Controller: @user = User.find(1) # => #<User id: 1, name: "Carlos E. Brando"> View: link_to @user.name, user_path # => <a href="/users/1-carlos-e-brando">Carlos E. Brando</a> Um fato interessante é que logo de inicio a implementação feita não aceitava o uso de caracteres acentuados, o que significava um problema para muita gente ao redor do mundo, inclusive nós brasileiros. Um dia depois da primeira implementação, Michael Koziarski salvou nossas vidas incluindo este suporte. Mesmo assim o código ainda não estava perfeito, então decidiu-se adaptar o código do ótimo plugin slugalizer criado por Henrik Nyh. Agora sim, ficou perfeito! Para aqueles que ainda não estão fazendo uso do Rails 2.2, o plugin slugalizer resolve o problema. 39 Ruby on Rails 2.2 - O que há de novo? TRÊS NOVOS MÉTODOS PARA CLASSES QUE TRABALHAM COM DATAS E HORAS As classes Time, Date, DateTime e TimeWithZone receberam três novos métodos muito convenientes. Os métodos today?, past? e future? foram introduzidos em todas as classes que trabalham com datas e horas para facilitar nossa vida em algumas situações. Acredito que não seja necessário explicar o funcionamento de cada um. Então vejamos os métodos em ação: date = Date.current # => Sat, 04 Oct 2008 date.today? # => true date.past? # => false date.future? # => false time = Time.now # => Sat Oct 04 18:36:43 -0300 2008 time.today? # => true 40 Capitulo 2: ActiveSupport ALTERADA A MENSAGEM DO MÉTODO ASSERT_DIFFERENCE Quando usávamos o método assert_difference com múltiplas expressões e um erro ocorria era difícil de saber qual das expressões não teve seu valor alterado, já que a mensagem de erro não informava isto. No Rails 2.2 a mensagem devolvida pelo método informará exatamente qual expressão não passou no teste. Por exemplo: assert_difference ['Post.count', 'current_user.posts.count'] do Post.create(params) end O código acima retornará a seguinte mensagem, caso a segunda expressão não tenha passado: "<current_user.posts.count> was expression that failed. expected but was .". ADICIONANDO UM PREFIXO AOS DELEGATES O método Module#delegate agora possui uma nova opção :prefix que pode ser atribuída caso você queira que o nome da classe alvo seja usado como prefixo antes do nome do método. Vejamos dois exemplos, primeiro da forma comum: class Invoice < ActiveRecord::Base delegate :street, :city, :name, :to => :client end Invoice.new.street Invoice.new.city Invoice.new.name 41 Ruby on Rails 2.2 - O que há de novo? Agora usando a opção :prefix: class Invoice < ActiveRecord::Base delegate :street, :city, :name, :to => :client, :prefix => true end Invoice.new.client_street Invoice.new.client_city Invoice.new.client_name Você também pode personalizar, atribuindo um outro nome ao prefixo: class Invoice < ActiveRecord::Base delegate :street, :city, :name, :to => :client, :prefix => :customer end Invoice.new.customer_street Invoice.new.customer_city Invoice.new.customer_name 42 Capitulo 3: ActiveResource Capitulo 3 ActiveResource RECUPERANDO O ÚLTIMO REGISTRO DO ACTIVERESOURCE Seguindo o padrão do ActiveRecord, também foi adicionado ao método find do ActiveResource a opção :last. Person.find(:last, :from => :managers) # => GET /people/managers.xml Desta forma podemos recuperar o último recurso encontrado. ACTIVERESOURCE::BASE #TO_XML E #TO_JSON Mais dois novos métodos foram acrescentados ao ActiveResource::Base: to_xml e to_json. O primeiro converte o recurso em uma string XML e o segundo retorna também uma string, mas no formato JSON representando o modelo. 43 Ruby on Rails 2.2 - O que há de novo? O método to_json pode ser configurado através de um parâmetro opcional com as opções permitindo restringir ou remover determinados atributos. Por exemplo: person = Person.new(:first_name => "Carlos", :last_name => "Brando") person.to_json # => {"first_name": "Carlos", "last_name": "Brando"} person.to_json(:only => ["first_name"]) # => {"first_name": "Carlos"} person.to_json(:except => ["first_name"]) # => {"last_name": "Brando"} 44 :only e :except, Capitulo 4: ActionPack Capitulo 4 ActionPack NOVA OPÇÃO :LAYOUT NO MÉTODO CACHES_ACTION Foi acrescentado a opção :layout no método caches_action. class ListsController < ApplicationController ... caches_action :index, :layout => false ... end No exemplo acima eu especifiquei :layout => false o que significa que o layout não será armazenado no cache, apenas o conteúdo da action será. Isto é muito útil quando temos conteúdo dinâmico no layout (o que acontece na maioria dos casos). 45 Ruby on Rails 2.2 - O que há de novo? Se você não especificar nada, ele assumirá o padrão atual, que é true. TEMPLATES EM CACHE Para melhorar a performance do Rails as páginas de template serão armazenadas em cache automaticamente em ambiente de produção. Em outras palavras, se você alterar algum template em produção terá de reiniciar o servidor para que isto tenha efeito. ALTERAÇÃO NO MÉTODO CONCAT Se você tem o costume de evitar repetições em suas views criando helpers, com certeza já usou o método você nunca usou este método, saiba que ele é como o puts para uma view. concat. Se A implementação atual do método recebe dois parâmetros, uma String com o texto que será exibido na view e um segundo chamado binding. Acontece que devido a melhorias no código, embora ele ainda espere estes dois parâmetros, o binding não é mais necessário. Este parâmetro foi deprecado, ou seja, se você estiver rodando o seu projeto sob o Rails 2.2, receberá uma mensagem de alerta quando este método for executado. Em uma futura versão do Rails, este parâmetro será removido. A mensagem de alerta exibida é a seguinte: "The binding argument of #concat is no longer needed. Please remove it from your views and helpers". 46 Capitulo 4: ActionPack MÉTODO LINK_TO COM BLOCOS O método link_to recebeu uma atualização que permite seu uso com blocos. Isto é interessante para os casos onde temos textos muito longos no hyperlink. Por exemplo, se hoje fazemos assim: <%= link_to "<strong>#{@profile.name}</strong> -- <span>Check it out!!</span>", @profile %> Agora podemos fazer assim, que teremos o mesmo resultado: <% link_to(@profile) do %> <strong><%= @profile.name %></strong> -- <span>Check it out!!</span> <% end %> Não é uma mudança significativa em funcionalidade, mas permite deixar o código mais legivel, e isto também é importante. RJS#PAGE.RELOAD O método reload foi incluído ao ActionView::Helpers::PrototypeHelper para ser usado em templates .rjs ou blocos render(:update). Este método força a recarga da página atual no browser usando javascript. Em outras palavras é um atalho para o já muito usado window.location.reload();. Veja como usar: respond_to do |format| format.js do render(:update) { |page| page.reload } end end 47 Ruby on Rails 2.2 - O que há de novo? DANDO UM NOME PARA A VARIÁVEL LOCAL DE UMA COLEÇÃO DE PARTIALS No código abaixo estamos usando uma partial com uma coleção de dados: render :partial => "admin_person", :collection => @winners Dentro da partial podemos usar então a variável admin_person para acessar os itens da coleção. Mas temos de concordar que este nome de variável é meio ruim. Agora temos a opção de personalizar o nome desta variável usando a opção :as. Vamos alterar o exemplo acima: render :partial => "admin_person", :collection => @winners, :as => :person Agora podemos acessar cada item da coleção usando a variável person que tem um nome mais intuitivo. PARTIALS NÃO VÃO MAIS DEFINIR AS VARIÁVEIS LOCAIS IMPLICITAMENTE No exemplo abaixo estou renderizando uma partial, e não estou informando qual variável ela deve usar para preencher o conteúdo. Hoje o Rails encara que como tenho uma variável de instância com o mesmo nome, implicitamente é esta que deve ser usada. @customer = Customer.new("Carlos Brando") render :partial => "customer" 48 Capitulo 4: ActionPack Isto funciona mas é um pouco arriscado. A partir do Rails 2.2, esta funcionalidade continua funcionando mas sempre emitindo uma aviso de que será por pouco tempo: "@customer will no longer be implicitly assigned to customer" Sim, este recurso será removido do Rails numa futura versão. POLYMORPHIC_URL AGORA É CAPAZ DE LIDAR COM RECURSOS SINGLETON Até agora o helper polymorphic_url não estava tratando singleton resources corretamente. Um novo patch foi incluído no Rails para permitir que especifiquemos um singular resource usando símbolos, assim como fazemos com namespaces. Exemplo: # este código polymorphic_url([:admin, @user, :blog, @post]) # é a mesma coisa que admin_user_blog_post_url(@user, @post) SUPORTE A EXPRESSÕES REGULARES NO TIME_ZONE_SELECT Na classe TimeZone do ActiveSupport existe o método us_zones que convenientemente retorna uma lista dinâmica com todos os fusos-horários americanos. 49 Ruby on Rails 2.2 - O que há de novo? O problema é que em alguns casos vamos desenvolver software para pessoas em outros países, mas não existe um método tão conveniente assim que liste os fusos-horários destes países. Houve uma longa discussão sobre criar ou não métodos como prevaleceu o seguinte: african_zones, american_zones, etc.. No fim Foi implantado no objeto TimeZone o suporte para =~ afim de ajudar a montar uma lista de fusos-horários a partir de uma expressão regular. Além disso o método time_zone_select foi alterado para trabalhar em conjunto com esta novidade. Agora podemos fazer isto: <%= time_zone_select( "user", 'time_zone', /Australia/) %> O código acima retornará todos os fusos-horários, mas colocará no topo da lista os seguintes: (GMT (GMT (GMT (GMT (GMT (GMT (GMT (GMT +08:00) +09:30) +09:30) +10:00) +10:00) +10:00) +10:00) +10:00) Perth Adelaide Darwin Brisbane Canberra Hobart Melbourne Sydney Para aprender mais sobre TimeZones, aconselho assistir ao episódio 106 do RailsCasts (http://railscasts.com/episodes/ 106) e dar uma olhada no livro antecessor a este. 50 Capitulo 4: ActionPack RENDER :TEMPLATE AGORA ACEITA :LOCALS Os métodos render :action e render :partial permitem que passemos um Hash através da opção :locals com dados para serem processados por eles, mas o render :template não permitia. Nesta versão do Rails isto irá funcionar também: render :template => "weblog/show", :locals => {:customer => Customer.new} :USE_FULL_PATH DO MÉTODO RENDER NÃO EXISTIRÁ MAIS render :file => "some/template", :use_full_path => true A opção :use_full_path do método render não existe mais no Rails 2.2. Não é algo sério já que ela nem mesmo era necessária. DEFINE_JAVASCRIPT_FUNCTIONS Mais um método que deixa de existir no Rails: define_javascript_functions. Faz sentido já que métodos como javascript_include_tag e outros realizam a mesma tarefa de uma forma melhor. DESABILITANDO O CABEÇALHO ACCEPT EM REQUISIÇÕES HTTP Vamos iniciar com um código de exemplo: 51 Ruby on Rails 2.2 - O que há de novo? def index @people = Person.find(:all) respond_to do |format| format.html format.xml { render :xml => @people.to_xml } end end No exemplo acima o Rails tem duas formas de identificar qual é o formato que deve ser usado no bloco respond_to. A primeira e mais comum é através do formato informado na URL (/index.xml, por exemplo) e a segunda forma para o caso de o formato não ter sido especificado é examinando o cabeçalho Accept da requisição HTTP. Para quem não sabe o cabeçalho Accept é aquele que informa o tipo do documento desejado em strings mais ou menos assim: Accept: text/plain, text/html Accept: text/x-dvi; q=.8; mxb=100000; mxt=5.0, text/x-c # recuperando esta informação via código @request.env["HTTP_ACCEPT"] = "text/javascript" Para ver uma lista dos tipos mais comuns acesse a endereço: http://www.developershome.com/wap/detection/ detection.asp?page=httpHeaders Acontece que este cabeçalho é porcamente implementado na maioria dos browsers. E quando ele é usado em sites públicos algumas vezes estranhos erros acontecem quando robôs indexadores fazem seus requests. 52 Capitulo 4: ActionPack Assim, tomou-se a decisão de desativar este cabeçalho por padrão. É sempre uma melhor opção utilizar URLs formatadas para indicar o formato desejado. Se precisar do cabeçalho Accept, você deve habilitá-lo de volta, para isto basta incluir a seguinte linha no arquivo environment.rb: config.action_controller.use_accept_header = false Quando desligado, caso o formato não seja informado na URL o Rails assumirá que deve usar o format.html. Existe um caso especial quando fazemos requisições usando ajax se o cabeçalho X-Requested-With estiver presente. Neste caso o formato format.js ainda será usado mesmo que o formato não tenha sido especificado (/people/1, por exemplo). BUTTON_TO_REMOTE Se você já usa Rails há um tempo, com certeza conhece o método submit_to_remote, certo? Este método retorna um botão HTML preparado para enviar o formulário via XMLHttpRequest. No Rails 2.2 este método foi renomeado para seu método irmão o link_to_remote. button_to_remote, para manter uma certa consistência com o nome do Aqueles que irão migrar seus projetos não precisam se preocupar já que o nome antigo ( submit_to_remote) será um alias para novo. 53 Ruby on Rails 2.2 - O que há de novo? RECEBENDO ALERTAS PARA MELHORIA DE DESEMPENHO O Rails recebeu mais um parâmetro de configuração que faz com que ele emita um alerta caso esteja renderizando um template fora do diretório especificado para views. Isto é muito bom já que arquivos fora dos diretórios especificados para views não são armazenados em cache, o que resulta em mais operações no disco. Para começar a receber os avisos basta incluir a seguinte linha no arquivo environment.rb do projeto: config.action_view.warn_cache_misses = true Fazendo isto a seguinte mensagem aparecerá caso algum arquivo fora dos diretórios configurados seja renderizado: [PERFORMANCE] Rendering a template that was not found in view path. Templates outside the view path are not cached and result in expensive disk operations. Move this file into /Users/user/project/app/ views or add the folder to your view path list Esta configuração está desligada como padrão. OPÇÃO :INDEX FUNCIONANDO NO FIELDS_FOR Muitas vezes a opção :index do método select pode ser útil, como por exemplo quando se precisa criar diversos dropdowns dinamicamente em uma página. Até agora o método fields_for não repassava esta opção para métodos como select, collection_select, country_select e time_zone_select, o que limitava o seu uso em determinados casos. 54 Capitulo 4: ActionPack Isto já foi corrigido nesta versão do Rails. Por exemplo, apenas para efeito de teste veja o código abaixo e o seu retorno: fields_for :post, @post, :index => 108 do |f| concat f.select(:category, %w( abe <mus> hest)) end Isto retornará: <select id=\"post_108_category\" name=\"post[108][category]\"> <option value=\"abe\">abe</option> <option value=\"<mus>\" selected=\"selected\"><mus></option> <option value=\"hest\">hest</option> </select> Veja que estou usando a opção :index => 108 no método fields_for. Agora repare as propriedades id e name da tag criada pelo método select. Embora nada tenha sido especificado para este método, ele acrescenta o indice em seu resultado também. MELHORANDO A PERFORMANCE USANDO ETAGS Antes de começar a explicar este novo recurso, deixe-me tentar explicar o que são ETags (Entity Tags). Etags seriam de uma forma grosseira identificadores associados a cada recurso para determinar se o arquivo que está no servidor é o mesmo que está no cache do browser. No caso, o recurso seria uma página em HTML, mas também poderia ser um XML ou JSON. Cabe ao servidor a responsabilidade de verificar se o recurso solicitado é igual dos dois lados. Caso o servidor confirme que o recurso armazenado no cache do browser do usuário é exatamente o mesmo que seria enviado de 55 Ruby on Rails 2.2 - O que há de novo? volta para ele, ao invés de devolver todo o conteúdo do recurso novamente ele apenas retorna um status 304 (Not Modified) e o browser usará o que está em seu cache. Servidores Web como o Apache e o IIS já sabem fazer isto para página estáticas. Mas quando o conteúdo é dinâmico, como na maioria das páginas de um projeto Ruby on Rails, a responsabilidade é todo nossa. Dois novos atributos foram adicionados ao objeto response, são o last_modified e o etag. Quando um valor é atribuído a estes atributos eles são automaticamente repassados aos cabeçalhos HTTP_IF_MODIFIED_SINCE e HTTP_IF_NONE_MATCH respectivamente. Quando uma nova requisição (request) deste recurso for feita ela retornará com estes cabeçalhos permitindo que você possa comparar com o valor atual e determinar se o que está no cache do usuário é uma versão recente ou antiga do conteúdo do recurso. Caso a versão do usuário seja a mais recente, ao invés de renderizar o recurso novamente, você pode simplesmente enviar um status "304 Not Modified" solicitando ao browser que use a versão armazenada em seu cache. Para realizar esta operação temos dois métodos que podemos usar dependendo da situação: stale? Vejamos um exemplo: class ArticlesController < ApplicationController def show @article = Article.find(params[:id]) if stale?(:last_modified => @article.published_at.utc, :etag => @article) respond_to do |wants| wants.html wants.xml { render :xml => @article } end end end end 56 e fresh_when. Capitulo 4: ActionPack No exemplo acima, se no cabeçalho do request os valores forem diferentes dos atribuídos ao método stale? significa que a versão no cache do usuário não é recente, então o bloco respond_to é disparado e os valores informados ao método são atribuídos aos atributos last_modified e etag do objeto response. Se os valores forem iguais, quer dizer que a versão no cache do usuário é a mais recente. Então o bloco respond_to não é disparado e apenas um status "304 Not Modified" é devolvido. Ainda temos o método fresh_when que é uma versão simplificada do método stale?. Veja um exemplo: def show @article = Article.find(params[:id]) fresh_when(:etag => @article, :last_modified => @article.created_at.utc) end Basicamente este método faz o seguinte: Ele atribui os valores informados aos seus respectivos atributos no objeto response e verifica se eles são iguais aos enviados no objeto request, se forem diferentes (stale) então ele renderizará o template padrão da action. Caso os atributos sejam iguais em ambos os objetos (fresh), então ele apenas retornará um status "304 Not Modified" no lugar de renderizar o template. Em algumas situações talvez seja necessário informar um Array à opção :etag, como no exemplo abaixo: fresh_when(:etag => [@article, current_user], :last_modified => @article.created_at.utc) # ou if stale?(:last_modified => @article.published_at.utc, :etag => [@article, current_user]) # ... end 57 Ruby on Rails 2.2 - O que há de novo? ALTERAÇÃO NA ASSINATURA DO MÉTODO NUMBER_WITH_DELIMITER O método number_with_delimiter recebeu uma nova implementação. Além de uma melhora no código para que fique mais limpo a assinatura do método também mudou. Veja a antiga: def number_with_delimiter(number, delimiter=",", separator=".") # Exemplos de uso number_with_delimiter(12345678) # => 12,345,678 number_with_delimiter(12345678.05) # => 12,345,678.05 number_with_delimiter(12345678, ".") # => 12.345.678 number_with_delimiter(98765432.98, " ", ",") E a nova: def number_with_delimiter(number, *args) # Exemplos de uso number_with_delimiter(12345678) # => 12,345,678 number_with_delimiter(12345678.05) # => 12,345,678.05 number_with_delimiter(12345678, :delimiter => ".") # => 12.345.678 number_with_delimiter(12345678, :seperator => ",") # => 12,345,678 number_with_delimiter(98765432.98, :delimiter => " ", :separator => ",") Então atenção, agora ao usarmos o método método. 58 number_with_delimiter devemos informar as opções na chamada do Capitulo 4: ActionPack EXECUTANDO MÚLTIPLAS INSTÂNCIAS DE UM PROJETO EM SUBDIRETÓRIOS Às vezes você tem de rodar múltiplas cópias do mesmo projeto. Talvez você tenha um produto que será usado por vários clientes, ou talvez você apenas deseje rodar uma versão de teste e produção do seu software ao mesmo tempo. A forma mais simples de se fazer isto é ter múltiplos (sub)domínios com uma instância do aplicativo em cada uma. Mas se isto não for possível você pode colocar seu aplicativo em um subdiretório e usar um prefixo na sua URL para distinguir as instâncias do seu software. Por exemplo, você poderia rodar vários blogs de usuários diferentes usando URLs como: • http://www.nomedojogo.com/fulano/blog • http://www.nomedojogo.com/sicrano/blog • http://www.nomedojogo.com/beltrano/blog Nestes casos, os prefixos fulano, sicrano e beltrano identificarão as instâncias do aplicativo rodando em subdiretórios com os mesmos nomes. O roteamento do aplicativo começa depois disto. Você pode dizer ao Rails para ignorar esta parte das URLs quando uma requisição for feita, mas colocá-la nas URLs geradas por ele, configurando isto através da constante RAILS_RELATIVE_URL_ROOT ou do método AbstractRequest.relative_url_root. Se seu projeto Rails estiver rodando sob o Apache, esse recurso já é ativado automaticamente. Por isso na maioria dos casos não temos de nos preocupar em configurar isto hoje. Isto se você estiver usando Apache. Porém, no Rails 2.2 o relative_url_root não será mais configurado automaticamente pelo HTTP header. Teremos de fazer isto manualmente, colocando uma linha mais ou menos assim no arquivo environment.rb de cada um dos aplicativos: config.action_controller.relative_url_root = "/fulano" 59 Ruby on Rails 2.2 - O que há de novo? Feito isto, seu aplicativo irá ignorar o prefixo "fulano" logo depois do domínio. Mas ao gerar URLs ele sempre colocará este prefixo para garantir que você estará acessando o projeto no subdiretório correto. ALTERAÇÃO NO MÉTODO ERROR_MESSAGE_ON O método error_message_on é extremamente útil. Com ele podemos exibir mensagens de erro retornadas por determinados métodos em um objeto de uma forma bem simples. <%= error_message_on "post", "title" %> <!-- ou --> <%= error_message_on @post, "title" %> Isto fará com que uma mensagem de erro seja exibida na sua página dentro de uma tag DIV, caso um erro esteja associado ao campo title do modelo post. Mas o mais interessante do método error_message_on é que podemos personaliza-lo para que exibida mensagens mais amigáveis. E é aqui que entra a alteração para o Rails 2.2. Na versão atual os parâmetros de personalização são passadas diretamente para o método, mas no Rails 2.2 serão passadas via um Hash de opções: <%= error_message_on "post", "title", :prepend_text => "Title simply ", :append_text => " (or it won't work).", :css_class => "inputError" %> 60 Capitulo 4: ActionPack Fique tranqüilo quanto a uma possível migração de seus projetos atuais, pois o código está preparado para funcionar também da forma antiga (pelo menos por um tempo), mas emitindo um aviso de alerta para que o código seja atualizado. MAIS MÉTODOS ATUALIZADOS PARA RECEBER HASHES DE OPÇÕES Os seguintes métodos também foram alterados para aceitarem seus argumentos na forma de um tornando o código mais legível e facilitando a manutenção. Hash de opções, truncate truncate("Once upon a time in a world far far away") # => Once upon a time in a world f... truncate("Once upon a time in a world far far away", :length => 14) # => Once upon a... truncate("And they found that many people were sleeping better.", :omission => "... (continued)", :length => 15) # => And they found... (continued) highlight highlight('You searched for: rails', ['for', 'rails'], :highlighter => '<em>\1</em>') # => You searched <em>for</em>: <em>rails</em> highlight('You searched for: rails', 'rails', :highlighter => '<a href="search?q=\1">\1</a>') # => You searched for: <a href="search?q=rails">rails</a> 61 Ruby on Rails 2.2 - O que há de novo? excerpt excerpt('This is an example', 'an', :radius => 5) # => ...s is an exam... excerpt('This is an example', 'is', :radius => 5) # => This is a... excerpt('This next thing is an example', 'ex', :radius => 2) # => ...next... excerpt('This is also an example', 'an', :radius => 8, :omission => '<chop> ') # => <chop> is also an example word_wrap word_wrap('Once upon a time', :line_width => 8) # => Once upon\na time word_wrap('Once upon a time', :line_width => 1) # => Once\nupon\na\ntime auto_link post_body = "Welcome to my blog at http://www.nomedojogo.com/. Please e-mail me at [email protected]." auto_link(post_body, :urls) # => "Welcome to my blog at <a href=\"http://www.nomedojogo.com/\">http://www.nomedojogo.com</a>. Please e-mail me at [email protected]." auto_link(post_body, :all, :target => "_blank") 62 Capitulo 4: ActionPack # => "Welcome to my blog at <a href=\"http://www.nomedojogo.com/\" target=\"_blank\">http://www.nomedojogo.com</a>. Please e-mail me at <a href=\"mailto:[email protected]\">[email protected]</a>." auto_link(post_body, :link => :all, :html => {:target => "_blank"}) # => "Welcome to my blog at <a href=\"http://www.nomedojogo.com/\" target=\"_blank\">http://www.nomedojogo.com</a>. Please e-mail me at <a href=\"mailto:[email protected]\">[email protected]</a>." Todos os métodos continuam funcionando da forma antiga por enquanto, mas alertas serão emitidos no terminal para lembra-lo de atualizar seu código o mais rápido possível. AUMENTANDO AS POSSIBILIDADES COM PARTIALS Vamos pegar o seguinte exemplo: <!-- Arquivo _layout.html.erb --> inicio <%= yield %> fim <!-- uma view qualquer --> <%= render :layout => 'layout' do %> meio <% end %> O resultado deste código seria: inicio meio fim 63 Ruby on Rails 2.2 - O que há de novo? No exemplo acima estou incluindo uma partial dentro da minha view e usando o método conteúdo, que é passado dentro de um bloco através do método render. yield para personalizar o Porém, até agora você não podia passar nenhum argumento dentro do bloco. No Rails 2.2. isto será possível. Você poderá fazer coisas realmente legais. Veja um exemplo usando uma coleção de livros: <!-- app/views/books/_book.html.erb --> <div class="book"> Price: $<%= book.price %> <%= yield book %> </div> <!-- app/views/books/index.html.erb --> <% render :layout => @books do |book| %> Title: <%= book.title %> <% end %> Isto devolveria algo como: <div class="book"> Price: $29.74 Title: Advanced Rails </div> Veja que dentro do bloco estou informando o título do livro, mas em uma outra view eu poderia informar também a quantidade em estoque ou outras informações relevantes, sempre usando a mesma partial. Você também poderia usar a mesma partial várias vezes na mesma view e usar blocos para diferenciar as seções dentro da página, por exemplo: 64 Capitulo 4: ActionPack <!-- app/views/books/_book.html.erb --> <div class="book"> <%= yield book, :header %> Price: $<%= book.price %> <%= yield book, :footer %> </div> <!-- app/views/books/index.html.erb --> <% render :layout => @books do |book, section| %> <%- case section when :header -%> Title: <%= book.title %> <%- when :footer -%> (<%= book.reviews.count %> customer reviews) <%- end -%> <% end %> RECUPERANDO A STRING ATUAL DO MÉTODO CYCLE Muito provavelmente você já conhece o método cycle. Ele é muito usado para alternar as cores de linhas em um tabela, alterando a propriedade class de cada row. @items = [1,2,3,4] <table> <% @items.each do |item| %> <tr class="<%= cycle("even", "odd") -%>"> <td>item</td> </tr> <% end %> </table> 65 Ruby on Rails 2.2 - O que há de novo? Um novo método foi criado para auxiliar o uso do método cycle em tabelas mais complexas ou outros tipos de design onde se faça necessário recuperar a string corrente desde a última execução do método cycle. Veja um exemplo similar ao mostrado acima usando o novo método current_cycle: @items = [1,2,3,4] <% @items.each do |item| %> <div style="background-color:<%= cycle("red", "white", "blue") %>"> <span style="background-color:<%= current_cycle %>"><%= item %></span> </div> <% end %> EVITANDO FEEDS DUPLICADOS Algumas vezes você assina o feed de um blog e de repente se depara com uma série de posts que já foram lidos aparecendo como se fossem novos no seu Google Reader. Já aconteceu com você? Isto pode acontecer por vários motivos, mas não seria legal deixar isto acontecer com os assinantes do seu feed, correto? Para ajudar-nos a evitar este tipo de constrangimento, cada entrada e o próprio feed criado pelo builder receberam uma nova opção chamada :id, que permite a personalização do id. atom_feed({ :id => 'tag:nomedojogo.com,2008:test/' }) do |feed| feed.title("My great blog!") feed.updated((@posts.first.created_at)) for post in @posts feed.entry(post, :id => "tag:nomedojogo.com,2008:" + post.id.to_s) do |entry| entry.title(post.title) 66 atom_feed Capitulo 4: ActionPack entry.content(post.body, :type => 'html') entry.author do |author| author.name("Carlos Brando") end end end end Fazendo desta forma, mesmo que você tenha de reescrever algum trecho do código que gera os feeds, ou fazer alguma grande alteração no conteúdo do seu site, o id criado para aquela entrada será sempre o mesmo fazendo com que o leitor de feeds não duplique as entradas antigas. Seus leitores agradecem. ADICIONANDO INSTRUÇÕES DE PROCESSAMENTO EM DOCUMENTOS XML Uma nova opção foi incluida ao método atom_feed, agora podemos incluir instruções de processamento ao xml. Veja um exemplo: atom_feed(:schema_date => '2008', :instruct => { 'xml-stylesheet' => { :href=> 't.css', :type => 'text/css' } }) do |feed| # ... end 67 Ruby on Rails 2.2 - O que há de novo? Instruções de processamento em arquivos XML são informações contidas no documento XML que serão repassadas para o aplicativo que o requisitou. Essas instruções são na maioria das vezes usadas para informar ao aplicativo como ele deve manipular os dados que estão no documento XML. No exemplo acima estou dizendo ao aplicativo que recebe o XML que ele deve exibi-lo com uma folha de estilo (CSS) especifico. Veja como fica no XML: <?xml-stylesheet type="text/css" href="t.css"?> SIMPLIFICANDO O ACESSO AOS RESOURCES Rotas aninhadas já não é mais novidade. Ao configurar nossas rotas é comum codificarmos algo mais ou menos assim: map.resources :users do |user| user.resources :posts do |post| post.resources :comments end end No código acima estou deixando claro que meus usuários tem posts, que por sua vez tem comentários. Da forma como minhas rotas estão configuradas posso recuperar os posts de um usuário através da url '/users/1/posts'. Ou recuperar os comentários de um determinado post através da url '/users/1/posts/5/comments'. Com a nova opção :shallow => true, ganhamos mais flexibilidade. Note que ao adicionar esta opção no primeiro resource, todos os outros que estiverem abaixo dele herdarão esta característica. map.resources :users, :shallow => true do |user| user.resources :posts do |post| post.resources :comments 68 Capitulo 4: ActionPack end end Com esta opção habilitada eu posso continuar recuperando dados da mesma forma como fazia antes. A vantagem é que agora eu também posso recuperar todas os comentários de um post sem precisar informar o usuário, através da url '/posts/2/comments'. Ou recuperar um determinado post usando apenas a url '/posts/2'. A opção :shallow também funciona em recursos que estão usando as notações has_many ou has_one, por exemplo: map.resources :users, :has_many => { :posts => :comments }, :shallow => true Todos os helpers para acessar as rotas diretamente também são criados, como: user_posts_path(@user) # => '/users/1/posts' posts_path # => '/posts' post_comments_path(@post) # => /posts/5/comments USANDO ARRAYS PARA DEFINIR OS VERBOS DE MEMBROS E COLEÇÕES DE ROTAS Agora podemos fornecer um array de métodos para novos membros ou coleções de rotas. Isso remove o problema de termos que definir uma rota aceitando qualquer verbo (:any) quando na verdade apenas precisávamos que ele respondesse a mais de um. No Rails 2.2 podemos declarar uma rota desta maneira: map.resources :photos, :collection => { :search => [:get, :post] } 69 Ruby on Rails 2.2 - O que há de novo? POLYMORPHIC ROUTES Os métodos *_polymorphic_url e *_polymorphic_path, muito usados para gerar URLs a partir de registros do banco de dados, receberam um novo parâmetro opcional. Agora, além dos parâmetros normais eles também aceitam um hash de opções, tornando possível gerar rotas com parâmetros adicionais na url. Vamos aos exemplos, com o método equivalente nos comentários: edit_polymorphic_url(@article, :param1 => '10') # => edit_article_url(@article, :param1 => '10') polymorphic_url(@article, :param1 => '10') # => article_url(@article, :param1 => '10') polymorphic_url(@article, :format => :pdf, :param1 => '10') # => formatted_article_url(@article, :pdf, :param1 => '10') COUNTRY_SELECT REMOVIDO DO RAILS O helper country_select foi removido do Rails. Para quem não se lembra, este método retorna uma lista com todos os países do mundo. O motivo deste método ter sido removido do Rails é que ele foi atualizado para utilizar a norma ISO 3166 para os nomes dos países. O problema todo é que Taiwan, segundo a norma ISO 3166, se chama na verdade "Taiwan, Province of China". E foi exatamente assim que Michael Koziarski deixou no método. 70 Capitulo 4: ActionPack Então, Jamis Buck questionou se não seria possível deixar apenas "Taiwan", já que o "Province of China" parece ser politicamente agressivo. No GitHub iniciou-se então uma série de comentários que foram entrando cada vez mais em questões políticas e deixando o técnico totalmente de lado. Mas, Michael Koziarski foi categórico em afirmar que essas questões políticas estão muito além do que poderíamos resolver com uma simples alteração no código. E se aceitasse esta alteração, logo outras seriam solicitadas para países como Kosovo, Ossétia do Sul, Abecásia, Transnístria e uma longa lista. A melhor solução, ou pelo menos a que geraria menos controvérsias, foi remover o helper do Rails e disponibiliza-lo na forma de um plugin. Desta forma qualquer um poderia facilmente criar um fork e montar sua própria lista da forma como mais lhe agradar. Para instalar o plugin oficial: ./script/plugin install git://github.com/rails/country_select.git BENCHMARKING REPORTS EM MILISEGUNDOS Todas as mensagens de log que continham uma indicação do tempo que determinado processo levou para ser executado, foram alteradas para exibir o tempo em milisegundos. Por exemplo, a mensagem: "Completed in 0.10000 (4 reqs/sec) | Rendering: 0.04000 (40%) | DB: 0.00400 (4%) | 200 OK" Agora será exibida da seguinte forma: "Completed in 100ms (View: 40, DB: 4) | 200 OK" 71 Ruby on Rails 2.2 - O que há de novo? OPÇÃO :CONFIRM NO MÉTODO IMAGE_SUBMIT_TAG A opção :confirm, muito utilizada em helpers como o link_to, agora também está disponível para o método image_submit_tag. Esta opção faz com que uma caixa de confirmação, com uma pergunta personalizada, seja exibida ao se clicar na imagem. Se o usuário aceitar, o formulário é enviado normalmente, caso contrário nada acontece. image_submit_tag("delete.gif", :confirm => "Are you sure?") # => <input type="image" src="/images/delete.gif" # onclick="return confirm('Are you sure?');"/> COOKIE DE SESSÃO AGORA É HTTP ONLY Ao se criar um cookie existe uma opção esquecida por muita gente. A opção http_only faz com que o cookie somente seja acessível via HTTP, impedindo que um trecho de código em javascript consiga acessá-lo. O valor padrão para esta opção é false. No Rails 2.2 o cookie que armazena a sessão do usuário terá a opção http_only ligada por padrão. A intenção é aumentar a segurança em nossos projetos. Obviamente esta opção pode ser desligada caso necessário. Se este for o caso, basta incluir a seguinte linha no ApplicationController ou em um controller especifico: session :session_http_only => false CAMINHO COMPLETO DA VIEW NO CASO DE UMA EXCEÇÃO Quando uma exceção é dispara recebemos em nossa view uma mensagem semelhante a esta: 72 Capitulo 4: ActionPack NoMethodError in Administration/groups#show Showing app/views//_list.erb where line ... Quando na verdade deveríamos receber uma mensagem com o caminho completo do arquivo que disparou o erro, assim: NoMethodError in Administration/groups#show Showing app/views/administration/reports/_list.erb where line ... Este problema já está corrigido nesta nova versão do Rails, facilitando nosso trabalho. OPÇÕES NO MÉTODO FIELD_SET_TAG Foi adicionado ao método field_set_tag um parâmetro opcional para facilitar a formatação do HTML. Este parâmetro aceita todas as opções que o método tag já aceita. Exemplo: <% field_set_tag nil, :class => 'format' do %> <p>Some text</p> <% end %> O código acima retornará o seguinte: <fieldset class="format"> <p>Some text</p> </fieldset> 73 Ruby on Rails 2.2 - O que há de novo? SUPORTE A XHTML NO ATOM_FEED O helper atom_feed agora possui um builder interno que permite a criação de XHTML simplesmente acrescentando :type=>"xhtml" em qualquer elemento content, rights, title, subtitle ou summary. Assim: entry.summary(:type => 'xhtml') do |xhtml| xhtml.p "A XHTML summary" xhtml.p post.body end Veja como este bloco se encaixa dentro do atom_feed: atom_feed do |feed| feed.title("My great blog!") feed.updated((@posts.first.created_at)) for post in @posts feed.entry(post) do |entry| entry.title(post.title) entry.summary(:type => 'xhtml') do |xhtml| xhtml.p "A XHTML summary" xhtml.p post.body end entry.author do |author| author.name("DHH") end end end end 74 Capitulo 4: ActionPack Desta forma o builder interno do atom_feed irá incluir o XHTML gerado dentro de uma tag DIV. Claro que ainda podemos fazer da forma antiga passando todo o HTML dentro de uma string, mas desta forma nosso código fica mais limpo. EVITANDO ATAQUES RESPONSE SPLITTING Até agora no Rails as URLs passadas para o método redirect_to não passavam por um processo de santization. Isto era perigoso, pois abria brechas para que pessoas mal intencionadas pudessem realizar ataques do tipo response splitting e header injection em sua aplicação. Um exemplo desta vulnerabilidade é quando seu aplicativo recebe uma URL via query string e redireciona seu usuário através do método redirect_to para esta URL. Através desta brecha de segurança pessoas mal intencionadas podem gravar cookies na máquina e forjar falsos responses para seus usuários se o seu projeto usar estes parâmetros para construir cabeçalhos HTTP. Para evitar este tipo de problema o Rails foi atualizado para limpar (sanitize) todas as URLs passadas para o método redirect_to. Mas isto não significa que não precisamos nos preocupar mais com este problema, é sempre bom ficar atento. UMA MANEIRA MELHOR DE INTERCEPTAR ERROS A pior coisa que pode acontecer em nossa aplicação é uma horrível página com uma mensagem de erro. Por este motivo é sempre bom se preparar para estes casos. Agora podemos facilmente mostrar uma página de erro personalizada para exceções lançadas quando estamos roteando uma requisição, usando o método rescue_from. Veja um exemplo: 75 Ruby on Rails 2.2 - O que há de novo? class ApplicationController < ActionController::Base rescue_from User::NotAuthorized, :with => :deny_access rescue_from ActiveRecord::RecordInvalid, :with => :show_errors protected def deny_access … end def show_errors(exception) exception.record.new_record? ? … end end A adição de ActiveSupport:Rescuable permite qualquer classe de fazer mixin da sintaxe rescue_from. NOVA OPÇÃO :RECURSIVE PARA OS HELPERS JAVASCRIPT_INCLUDE_TAG E STYLESHEET_LINK_TAG Os helpers javascript_include_tag e stylesheet_link_tag receberam uma nova opção :recursive que pode ser usada junto com :all, para que eles possam carregar toda a árvore de arquivos em uma única linha de código. Por exemplo, caso eu tenha os seguintes arquivos: public/javascripts/super_calendar/calendar.js public/stylesheets/super_calendar/calendar.css Ambos estarão inclusos, mesmo estando em um subdiretório, quando eu executar os métodos da seguinte forma: javascript_include_tag :all, :recursive => true stylesheet_link_tag :all, :recursive => true 76 Capitulo 5: ActionMailer Capitulo 5 ActionMailer APLICANDO LAYOUTS EM EMAILS A partir deste release do Rails teremos uma nova funcionalidade que será a alegria dos spammers! Bincadeiras à parte, assim como temos layouts para controllers, teremos layouts para mailers também. A única restrição é que devemos colocar o sufixo _mailer no nome do arquivo se quisermos que o layout seja aplicado automaticamente. Vamos aos nossos exemplos. Primeiro as views: <!-- layouts/auto_layout_mailer.html.erb --> Hello from layout <%= yield %> 77 Ruby on Rails 2.2 - O que há de novo? <!-- auto_layout_mailer/hello.html.erb Inside --> Agora vamos dar uma olhada no ActionMailer: # auto_layout_mailer.rb class AutoLayoutMailer < ActionMailer::Base def hello(recipient) recipients recipient subject "You have a mail" from "[email protected]" end end Da forma como fizemos qualquer novo mailer fará uso do layout criado. Para evitar que o mailer use o layout padrão, basta incluir a cláusula :layout => false no corpo do email. def nolayout(recipient) recipients recipient subject "You have a mail" from "[email protected]" body render(:inline => "Hello, <%= @world %>", :layout => false, :body => { :world => "Earth" }) end Você também pode informar um outro layout para o mailer, alterando a opção não é obrigado a ter o sufixo ‘_mailer’. def spam(recipient) recipients recipient subject "You have a mail" 78 :layout. Neste caso o layout criado Capitulo 5: ActionMailer from "[email protected]" body render(:inline => "Hello, <%= @world %>", :layout => 'spam', :body => { :world => "Earth" }) end Além disso você também pode informar ao ActionMailer qual layout ele deve usar da seguinte forma: class ExplicitLayoutMailer < ActionMailer::Base layout 'spam', :except => [:logout] 79 Ruby on Rails 2.2 - O que há de novo? Capitulo 6 Railties ESTÁ CHEGANDO O FIM DOS PLUGINS? No Rails 2.1, gems passaram a poder ser usadas como plugins em nossos projetos. Para isto bastava criar uma pasta chamada rails dentro do projeto do gem e incluir um arquivo init.rb. Isto acrescentou um leque de novidades como config.gem e rake:gems. Mas isto nos faz pensar, "Já que agora eu posso carregar gems dentro da minha aplicação Rails, seria apenas uma questão de tempo até que plugins deixassem de existir"? E parece que isto realmente pode acontecer. Para esta versão do Rails, por exemplo, foi incluída uma alteração que permite inicializar plugins tanto pelo arquivo init.rb na raiz do plugin, como em um arquivo em um diretório rails/ init.rb (da mesma forma como fazemos com os gems), sendo esta segunda opção a prioritária. 80 Capitulo 6: Railties Assim, eu poderia por exemplo criar um gem (que funcionaria como um plugin) e instalar de duas maneiras: ./script/plugin install git://github.com/user/plugin.git sudo gem install user-plugin --source=http://gems.github.com Isto sem precisar manter dois arquivos init.rb (um na raiz e outro no diretório rails). SUPORTE AO THIN MELHORADO NO RAILS O script/server agora verifica a disponibilidade do Thin e o usa. Muito conveniente se vocês estiver usando Thin no seu ambiente de produção (e quiser rodar o mesmo em desenvolvimento). Você deve acrescentar a seguinte linha no seu arquivo environment.rb para que isto funcione. config.gem 'thin' TESTANDO APENAS ARQUIVOS UNCOMMITTEDS NO GIT Existe uma tarefa rake no Rails que pouca gente conhece mas que é muito útil: rake test:uncommitted Como o nome já diz esta tarefa roda os testes apenas dos arquivos que ainda não foram enviados (commit) para o subversion, ao invés de rodar todos os testes do projeto. Eu costumava usar isto muito, mas quando mudei para Git ela não funcionou mais, o suporte era apenas para o SVN. Mas um patch foi aplicado no Rails garantindo que daqui em diante teremos o mesmo suporte também para o Git. 81 Ruby on Rails 2.2 - O que há de novo? RAILS.INITIALIZED? Novo método adicionado ao Rails: Rails.initialized? Ele informa se todos os processos de inicialização já foram finalizados. CACHE_CLASSES AGORA ESTARÁ LIGADO POR PADRÃO Nos arquivos de configuração do seu projeto provavelmente deve haver uma linha assim: config.cache_classes = false Esta linha informa ao Rails que ele não deve fazer cache do código de seu projeto, ou seja, para cada requisição feita ele carregará o código novamente. Embora isto torne a execução de seu código mais lenta, é ótimo para o ambiente de desenvolvimento, assim você não precisa ficar recarregando o seu servidor web a cada alteração. Em produção é importantíssimo que você deixe isto ligado. Em projetos Rails 2.1, caso a linha acima não se encontre em seus arquivos de configuração, o Rails assumirá que não deve fazer o cache. Esta era a condição padrão. No Rails 2.2 isto foi invertido, caso nenhuma configuração seja encontrada ele assumirá que deve fazer o cache. Isto ajudará os inexperientes que colocam seus projetos em produção sem configurá-lo corretamente. 82 Capitulo 6: Railties FAZENDO O CONFIG.GEM NÃO CARREGAR O GEM Uma das novidades introduzidas no Rails 2.1 foi o projeto era dependente. config.gem, que nos permitia configurar de quais gems nosso Com isto ganhamos diversas tarefas que facilitaram nosso trabalho, como o dependências automaticamente. rake gems:install, que instala todas as Mas tínhamos de tomar alguns cuidados ao configurar as dependências, porque em alguns casos o nome da gem não é necessariamente o nome da biblioteca que o método require precisa carregar. Por exemplo, a gem aws-s3 deve ser carregada com o nome aws/s3, trocando o traço por uma barra invertida. Prevendo este tipo de coisa, foi incluído no config.gem a opção :lib. Veja como ficaria o caso acima: config.gem 'aws-s3', :lib => 'aws/s3' Depois de um tempo surgiu um outro problema. Meu projeto depende desta gem, mas eu não quero que ela seja carregada neste momento. Como fazer? Até agora nada, mas nesta nova versão podemos configurar a opção carregue a gem. :lib como false e isto fará com que o Rails não config.gem 'aws-s3', :lib => false Mesmo sem carregá-la ela ainda será instalada caso você execute a tarefa qualquer outra tarefa relacionada a gems. rake gems:install e estará incluída em 83 Ruby on Rails 2.2 - O que há de novo? THREADSAFE! Foi incluído no Rails um novo método de configuração para ligar o threaded mode. config.threadsafe! Ao executar este método em seu arquivo de configuração (normalmente no environments/production.rb), você estará fazendo com que as actions de um controller aceitem requisições concorrentes e múltiplas conexões com o banco de dados. E dependendo da infra-estrutura do seu servidor você poderá receber mais requisições enquanto mantém menos cópias do Rails em memória. Ele também desliga o carregamento de dependências após a inicialização. Para mais detalhes sobre isto veja o tópico "Ligando e desligando carga de dependências" no capítulo de ActiveSupport. FIXTURES_PATH As tarefas rake FIXTURES_PATH. db:fixtures:load e rake db:fixtures:identify receberam um novo parâmetro opcional: rake db:fixtures:load FIXTURES_PATH=spec/fixtures Desta forma você pode especificar um caminho alternativo para suas fixtures (por exemplo: spec/fixtures). RELACIONAMENTOS BELONGS_TO AUTOMATIZADOS Se você já estiver usando o Rails 2.2, tente executar o seguinte comando para criar um novo modelo: 84 Capitulo 6: Railties ./script/generate scaffold comment author:string body:text post:references Note que estou informando que meus comentários terão uma referência a tabela posts. Ou em outras palavras que meus comentários pertencem (belongs_to) a um post. Agora veja o arquivo app/models/comment.rb gerado pelo script: class Comment < ActiveRecord::Base belongs_to :post end O relacionamento com a tabela posts já foi incluído automaticamente no modelo. Este é um novo recurso que encontramos nesta versão. MENSAGEM DE AJUDA PARA NOVOS NO RAILS Foi adicionado ao arquivo 500.html do Rails uma mensagem para ajudar os programadores que estão começando com Rails: (If you’re the administrator of this website, then please read the log file “development.log” to find out what went wrong.) É o Rails se preocupando com a galera que está começando. DEBUG NO CONSOLE DO RAILS Da mesma forma como temos o script/server --debugger, agora também temos o script/console Esta opção basicamente carrega a biblioteca ruby-debug ao iniciar o console. --debugger. 85 Ruby on Rails 2.2 - O que há de novo? É mais fácil usar esta opção do que executar um recurso. require 'ruby-debug' no console toda vez que precisar deste TAREFA DB:MIGRATE:REDO AGORA ACEITA A VERSÃO DA MIGRATION A tarefa rake db:migrate:redo tem se mostrado muito útil quando precisamos voltar e executar novamente a última migration criada. Agora esta tarefa ficou ainda mais útil, porque podemos utilizar a opção VERSION e informar qual migration queremos que seja reexecutada. rake db:migrate:redo VERSION=20080725004631 SUPORTE À BANCO DE DADOS IBM DB NO GERADOR DO RAILS Foi adicionado ao generator do Rails a opção IBM DB para o banco de dados. Para usá-la basta executar no terminal o seguinte comando ao criar o projeto Rails: rails app_name -d ibm_db Isto criará seu projeto com o seguinte arquivo database.yml: # # # # # # # # 86 IBM Dataservers Home Page http://rubyforge.org/projects/rubyibm/ To install the ibm_db gem: On Linux: Source the db2profile file and set the necessary environment variables: Capitulo 6: Railties # # # # # # # # # # . /home/db2inst1/sqllib/db2profile export IBM_DB_DIR=/opt/ibm/db2/V9.1 export IBM_DB_LIB=/opt/ibm/db2/V9.1/lib32 Then issue the command: gem install ibm_db On Windows: Issue the command: gem install ibm_db If prompted, select the mswin32 option development: adapter: ibm_db username: db2inst1 password: database: app_name_dev #schema: db2inst1 #host: localhost #port: 50000 #account: my_account #app_user: my_app_user #application: my_application #workstation: my_workstation test: adapter: ibm_db username: db2inst1 password: database: app_name_tst #schema: db2inst1 #host: localhost #port: 50000 #account: my_account #app_user: my_app_user 87 Ruby on Rails 2.2 - O que há de novo? #application: my_application #workstation: my_workstation production: adapter: ibm_db username: db2inst1 password: database: app_name_prd #schema: db2inst1 #host: localhost #port: 50000 #account: my_account #app_user: my_app_user #application: my_application #workstation: my_workstation 88 Capitulo 7: Internacionalização (I18n) Capitulo 7 Internacionalização (I18n) Tudo começou em setembro de 2007 quando um grupo de desenvolvedores começou a construção de um plugin para o Rails chamado rails-I18n, que visava eliminar a necessidade de monkey patching no Rails para internacionalizar uma aplicação. O QUE É I18N? Primeiro, para entender o porque deste nome, precisamos ter um conhecimento profundo de cálculos matemáticos. Conte comigo, quantas letras temos entre o I e o n na palavra Internationalization? 18. Muito bom, I18n. O mesmo vale para Localization e L10n. 89 Ruby on Rails 2.2 - O que há de novo? Já viu quando um site tem aquelas bandeirinhas no topo, permitindo que você escolha em que língua quer navegar? Quando você clica em uma delas, todos os textos do site mudam para a língua correspondente daquele país. Isto se chama Internationalization, ou como acabamos de aprender I18n. Claro que estou sendo muito simplista, na maioria das vezes não é só os textos que mudam de um país para outro. Não podemos nos esquecer do formato da data, fuso-horário, padrões de peso e medida. E talvez o mais importante a moeda. I18N E L10N, QUAL A DIFERENÇA? Internacionalização é preparar seu software para que pessoas de outros países e línguas possam usá-lo. Localização (L10n) é adaptar o seu produto as necessidades de um país. É como pegar um site americano que só aceita pagamento via PayPal e adaptá-lo para aceitar boleto bancário, por exemplo. O QUE TEM DE NOVO NO RAILS? No Rails 2.2 este plugin de internacionalização será integrado ao seu código fonte. Isto não significa que o Rails sofreu grandes alterações. Na verdade quase nada mudou, ele continua sendo o mesmo framework de sempre, com todas as suas mensagens de validações em inglês e tudo mais. A diferença é que se quiséssemos estas mensagem em português, ou em outro idioma, teríamos de criar um monkey patch para isto. Não posso deixar de citar como exemplo o famoso Brazilian Rails, que faz exatamente isto para traduzir as mensagens do Active Record. 90 Capitulo 7: Internacionalização (I18n) A novidade é que com o lançamento do Rails 2.2 temos uma forma padronizada e mais simples de fazer isto, usando uma interface comum. COMO ISTO FUNCIONA? Básicamente este gem é dividido em duas partes: • A primeira acrescenta à API do Rails uma coleção de novos métodos, estes métodos basicamente servirão para acessar a segunda parte do gem. • A segunda parte é um backend simples que implementa toda a lógica para fazer o Rails funcionar exatamente como funcionava antes, usando a localização en-US. A grande diferença é que esta segunda parte poderá ser substituída por uma que dê suporte à internacionalização que você deseja. Melhor ainda, uma série de plugins que serão lançados irão fazer exatamente isto. O alicerce desta implementação é um módulo chamado I18n que vem através do gem incorporado ao Este módulo acrescenta as seguintes funcionalidades ao Rails: ActiveSupport. • O método translate, que será usado para retornar traduções. • O método localize, que usaremos para "traduzir" objetos Date, DateTime e Time para a localização atual. Além destes métodos este módulo traz todo o código necessário para armazenar e carregar os backends de "localização". E já vem com um manipulador de exceções padrão que captura exceções levantadas no backend. 91 Ruby on Rails 2.2 - O que há de novo? Tanto o backend como o manipulador de exceções podem (devem) ser substituídos. Além disso para facilitar, os métodos #translate e #localize também poderão ser executados usando os métodos #t e #l respectivamente. E ambos os métodos funcionam em suas views e controllers. EXEMPLOS PRÁTICOS A melhor forma de entender como usar este suporte a internacionalização no Rails é vendo seu funcionamento na prática. Eu aconselho então acessar o projeto criado por Clemens Kofle e outros no endereço: http://i18ndemo.phusion.nl/. PLURALIZAÇÃO NA INTERNACIONALIZAÇÃO Em alguns casos as traduções de certas frases pode depender de um número ou quantidade. Isto é algo muito comum e o pacote de internacionalização já vem preparado para estes casos. Como exemplo, vamos pegar o método distance_in_words. Se o tempo for de 1 segundo a frase “1 segundo” no singular nos serve muito bem, mas se o tempo for maior do que isto a frase deve estar no plural como em “5 segundos”. No arquivo de localização, podemos internacionalizar frases do nosso aplicativo que dependem de um número ou quantidade, por exemplo: datetime: distance_in_words: x_seconds: one: "1 segundo" other: "{{count}} segundos" 92 Capitulo 7: Internacionalização (I18n) Este é um recurso valioso que é usado por vários métodos nativos do Rails e pode ser usado em seus próprios métodos. 93 Ruby on Rails 2.2 - O que há de novo? Capitulo 8 Performance MELHORANDO A PERFORMANCE DO RAILS Jeremy Kemper andou trabalhando em melhorias de performance no Rails.Uma das coisas que ele andou melhorando foi o Erb, tornando-o mais rápido. Além disso ele tem atacado alguns métodos especiais como o concat e capture que são usados por muitos helpers do Rails. Jeremy também atacou o processo de inicialização de Javascript. partials e otimizou diversos helpers que geravam código em A classe RecordIdentifier também foi melhorada através do uso de caches. O RecordIdentifier incorpora uma série de convenções para lidar com registros ActiveRecords e ActiveResources ou praticamente qualquer outro tipo de modelo que tenha um id. 94 Capitulo 8: Performance É interessante ver este tipo de ação, o Rails já está ficando grande e pesado demais, e processos de otimização devem se tornar uma constante daqui para frente. CRIANDO TESTES DE PERFORMANCE No Rails 2.2 ganhamos um novo generator para testes de performance. Ao executar no terminal o seguinte comando: [carlosbrando:edge]$ ./script/generate performance_test Login exists test/performance/ create test/performance/login_test.rb Será criado um arquivo chamado test/performance/login_test.rb. Veja o código gerado: require 'performance/test_helper' class LoginTest < ActionController::PerformanceTest # Replace this with your real tests. def test_homepage get '/' end end Neste arquivo podemos colocar todos os testes que desejarmos e ao executá-lo teremos informações sobre cada um dos testes, como tempo de processamento, uso de memória e outros. Para realizar o teste executamos no terminal: [carlosbrando:edge]$ ruby test/performance/login_test.rb Loaded suite test/performance/login_test Started 95 Ruby on Rails 2.2 - O que há de novo? LoginTest#test_homepage (32 ms warmup) process_time: 11 ms memory: unsupported objects: unsupported . Finished in 0.870842 seconds. 96 Capitulo 9: Bugs e Correções Capitulo 9 Bugs e Correções ACTIVERECORD Correção de uma colisão entre named_scope e :joins. Quando se usava with_scope junto com :joins todos os atributos da tabelas secundárias eram adicionados ao modelo da tabela principal. Método find_all falhando no named_scope Quando você executava o método find_all em um named_scope o método não estava sendo direcionado para o proxy_found conforme o esperado. Isto fazia com que um erro NoMethodError fosse retornado. 97 Ruby on Rails 2.2 - O que há de novo? Topic.base.find_all(&:approved) # => NoMethodError: undefined method `find_all' for #<Class:0x19a0fb4> Este problema podia ser contornado usando o método to_a: Topic.base.to_a.find_all(&:approved) # => [#<Reply:0x179e720>#<Topic:0x179e388>#<Reply:0x179e20c>] Nesta versão do Ruby on Rails isto já foi resolvido. Partial updates não atualizavam o lock_version se nada foi alterado. Quando usávamos optimistic locking com partial updates, tínhamos queries extras quando na verdade elas não eram necessárias. CORREÇÃO NOS MÉTODOS TIME#END_OF_QUARTER E DATE#END_OF_QUARTER Nem bem havia saído o Rails 2.1 e já foi encontrado um erro sério. Se você ainda tiver um projeto criado nesta versão entre no irb e tente rodar isto: Date.new(2008, 5, 31).end_of_quarter ERRO! Por que? A implementação do método end_of_quarter foi feita da maneira errada, ele avança até o último mês do trimestre e depois pega o último dia. O problema é que ele apenas avança o mês, e como estou partindo do dia 31 de 98 Capitulo 9: Bugs e Correções maio, ele tentar criar uma nova instância do objeto Date para 31 de junho, que não existe. Com o objeto Time não é disparado uma exceção, mas ele retorna a data errada: 31 de julho. Nesta versão este erro já foi corrigido, mas caso você ainda esteja usando a versão 2.1 em algum projeto, muito cuidado, porque este erro só ocorrerá se usarmos o método end_of_quarter nos dias 31 de maio, julho e agosto. CORREÇÃO NAS TAREFAS DB:MIGRATE:DOWN E :UP Quando se usava o comando rake db:migrate:down schema_migrations não eram atualizados. VERSION=alguma_versão, os registros na tabela Isto significa que após usar o comando rake db:migrate:down ou up se você rodar o comando rake db:migrate algumas migrations podem não ser executadas. Vamos simular isto para ficar fácil de entender o problema: $ ./script/generate migration test_migration create db/migrate create db/migrate/20080608082216_test_migration.rb $ rake db:migrate (in /Users/erik/projects/railstest) == 20080608082216 TestMigration: migrating ==================================== -- create_table("foo") -> 0.0137s == 20080608082216 TestMigration: migrated (0.0140s) =========================== $ rake db:migrate:down VERSION=20080608082216 (in /Users/erik/projects/railstest) == 20080608082216 TestMigration: reverting ==================================== -- drop_table("foo") -> 0.0139s 99 Ruby on Rails 2.2 - O que há de novo? == 20080608082216 TestMigration: reverted (0.0144s) =========================== $ rake db:migrate (in /Users/erik/projects/railstest) $ Este problema foi corrigido ao se certificar de atualizar a tabela schema_migrations após a execução destas tarefas. POSTGRESQL No PostgreSQL, a sintaxe dele de typecast é a seguinte: <column>::<type> O problema é que quando se usava essa sintaxe, o ActiveRecord achava que o na verdade era um named bind e reclamava que o valor para ele não estava sendo passado no Hash. Agora este problema está corrigido, permitindo que façamos algo assim: :conditions => [':foo::integer', { :foo => 1 }] SOLUÇÃO DE BUG NO MÉTODO RENAME_COLUMN Esta alteração trata-se na verdade de uma correção de um bug no método rename_column. Para entender qual era o problema precisamos de um cenário como exemplo. Primeiro criamos um migration: create_table "users", :force => true do |t| t.column :name, :string, :default => '' end 100 Capitulo 9: Bugs e Correções Ok, agora criamos um segundo migration onde vamos renomear a coluna name da tabela: rename_column :users, :name, :first_name Se você fizer o teste em sua máquina, notará que ao usar o método terá mais o valor padrão definido no primeiro migration. rename_column a "nova" coluna first_name não Este bug já está resolvido para esta versão do Rails. MÉTODO COUNT DO ACTIVERECORD NÃO INCLUI UM ALIAS PARA ASSOCIAÇÕES Digamos que temos a seguinte associação has_many :through: class Author < ActiveRecord::Base has_many :authorships has_many :books, :through => :authorships end Ao procurar por um livro você pode incluir a autoria em sua busca: author.books.find(:all, :include => :authorships, :conditions => ["authorships.primary = ?", true]) Isto funciona muito bem, sem erros. Mas tente fazer o mesmo com o método count: author.books.count(:include => :authorships, :conditions => ["authorships.primary = ?", true]) 101 Ruby on Rails 2.2 - O que há de novo? Temos um erro. Isto acontece porque a tabela authorships foi incluída duas vezes na mesma query. O método find é mais esperto, porque ele cria um apelido para a tabela, coisa que o método count não faz. Eu sei que o exemplo dado não é muito bom, mas é apenas para tentar mostrar o problema com o método count. Esta falha foi corrigida. Agora o método :include. count se comporta exatamente como o método find em relação ao CORREÇÃO DE BUG NO MÉTODO COUNT DO ACTIVERECORD Existia um bug no método count do ActiveRecord quando usávamos uma associação has_many em conjunto com a opção :limit ou :offset. Vejamos um exemplo: class Post < ActiveRecord::Base has_many :comments, :limit=> 2 end No código acima quando tentarmos recuperar os comentários de um post, apenas 2 comentários devem ser retornados. post.comments.length # => 2 # Veja o SQL usado: # SELECT * AS count_all FROM "comments" WHERE # ("comments".post_id = 1) LIMIT 2 Mas, ao usarmos o método count: post.comments.count # => 3 102 Capitulo 9: Bugs e Correções # Veja o SQL usado: # SELECT count(*) AS count_all FROM "comments" WHERE # ("comments".post_id = 1) Como você pode ver o erro ocorria porque a clausula LIMIT 2 não era incluída na query do SQL. Obviamente isto já foi corrigido e já está funcionando no Rails 2.2. BUG NO SUBMIT_TAG CORRIGIDO Quando usávamos o método submit_tag com a opção :disable_with ligada, ele suprimia o parâmetro :commit quando o formulário era enviado para o servidor. Isto acontecia porque após submeter o formulário, o javascript do evento onclick primeiro desabilita o botão e só depois envia o formulário ao servidor, e como sabemos campos desabilitados não são enviados no submit. Isto representava um problema para os casos onde o formulário possuía mais de um submit_tag e a sua lógica de atualização/criação dependia do valor do parâmetro :commit para fazer alguma coisa. Este problema foi corrigido incluindo um código no inicio do javascript que copia o valor deste parâmetro para um campo hidden no formulário e o envia para o servidor mesmo com o botão desabilitado. BUG AO TESTAR ROTAS NOMEADAS Existe um bug bem especifico até a versão 2.1 quando em um teste funcional testamos uma rota nomeada com parâmetros antes de executar um request. Para entender do que estou falando, veja um exemplo: 103 Ruby on Rails 2.2 - O que há de novo? def test_something post_url(:id => 1) # Antes do request isto retornará um erro get :edit, :id => 1 post_url(:id => 1) # Aqui funciona end Este problema já foi corrigido no Rails 2.2. TIME#ADVANCE RECONHECE FRAÇÕES DE DIAS E SEMANAS Após o lançamento do Rails 2.1 o método Time#advance parou de funcionar corretamente com frações de tempo como: >> Time.stubs(:now).returns Time.local(2000) >> 1.5.days.ago == 36.hours.ago # => false Este erro foi corrigido no Rails 2.2, faça o teste. ERRO AO CRIAR DOIS CONTROLLERS COM O MESMO NOME No Rails 2.1 em alguns casos ao se criar dois controllers com o mesmo nome, mas em namespaces diferentes um erro acontece, veja: $ rails -v Rails 2.1.0 104 Capitulo 9: Bugs e Correções $ ruby -v ruby 1.8.6 (2008-03-03 patchlevel 114) [universal-darwin9.0] $ rails test $ cd test/ $ script/generate scaffold Posts title:string body:text $ script/generate controller -t Admin::Posts The name 'Admin::PostsHelper' is either already used in your application or reserved by Ruby on Rails. Please choose an alternative and run this generator again. ... Mais um bug corrigido nesta versão. 105 Ruby on Rails 2.2 - O que há de novo? Capitulo 10 CHANGELOG ACTIONMAILER 2.2.0 [RC1] (October 24th, 2008) • Add layout functionality to mailers [Pratik] Mailer layouts behaves just like controller layouts, except layout names need to have '_mailer' postfix for them to be automatically picked up. ACTIONPACK 2.2.0 [RC1] (October 24th, 2008) 106 Capitulo 10: CHANGELOG • Fix incorrect closing CDATA delimiter and that HTML::Node.parse would blow up on unclosed CDATA sections [packagethief] • Added stale? and fresh_when methods to provide a layer of abstraction above request.fresh? and friends [DHH]. Example: class ArticlesController < ApplicationController def show_with_respond_to_block @article = Article.find(params[:id]) if stale?(:last_modified => @article.published_at.utc, :etag => @article) respond_to do |wants| # normal response processing end end end def show_with_implied_render @article = Article.find(params[:id]) fresh_when(:last_modified => @article.published_at.utc, :etag => @article) end end • Added inline builder yield to atom_feed_helper tags where appropriate [Sam Ruby]. Example: entry.summary :type => 'xhtml' do |xhtml| xhtml.p pluralize(order.lineitems.count, "line item") xhtml.p "Shipped to #{order.address}" xhtml.p "Paid by #{order.paytype}" end 107 Ruby on Rails 2.2 - O que há de novo? • Make PrototypeHelper#submit_to_remote a wrapper around PrototypeHelper#button_to_remote. [Tarmo Tänav] • Set HttpOnly for the cookie session store's cookie. #1046 • Added FormTagHelper#image_submit_tag confirm option #784 [Alastair Brunton] • Fixed FormTagHelper#submit_tag with :disable_with option wouldn't submit the button's value when was clicked #633 [Jose Fernandez] • Stopped logging template compiles as it only clogs up the log [DHH] • Changed the X-Runtime header to report in milliseconds [DHH] • Changed BenchmarkHelper#benchmark to report in milliseconds [DHH] • Changed logging format to be millisecond based and skip misleading stats [DHH]. Went from: Completed in 0.10000 (4 reqs/sec) | Rendering: 0.04000 (40%) | DB: 0.00400 (4%) | 200 OK [http://example.com] ...to: Completed in 100ms (View: 40, DB: 4) | 200 OK [http://example.com] • Add support for shallow nesting of routes. #838 [S. Brent Faulkner] Example : 108 Capitulo 10: CHANGELOG map.resources :users, :shallow => true do |user| user.resources :posts end • GET /users/1/posts (maps to PostsController#index action as usual) named route "user_posts" is added as usual. • GET /posts/2 (maps to PostsController#show action as if it were not nested) Additionally, named route "post" is added too. • Added button_to_remote helper. #3641 [Donald Piret, Tarmo Tänav] • Deprecate render_component. Please use render_component plugin from http://github.com/rails/ render_component/tree/master [Pratik] • Routes may be restricted to lists of HTTP methods instead of a single method or :any. #407 [Brennan Dunn, Gaius Centus Novus] map.resource :posts, :collection => { :search => [:get, :post] } map.session 'session', :requirements => { :method => [:get, :post, :delete] } • Deprecated implicit local assignments when rendering partials [Josh Peek] • Introduce current_cycle helper method to return the current value without bumping the cycle. #417 [Ken Collins] • Allow polymorphic_url helper to take url options. #880 [Tarmo Tänav] • Switched integration test runner to use Rack processor instead of CGI [Josh Peek] 109 Ruby on Rails 2.2 - O que há de novo? • Made AbstractRequest.if_modified_sense return nil if the header could not be parsed [Jamis Buck] • Added back ActionController::Base.allow_concurrency flag [Josh Peek] • AbstractRequest.relative_url_root is no longer automatically configured by a HTTP header. It can now be set in your configuration environment with config.action_controller.relative_url_root [Josh Peek] • Update Prototype to 1.6.0.2 #599 [Patrick Joyce] • Conditional GET utility methods. [Jeremy Kemper] response.last_modified = @post.updated_at response.etag = [:admin, @post, current_user] if request.fresh?(response) head :not_modified else # render ... end • All 2xx requests are considered successful [Josh Peek] • Fixed that AssetTagHelper#compute_public_path shouldn't cache the asset_host along with the source or per-request proc's won't run [DHH] • Removed config.action_view.cache_template_loading, use config.cache_classes instead [Josh Peek] • Get buffer for fragment cache from template's @output_buffer [Josh Peek] • Set config.action_view.warn_cache_misses = true to receive a warning if you perform an action that results in an expensive disk operation that could be cached [Josh Peek] • Refactor template preloading. New abstractions include Renderable mixins and a refactored Template class [Josh Peek] 110 Capitulo 10: CHANGELOG • Changed ActionView::TemplateHandler#render API method signature to render(template, local_assigns = {}) [Josh Peek] • Changed PrototypeHelper#submit_to_remote to PrototypeHelper#button_to_remote to stay consistent with link_to_remote (submit_to_remote still works as an alias) #8994 [clemens] • Add :recursive option to javascript_include_tag and stylesheet_link_tag to be used along with :all. #480 [Damian Janowski] • Allow users to disable the use of the Accept header [Michael Koziarski] The accept header is poorly implemented by browsers and causes strange errors when used on public sites where crawlers make requests too. You can use formatted urls (e.g. /people/1.xml) to support API clients in a much simpler way. To disable the header you need to set: config.action_controller.use_accept_header = false • Do not stat template files in production mode before rendering. You will no longer be able to modify templates in production mode without restarting the server [Josh Peek] • Deprecated TemplateHandler line offset [Josh Peek] • Allow caches_action to accept cache store options. #416. [José Valim]. Example: caches_action :index, :redirected, :if => Proc.new { |c| !c.request.format.json? }, :expires_in => 1.hour • Remove define_javascript_functions, javascript_include_tag and friends are far superior. [Michael Koziarski] 111 Ruby on Rails 2.2 - O que há de novo? • Deprecate :use_full_path render option. The supplying the option no longer has an effect [Josh Peek] • Add :as option to render a collection of partials with a custom local variable name. #509 [Simon Jefford, Pratik Naik] render :partial => 'other_people', :collection => @people, :as => :person This will let you access objects of @people as 'person' local variable inside 'other_people' partial template. • time_zone_select: support for regexp matching of priority zones. Resolves #195 [Ernie Miller] • Made ActionView::Base#render_file private [Josh Peek] • Refactor and simplify the implementation of assert_redirected_to. Arguments are now normalised relative to the controller being tested, not the root of the application. [Michael Koziarski] This could cause some erroneous test failures if you were redirecting between controllers in different namespaces and wrote your assertions relative to the root of the application. • Remove follow_redirect from controller functional tests. If you want to follow redirects you can use integration tests. The functional test version was only useful if you were using redirect_to :id=>... • Fix polymorphic_url with singleton resources. #461 [Tammer Saleh] • Replaced TemplateFinder abstraction with ViewLoadPaths [Josh Peek] • Added block-call style to link_to [Sam Stephenson/DHH]. Example: 112 Capitulo 10: CHANGELOG <% link_to(@profile) do %> <%= @profile.name %> -- Check it out!! <% end %> • Performance: integration test benchmarking and profiling. [Jeremy Kemper] • Make caching more aware of mime types. Ensure request format is not considered while expiring cache. [Jonathan del Strother] • Drop ActionController::Base.allow_concurrency flag [Josh Peek] • More efficient concat and capture helpers. Remove ActionView::Base.erb_variable. [Jeremy Kemper] • Added page.reload functionality. Resolves #277. [Sean Huber] • Fixed Request#remote_ip to only raise hell if the HTTP_CLIENT_IP and HTTP_X_FORWARDED_FOR doesn't match (not just if they're both present) [Mark Imbriaco, Bradford Folkens] • Allow caches_action to accept a layout option [José Valim] • Added Rack processor [Ezra Zygmuntowicz, Josh Peek] ACTIVERECORD 2.2.0 [RC1] (October 24th, 2008) • Skip collection ids reader optimization if using :finder_sql [Jeremy Kemper] • Add Model#delete instance method, similar to Model.delete class method. #1086 [Hongli Lai] 113 Ruby on Rails 2.2 - O que há de novo? • MySQL: cope with quirky default values for not-null text columns. #1043 [Frederick Cheung] • Multiparameter attributes skip time zone conversion for time-only columns [#1030 state:resolved] [Geoff Buesing] • Base.skip_time_zone_conversion_for_attributes uses class_inheritable_accessor, so that subclasses don't overwrite Base [#346 state:resolved] [miloops] • Added find_last_by dynamic finder #762 [miloops] • Internal API: configurable association options and build_association method for reflections so plugins may extend and override. #985 [Hongli Lai] • Changed benchmarks to be reported in milliseconds [DHH] • Connection pooling. #936 [Nick Sieger] • Merge scoped :joins together instead of overwriting them. May expose scoping bugs in your code! #501 [Andrew White] • before_save, before_validation and before_destroy callbacks that return false will now ROLLBACK the transaction. Previously this would have been committed before the processing was aborted. #891 [Xavier Noria] • Transactional migrations for databases which support them. #834 [divoxx, Adam Wiggins, Tarmo Tänav] • Set config.active_record.timestamped_migrations = false to have migrations with numeric prefix instead of UTC timestamp. #446. [Andrew Stone, Nik Wakelin] 114 Capitulo 10: CHANGELOG • change_column_default preserves the not-null constraint. #617 [Tarmo Tänav] • Fixed that create database statements would always include "DEFAULT NULL" (Nick Sieger) [#334] • Add :tokenizer option to validates_length_of to specify how to split up the attribute string. #507. [David Lowenfels] Example : # Ensure essay contains at least 100 words. validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least %d words."), :tokenizer => lambda {|str| str.scan(/\w+/) } • Allow conditions on multiple tables to be specified using hash. [Pratik Naik]. Example: User.all :joins => :items, :conditions => { :age => 10, :items => { :color => 'black' } } Item.first :conditions => { :items => { :color => 'red' } } • Always treat integer :limit as byte length. #420 [Tarmo Tänav] • Partial updates don't update lock_version if nothing changed. #426 [Daniel Morrison] • Fix column collision with named_scope and :joins. #46 [Duncan Beevers, Mark Catley] • db:migrate:down and :up update schema_migrations. #369 [Michael Raidel, RaceCondition] • PostgreSQL: support :conditions => [':foo::integer', { :foo => 1 }] without treating the ::integer typecast as a bind variable. [Tarmo Tänav] • MySQL: rename_column preserves column defaults. #466 [Diego Algorta] • Add :from option to calculations. #397 [Ben Munat] 115 Ruby on Rails 2.2 - O que há de novo? • Add :validate option to associations to enable/disable the automatic validation of associated models. Resolves #301. [Jan De Poorter] • PostgreSQL: use 'INSERT ... RETURNING id' for 8.2 and later. [Jeremy Kemper] • Added SQL escaping for :limit and :offset in MySQL [Jonathan Wiess] ACTIVERESOURCE 2.2.0 [RC1] (October 24th, 2008) • Add ActiveResource::Base#to_xml and ActiveResource::Base#to_json. #1011 [Rasik Pandey, Cody Fauser] • Add ActiveResource::Base.find(:last). [#754 state:resolved] (Adrian Mugnolo) • Fixed problems with the logger used if the logging string included %'s [#840 state:resolved] (Jamis Buck) • Fixed Base#exists? to check status code as integer [#299 state:resolved] (Wes Oldenbeuving) ACTIVESUPPORT 2.2.0 [RC1] (October 24th, 2008) • TimeWithZone#freeze: preload instance variables so that we can actually freeze [Geoff Buesing] • Fix Brasilia timezone #1180 [Marcus Derencius, Kane] 116 Capitulo 10: CHANGELOG • Time#advance recognizes fractional days and weeks. Deprecate Durations of fractional months and years #970 [Tom Lea] • Add ActiveSupport::Rescuable module abstracting ActionController::Base rescue_from features. [Norbert Crombach, Pratik] • Switch from String#chars to String#mb_chars for the unicode proxy. [Manfred Stienstra] This helps with 1.8.7 compatibility and also improves performance for some operations by reducing indirection. • TimeWithZone #wday, #yday and #to_date avoid trip through #method_missing [Geoff Buesing] • Added Time, Date, DateTime and TimeWithZone #past?, #future? and #today? #720 [Clemens Kofler, Geoff Buesing] • Fixed Sri Jayawardenepura time zone to map to Asia/Colombo [Jamis Buck] • Added Inflector#parameterize for easy slug generation ("Donald E. Knuth".parameterize => "donald-eknuth") #713 [Matt Darby] • Changed cache benchmarking to be reported in milliseconds [DHH] • Fix Ruby's Time marshaling bug in pre-1.9 versions of Ruby: utc instances are now correctly unmarshaled with a utc zone instead of the system local zone [#900 state:resolved] [Luca Guidi, Geoff Buesing] • Add Array#in_groups which splits or iterates over the array in specified number of groups. #579. [Adrian Mugnolo] Example: 117 Ruby on Rails 2.2 - O que há de novo? a = (1..10).toa a.ingroups(3) #=> [[1, 2, 3, 4], [5, 6, 7, nil], [8, 9, 10, nil]] a.in_groups(3, false) #=> [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]] • Fix TimeWithZone unmarshaling: coerce unmarshaled Time instances to utc, because Ruby's marshaling of Time instances doesn't respect the zone [Geoff Buesing] • Added Memoizable mixin for caching simple lazy loaded attributes [Josh Peek] • Move the test related core_ext stuff out of core_ext so it's only loaded by the test helpers. [Michael Koziarski] • Add Inflection rules for String#humanize. #535 [dcmanges] ActiveSupport::Inflector.inflections do |inflect| inflect.human(/_cnt$/i, '_count') end 'jargon_cnt'.humanize # => 'Jargon count' • TimeWithZone: when crossing DST boundary, treat Durations of days, months or years as variable-length, and all other values as absolute length. A time + 24.hours will advance exactly 24 hours, but a time + 1.day will advance 23-25 hours, depending on the day. Ensure consistent behavior across all advancing methods [Geoff Buesing] • Added TimeZone #=~, to support matching zones by regex in time_zone_select. #195 [Ernie Miller] • Added Array#second through Array#tenth as aliases for Array#[1] through Array#[9] [DHH] 118 Capitulo 10: CHANGELOG • Added test/do declaration style testing to ActiveSupport::TestCase [DHH via Jay Fields] • Added Object#present? which is equivalent to !Object#blank? [DHH] • Added Enumberable#many? to encapsulate collection.size > 1 [DHH/Damian Janowski] • Add more standard Hash methods to ActiveSupport::OrderedHash [Steve Purcell] • Namespace Inflector, Dependencies, OrderedOptions, and TimeZone under ActiveSupport [Josh Peek] • Added StringInquirer for doing things like StringInquirer.new("production").production? # => true and StringInquirer.new("production").development? # => false [DHH] • Fixed Date#end_of_quarter to not blow up on May 31st #289 state:resolved RAILTIES 2.2.0 [RC1] (October 24th, 2008) • Fixed that sqlite would report "db/development.sqlite3 already exists" whether true or not on db:create #614 [Antonio Cangiano] • Added config.threadsafe! to toggle allow concurrency settings and disable the dependency loader [Josh Peek] • Turn cache_classes on by default [Josh Peek] • Added configurable eager load paths. Defaults to app/models, app/controllers, and app/helpers [Josh Peek] 119 Ruby on Rails 2.2 - O que há de novo? • Introduce simple internationalization support. [Ruby i18n team] • Make script/plugin install -r option work with git based plugins. #257. [Tim Pope Jakub Kuźma]. Example: script/plugin install git://github.com/mislav/will_paginate.git -r agnostic # Installs 'agnostic' branch script/plugin install git://github.com/dchelimsky/rspec.git -r 'tag 1.1.4' • Added Rails.initialized? flag [Josh Peek] • Make rake test:uncommitted work with Git. [Tim Pope] • Added Thin support to script/server. #488 [Bob Klosinski] • Fix script/about in production mode. #370 [Cheah Chu Yeow, Xavier Noria, David Krmpotic] • Add the gem load paths before the framework is loaded, so certain gems like RedCloth and BlueCloth can be frozen. • Fix discrepancies with loading rails/init.rb from gems. • Plugins check for the gem init path (rails/init.rb) before the standard plugin init path (init.rb) [Jacek Becela] • Changed all generated tests to use the test/do declaration style [DHH] • Wrapped Rails.env in StringInquirer so you can do Rails.env.development? [DHH] • Fixed that RailsInfoController wasn't considering all requests local in development mode (Edgard Castro) [#310 state:resolved] 120
Documentos relacionados
Por Flávia Freire
Quando comecei a estudar Ruby on Rails em 2005, eu não tinha levado a sério o fator curva de aprendizado. Quando percebi que em pouco tempo eu já estava dominando boa parte de suas funcionalidades ...
Leia maisCluster nos trilhos
O Rails não é uma nova tecnologia, mas apenas um pacote muito organizado. Não é um substituto para o J2EE, e sim mais uma alternativa. Quando se fala em Rails, não se fala de “alta tolerância a fal...
Leia mais