ERR THE BLOG: Combo 3 em 1!
Posted on January 09, 2007
O Chris, do err.the_blog tem artigos fantásticos e excelentes dicas de “Rubyisms and Railities”, como ele mesmo diz. Resolvi fazer mais uma tradução, mas desta vez será um combo, 3 artigos em 1 de uma seleção que eu fiz. A cereja do bolo é o terceiro artigo, mas espero que todos os três aumentem seus apetites para buscar o resto dos mais de 40 artigos em seu blog.
Organize seus Models
29/07/2006
No meio de uma dessas discussões sobre CRUD e dicas de organização da aplicação, eu gostaria de submeter uma idéia um pouco menos controversa: organize seus models sem precisar usar namespaces.
Como? Aqui vai um típico diretório app/models:
Argh. Models demais. Mas digamos que realmente precisamos de todos eles mas não queremos ligar com namespaces. A primeira coisa a fazer, assim como em qualquer tipo de organização, é separar nosso grande grupo em agrupamentos lógicos menores. Nesse caso temos três: models database, que mapeiam diretamente a tabelas no banco de dados, models cache, que não mapeiam diretamente a uma tabela e vivem no memcached, e models tableless, que usamos para tirar vantagem das validações do ActiveRecord sem usar tabelas no banco.
Criamos esses três subdiretórios dentro do models para representar nossos grupos menores: database, cache e tableless, desse jeito:
Assim é bem melhor. Está imediatamente claro que models fazem o que. Agora, como fazemos Rails reconhecer os models nesses subdiretórios sem que ele assuma que caem em namespaces dos próprios diretórios?
Não fazemos.
Rails vai automaticamente achar esses models sem qualquer problema. Não haverá diferença em seu código, apenas menos sobrecarga mental quando estiver pulando entre diretórios.
Você também pode usar esse truque para organizar melhor seus controllers, helpers, plugins ou bibliotecas (cuiddado, isso não funciona com migrations). Obrigado, Rails.
Diversão de Sexta com Hash
18/08/2006
Ei, é sexta-feira. Vamos brincar com hashes.
Primeiro de tudo: como construir um hash a partir de um array? Fácil e direto. Garanta que seus arrays estejam no formato [‘name’, ‘chris’, ‘age’, 47 ] então apenas passe ele para Hash[] com um asterisco:
=> [“name”, “chris”, “age”, 47]
>> Hash[*array]
=> {"name"=>"chris", “age”=>47}
O asterisco expande os elementos do array em parâmetros de método. Certo? Nossa chamada Hash[*array] é interpretada mais ou menos assim:
Que é como Hash[] gosta.
Faça-me um favor. Jogue flatten nele para poder passar arrays dentro de arrays.
=> [[“name”, “chris”], [“age”, 47]]
>> Hash[*array.flatten]
=> {"name"=>"chris", “age”=>47}
Agora podemos ficar pulando de arrays para hashes o dia todo.
=> {:name=>"err", :style=>"classic"}
>> blog.to_a
=> [[:name, “err”], [:style, “classic”]]
>> Hash[*blog.to_a.flatten]
=> {:name=>"err", :style=>"classic"}
Ei, coloque no seu bolso e leve com você.
def to_hash
Hash[*self.flatten]
end
alias :to_h :to_hash
end
>> [[:name, “err”], [:style, “classic”]].to_h
=> {:name=>"err", :style=>"classic"}
Concordamos que é sexta, certo? Certo. E todos sabemos que sexta é um dia folgado. Então, divirta-se com valores default de hash.
=> {}
>> animals[:cachorro] = “Um animal!”
=> “Um animal!”
>> animals[:inseto]
=> “Não é um animal.”
>> animals[:cachorro]
=> “Um animal!”
Esse é o básico. Você também pode passar um bloco ao Hash.new, que é de onde fica interessante. O hash por si próprio e a chave tentando ser acessada são ambas escorregadas ao bloco sendo chamado em uma procura de hash que falha.
Vamos inicializar elementos em um array em uma procura perdida:
=> {}
>> hash_of_arrays[:animals] << ‘Cachorro’
=> [“Cachorro”]
>> hash_of_arrays[:animals]
=> [“Cachorro”]
Uau. Isso é super folgado. Com cuidado, no entanto, já que procuras perdidas vão se tornar elementos vazios no hash.
=> {}
>> hash_of_arrays.keys
=> []
>> hash_of_arrays[:cachorro]
=> []
>> hash_of_arrays.keys
=> [:cachorro]
Esse truque é útil também para entender “eu já fiz alguma coisa”? Observe:
=> {}
>> actions[:move]
=> false
>> actions[:move]
=> true
Estamos configurando o elemento para true mas retornando false no primeiro acerto. Agora podemos construir alguma lógica como:
object.delete
else
raise “Já deletado!”
end
Você é mais criativo do que eu. Estou certo que vai encontrar um uso legal.
Cara, código demais para uma sexta. Mas nada revolucionário. Você pode achar esse tipo de truques no Code Snippets ou lá fora na blogosfera. Tenha um bom fim de semana. Amigo.
With_scope com escopo
28/11/2006
Pst, ei garoto. Isso, você. Você sabe sobre around_filter? É? Já usou with_scope com ele? É? Ok, legal. Deixe me contar a respeito:
Usar with_scope em conjunto com um around_filter em um controller é ruim.
Desculpe. Você está para receber algum vinagre em seu escopo. Vá lendo.
O Furo
Existe uma funcionalidade legal no Rails que é realmente útil mas (não diga isso) algumas vezes abusado: with_scope. É algo assim:
Movie::NOW_PLAYING ] } do
Movie.find(:all)
end
Qualquer chamada a find dentro do bloco with_scope terá seu escopo limitado a filmes com um state igual ao valor da constante Movie::NOW_PLAYING. Essa chamada Movie.find(:all) é basicamente equivalente a Movie.find(:all, :conditions => [ ‘state = ?’, Movie::NOW_PLAYING ]).
Agora, aqui vai uma idéia. Digamos que você tem um controller MovieController que está encarregado de, hmm, filmes. Como ele seria?
def director
@director = Movie.find(params[:movie_id]).director
end
def related_movies
@related_movies = Movie.find(params[:movie_id]).related_movies
end
end
Limpo e bonito. Apenas um find padrão e associações. Vamos apimentar e dizer que queremos somente filmes visible que estão em NOW_PLAYING.
def director
movie = Movie.find_by_id(params[:movie_id],
:conditions => [ ‘state = ? AND visible = ?’,
Movie::NOW_PLAYING, true ])
@director = movie.director
end
def related_movies
movie = Movie.find_by_id(params[:movie_id],
:conditions => [ ‘state = ? AND visible = ?’,
Movie::NOW_PLAYING, true ])
@related_movies = movie.related_movies
end
end
Aqui estamos atendendo às novas especificações checando por algumas :conditions. Duas vezes. Meio grosso, e não muito DRY. Talvez podemos usar with_scope? Não, ainda não – seria definitivamente mais simples configurar o filme em um before_filter.
before_filter :set_movie
def director
@director = @movie.director
end
def related_movies
@related_movies = @movie.related_movies
end
private
def set_movie
@movie = Movie.find_by_id(params[:movie_id],
:conditions => [ ‘state = ? AND visible = ?’,
Movie::NOW_PLAYING, true ])
end
end
Uau, muito melhor. E agora sempre temos o filme disponível como uma instância variável. Entretanto, algumas vezes você precisa de mais do que um filme, sabe?
A lista
Digamos que eu queira, não, demande uma lista de filmes ordenados por suas datas de lançamento. Primeiro teríamos que mudar o before_filter para isso:
Então adicionaríamos algum tipo de método all_movies, como isso:
@movies = Movie.find(:all, :conditions => [ ‘state = ? AND visible = ?’,
Movie::NOW_PLAYING, true ], :order => ‘release_date DESC’)
end
Esses raios de :conditions de novo. Exatamente o mesmo de antes, só que desta vez sem a elegância de um before_filter. Vamos suspirar. Ok, atire o with_scope.
O around_filter
Rails 1.2 tem um novo recurso chamado around_filter. Ele permite rodar código ambos antes e depois (“around”) das actions em um controller. Da documentação:
logger.debug “before #{controller.action_name}”
action.call
logger.debug “after #{controller.action_name}”
end
Ei … e se rodássemos nossas actions do controller com um bloco with_scope, limitado aos filmes em NOW_PLAYING e visible? Então poderíamos apagar todos aqueles parâmetros de :conditions e deixe o around_filter se preocupar com isso!
Vamos jogar alguma coisa junta:
before_filter :set_movie, :except => :all_movies
around_filter do |controller, action|
Movie.with_scope :find => { :conditions => [ ‘state = ? AND visible = ?’,
Movie::NOW_PLAYING, true ] } do
action.call
end
end
def director
@director = @movie.director
end
def related_movies
@related_movies = @movie.related_movies
end
def all_movies
@movies = Movie.find(:all, :order => ‘release_date DESC’)
end
private
def set_movie
@movie = Movie.find_by_id(params[:movie_id])
end
end
Legal. Pude cortar toda repetição de :conditions adicionando a chamada around_filter … mas a que custo?
ISSO. ESTÁ. ERRADO.
Não, nada legal.
Não coloque with_scope em um around_filter. De fato, NUNCA chama um Model.with_scope fora do contexto do Model. Quando faço isso, apenas torno meu código mais difícil de compreender. Você pode não pensar assim, pode até se dar bem, mas existe definitivamente um jeito mais simples que ambos você e colegas vão se dar bem sem ter que sempre ter em mente que existe um around_filter onipotente voando pelos céus, afetando cada um dos seus find.
(Além disso, esse tipo de coisa é um hábito tão ruim que o with_scope se tornará protected no 2.0, como disse DHH. Você só poderá usá-lo dentro do contexto de um model).
Então, qual a maneira mais simples? O with_scope é realmente útil? Sim.
O Jeito Rails
Jamis Buck (o J-bomo, como o chamo) recentemente escreveu sobre emagrecer código em Controller Magro, Model Gordo – um artigo sensacional. Seguindo essa linha, aqui vai uma maneira realmente limpa de limitar seu controler sem around_filter:
before_filter :set_movie, :except => :all_movies
def director
@director = @movie.director
end
def related_movies
@related_movies = @movie.related_movies
end
def all_movies
@movies = Movie.find_playing(:all, :order => ‘release_date DESC’)
end
private
def set_movie
@movie = Movie.find_playing(:first, :conditions => [ ‘id = ?’,
params[:movie_id] ])
end
end
Está vendo? Adicionei um método find_playing à classe Movie. E como esse método brilhante se parece?
def self.find_playing(args)
with_scope :find => { :conditions => [ ‘state = ? AND visible = ?’, NOW_PLAYING, true ] } do
find(args)
end
end
…
end
Agora find_playing está me entregando filmes NOW_PLAYING, visible sem cair na mágica do with_scope.
Refrescante. Código menor, mais simples sem efeitos colaterais. Você vê find_playing e não sabe o que ele faz, vê procura por aí; você não está trabalhando sob a falsa pretensa que o find é totalmente normal e na verdade algum with_scope está perambulando por aí em around_filters. E agora eu posso escrever um teste unitário para find_playing diretamente, para ter certeza que realmente sei o que está acontecendo (e que não estou programando por acidente). Nada mal.
Escopo!
Está tudo bem em entender “uau, meu código é uma droga” e então ficar refatorando franticamente para torná-lo mais limpo e simples. Faço isso diariamente. De fato, se você tiver alguma outra dica sobre como emagrecer controllers e engordar models, deixe nos comentários.
Sedendo por mais? Aqui vai uma leitura de cabeceira: outro artigo de Jamis sobre finders customizados.
E, finalmente, um desafio: como você implementaria find_playing_by_id? Eu quero Movie_find_playing_by_id(id), passar opções, etc etc. Da mesma forma como um find_by_id mas limitado como find_playing. Me avisem!
find_playing_by_id
Atualização! Dan Miliron oferece uma sugestão: with_playing.
def self.with_playing
with_scope :find => { :conditions => [ ‘state = ? AND visible = ?’,
NOW_PLAYING, true ] } do
yield
end
end
end
Então você pode, em seu controller:
def director
Movie.with_playing do
@director = Movie.find_by_id(params[:movie_id]).director
end
end
end
Nada mal, mas eu realmente gosto de ter find_playing lidar com yield para mim para manter meus controllers magros. Que tal isso:
def self.find_playing(args)
with_playing do
find(args)
end
end
def self.find_playing_by_id
with_playing do
find_by_id(*args)
end
end
def self.with_playing
with_scope :find => { :conditions => [ ‘state = ? AND visible = ?’,
NOW_PLAYING, true ] } do
yield
end
end
end
Isso funciona, mas se eu fizer o with_playing do Dan no controller, eu tenho acesso a todos os métodos find_by_*- não apenas os que eu fizer hardcode. Isso é legal, e quero isso em meu model. Então, deixe-me adicionar uns method_missing em meu estilo de vida find_playing:
if method.to_s =~ /^find_(all_)?playing_by/
with_playing do
super(method.to_s.sub(‘playing_’, ’’), *args, &block)
end
else
super(method, *args, &block)
end
end
Isso vai lhe dar find_playing_by_blah e find_all_playing_by_blah. Louco!
blog comments powered by Disqus
Archives
- February 12(2)
- December 11(1)
- November 11(4)
- October 11(6)
- September 11(5)
- August 11(1)
- July 11(5)
- May 11(4)
- April 11(11)
- March 11(4)
- February 11(3)
- January 11(4)
- December 10(9)
- November 10(2)
- October 10(10)
- September 10(4)
- August 10(6)
- July 10(14)
- June 10(16)
- May 10(8)
- April 10(14)
- March 10(9)
- February 10(6)
- January 10(14)
- December 09(10)
- November 09(10)
- October 09(7)
- September 09(19)
- August 09(4)
- July 09(12)
- June 09(7)
- May 09(12)
- April 09(11)
- March 09(9)
- February 09(9)
- January 09(12)
- December 08(14)
- November 08(20)
- October 08(15)
- September 08(18)
- August 08(25)
- July 08(13)
- June 08(21)
- May 08(29)
- April 08(27)
- March 08(12)
- February 08(32)
- January 08(31)
- December 07(27)
- November 07(30)
- October 07(25)
- September 07(28)
- August 07(16)
- July 07(15)
- June 07(16)
- May 07(7)
- April 07(13)
- March 07(8)
- February 07(9)
- January 07(24)
- December 06(17)
- November 06(17)
- October 06(15)
- September 06(38)






