Utilizando o padrão Singleton no Ruby

23/07/08

Singleton é um design pattern usado para restringir a instanciação de objetos, fornecendo um ponto global de acesso a uma única instância de uma classe.

No Ruby, você pode utilizar este padrão da maneira mais simples possível. Basta carregar a biblioteca Singleton.

require "singleton"
 
class Database
  include Singleton
 
  def connect
    @connection = Adapter.connect
  end
end

Ao adicionar o módulo Singleton, você torna o método new privado. Se você tentar instanciar esta classe, verá um erro como este:

NoMethodError: private method `new' called for Test:Class
  from (irb):3
  from :0

Para ter acesso à instância da classe Test, você deve utilizar o método instance.

Database.instance.connect

Muito elegante, assim como quase tudo no Ruby!

Utilizando o named_scope no ActiveRecord do Ruby on Rails 2.1

14/07/08

O Ruby on Rails 2.1 trouxe uma série de novidades em quase todos os módulos do framework. Uma das novidades que mais gostei foi o named_scope, que permite criar buscas personalizadas, sem perder a elegância. Veja como usar todo o poder desta funcionalidade neste artigo.

Exemplos de uso

Imagine que você tenha dois modelos: Band e Genre.

class CreateBands < ActiveRecord::Migration
  def self.up
    create_table :bands do |t|
      t.string :name
      t.references :genre
 
      t.timestamps
    end
  end
 
  def self.down
    drop_table :bands
  end
end
 
class CreateGenres < ActiveRecord::Migration
  def self.up
    create_table :genres do |t|
      t.string :name
      t.timestamps
    end
  end
 
  def self.down
    drop_table :genres
  end
end
 

Antes do Ruby on Rails 2.1, se você quisesse criar consultas personalizadas poderia criar métodos ou estender as associações. Por exemplo, poderíamos implementar uma busca pelos artistas recentes ou de um gênero.

class Band < ActiveRecord::Base
  belongs_to :genre
 
  def self.recent
    all :order => "created_at desc"
  end
 
  def self.by_genre(genre)
    all :conditions => {:genre_id => genre}
  end
end

Mas como você faria se quisesse todos os artistas recentes de um gênero? E se você quisesse adicionar mais uma condição, como a primeira letra do nome do artista? É quando a coisa se complica! Se você usar o named_scope, pode escrever o exemplo anterior desta maneira:

class Band < ActiveRecord::Base
  belongs_to :genre
 
  named_scope :recent, :order => "created_at desc"
  named_scope :by_genre, lambda {|genre| {:conditions => ["genre_id = ?", genre] }}
end

Ambas abordagens funcionam da mesma maneira quando acessadas sozinhas, como Band.recent ou Band.by_genre(params[:genre_id]). A difença principal do named_scope é que você pode fazer chamadas encadeadas, propagando as opções de consulta. Note que não é preciso seguir nenhuma ordem específica nas chamadas.

Band.recent.by_genre(params[:genre_id])
Band.by_genre(params[:genre_id]).recent

Todas as buscas adicionadas pelo named_scope se comportam como objetos do ActiveRecord. Você pode executar contagens, fazer outras buscas ou agir ativamente nos resultados retornados.

Band.by_genre.first(:conditions => ["name = ?", params[:name]])
Band.recent.reload
Band.recent.count
Band.by_genre(params[:genre_id]).fans.average(:fans_count)

Se você ainda não usa o named_scope, comece a usá-la agora mesmo! Ele é, sem dúvida, uma das melhores funcionalidades do ActiveRecord. Não sei como pude viver sem isto até agora!

Executando métodos sem se preocupar com exceções no Ruby

14/07/08

Cansado de ter que verificar se um objeto existe antes de chamar algum método ou acessar um atributo?

if @object && @object.some && @object.some.fancy && @object.some.fancy.call
  # do something
else
  puts "meh. i don't like this syntax!"
end

Concordo que isto não é muito comum, mas você já deve ter feito algo como isto:

if @object && @object.respond_to?(:some_method) && @object.some_method
  # do something
end

Evite este tipo de aborrecimento trazido por estas verificações, adicionando um método como este:

def try(&block)
  raise 'No block given' unless block_given?
  yield rescue nil
end
 
if try { @objet.some.fancy.call }
  # do something
else
  puts 'hey! this call returned nil, false or threw an exception'
end

Muito mais com a cara de Ruby, não acha?