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=\"&lt;mus&gt;\" selected=\"selected\">&lt;mus&gt;</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

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 mais

Cluster nos trilhos

Cluster 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