Fernando Luizão

Desenvolvimento de software e nerdices em geral

Archive for the ‘Plugins’ Category

Tornando dependências opcionais com autoload

with 2 comments

Uma técnica bacana e que pouca gente conhece é usar o autoload para carregar classes. A função do autoload é a mesma do require (por baixo dos panos, o autoload usa o require), a diferença é que o autoload carregará o arquivo apenas quando for necessário. A idéia é que as constantes sejam registradas e associandas ao arquivo que deve ser requerido, por exemplo:

autoload :MinhaLib, 'minha_lib'

Note que o primeiro argumento é um símbolo, e não a classe. Com módulos é a mesma coisa:

module MeuModulo
  autoload :MinhaLib, 'meu_modulo/minha_lib'
end

Ou ainda:

module MeuModulo
  # coisas do meu modulo
end
MeuModulo.autoload :MinhaLib, 'meu_modulo/minha_lib'

Dessa forma, quando a constante MinhaLib (ou MeuModulo::MinhaLib, no caso do módulo) for usada, o arquivo ‘minha_lib‘ será carregado automaticamente. Qual a utilidade disso? Bem, se a constante não for usada, o arquivo não será carregado, o que nos trás duas vantagens:

  • Como as classes desnecessárias não serão carregadas, a biblioteca será carregada mais rapidamente
  • Conseguimos um mecanismo para tornar dependências opcionais

A primeira vantagem é óbvia, mas a segunda é meio implícita e depende de como o projeto está estruturado.
Por exemplo digamos que temos uma gem, que dependa da gem mail, mas apenas se o usuário desejar notificações. Se declararmos a mail como dependência na gemspec, quando alguém for instalar nossa gem, a mail será instalada mesmo que o usuário não precise dela.

O que podemos fazer para relaxar as dependências é usar o autoload para registrar a constante Mail,
e deixar a cargo do usuário instalar a dependência caso ele precise. Um exemplo de como seria o esqueleto da nossa gem:

# minha_gem.rb
autoload :Mail, 'mail'

# faz o que precisa ser feito

# a gem será carregada apenas se o usuário desejar notificações
# as notificações poderiam ser configuradas por um arquivo *.yml por exemplo
if user_wants_notification?
  Mail.new ...
end

Um exmplo de uso dessa técnica pode ser visto na gem backup, que antes especificava várias dependências na gemspec, e após uma pequena reestruturação, deixaram de ser obrigatórias. Veja aqui como foi feito. Outro exemplo de gem que usa essa técnica é a devise, vale a pena dar uma olhada no código :).

Se você desenvolve alguma gem que usa adaptadores, e esses adaptadores dependem de outras gems, você encontrou um bom uso para o autoload, e vai deixar seus usuários mais felizes por não precisarem instalar o que não usarem :).

Referências:

http://www.rubyinside.com/ruby-techniques-revealed-autoload-1652.html
http://ruby-doc.org/core/classes/Module.html

Written by fernandoluizao

May 11, 2010 at 10:11 pm

Posted in Gems, Plugins, Ruby

Tagged with , , ,

Migrando o Brazilian Rails para Ruby 1.9

leave a comment »

Relato passo a passo das alteraçoẽs para migrar o Brazilian Rails:

Adicionados comentários de encoding. commit commit

: (dois pontos) não são mais válidos como separador de instruções. Deve-se trocar por ; ou then, ambas funcionam no 1.8 e 1.9. commit

ParseDate foi removido, Date#parse faz grande parte do que fazia o ParseDate. commit

O 1.9 introduziu o método Numeric#real, e estava sobrescrevendo um alias. commit

Variável $KCODE não tem mais efeito no 1.9, será setada apenas se a versão for anterior a 1.9. commit

A parte mais chata foi relacionada ao parser datas. O Date#parse no 1.9 se comporta de maneira diferente do 1.8, foram introduzidos outros formatos para parseamento, e o parse tenta converter a data com os novos formatos. Exemplo do novo comportamento:

# ruby 1.8.7
Date.parse("13/12/200A")
ArgumentError: invalid date
    from /usr/lib/ruby/1.8/date.rb:956:in `new_by_frags'
    from /usr/lib/ruby/1.8/date.rb:1000:in `parse'
    from (irb):7
    from /usr/lib/ruby/1.8/date.rb:811

# ruby 1.9.1
Date.parse("13/12/200A")
=> #<date: 0200-12-13>

Outra mudança foi que o formato padrão do parse passou a ser dd/mm/yyyy em vez de mm/dd/yyyy:

# ruby 1.8.7
Date.parse("10/12/2009")
=> Seg, 12 Out 2009

# ruby 1.9.1
Date.parse("10/12/2009")
=> Qui, 10 Dez 2009

Levando em conta essas mudanças, alterei o formado de algumas datas nesse teste. Esse outro teste, não está sendo executado no 1.9, justamente pelas diferenças do parse.

Sim, sei que essa solução está longe da ideal, mas para manter o mesmo comportamento seria necessário sobrescrever o parser do 1.9, e não gosto dessa solução… Para quem usa apenas datas no formato brasileiro, não vejo problemas, o Date#valid? continua funcionando (afinal, o propósito da brdata é trabalhar com datas no formato brasileiro :)). O problema é se forem usadas datas em outros formatos, pois aí quem se encarrega de fazer a conversão é o parser do Ruby. Então, se você escolheu usar uby 1.9, esteja ciente das mudanças na classe Date.

Talvez no futuro seja interessante repensar a forma como o método Date#valid? é implementado, mas por hora, creio que deixar a cargo do usuário é razoável, já que é dele a decisão e sobre qual versão do Ruby usar. O suporte da brdata no 1.9 ainda é experimental, então se algém tiver idéias melhores, se pronuncie =).

Finalizando, destaco o papel dos testes na migração. Por sorte o Brazilian Rails tem uma boa cobertura, o que facilitou bastante. A cada alteração feita eu rodava os testes no 1.9 e no 1.8 para ver se estava tudo certo :).

PS: para mudar a versão do Ruby eu uso o ruby_switcher. Também tem o rvm, mas acho o ruby_switcher mais simples.

Written by fernandoluizao

September 12, 2009 at 1:13 pm

Posted in Plugins, Rails, Ruby

Tagged with , ,

acts_as_active: mais um plugin no estilo acts_as_paranoid

leave a comment »

Pra quem é do mundo Rails, o acts_as_paranoid já é velho conhecido, ele sobrescreve alguns métodos do ActiveRecord para marcar um registro como excluído em vez de efetivamente excluí-lo. Ele também adiciona um escopo para esconder os registros “exluídos” dos finders e outras operações comuns como count, sum, max, etc. Meu novo plugin, acts_as_active, faz basicamente a mesma coisa, porém utiliza um booleano em vez de um timestamp para indicar que o registro está ativo e utiliza um escopo padrão para mostrar apenas os ativos.

Instalando o acts_as_active

Pra instalar, o de sempre:

script/plugin install git://github.com/fernandoluizao/acts_as_active.git

IMPORTANTE: Meu plugin depende de um novo recurso adicionado no Rails 2.3 chamado default_scope
Para quem usa Rails 2.1 ou 2.2, instale o plugin default_scope.

script/plugin install git://github.com/duncanbeevers/default_scope.git

Uso

O plugin assume que vc tem um campo booleano com o nome “active”, com valor default TRUE. Exemplo de uso:

  create_table :users do |t|
    t.string :name
    t.string :last_name
    t.boolean :active, :null => false, :default => true
  end

  class User < ActiveRecord::Base
    acts_as_active
  end

  User.first.destroy
  UPDATE users SET active = 'f' WHERE id = 1

  #chama o destroy original e acaba com nosso usuário
  User.first.destroy! 
  DELETE FROM users WHERE id = 1

  User.all
  SELECT * FROM users WHERE active = 't'

  User.all :conditions => {:name => "joe"}
  SELECT * FROM users WHERE (name = 'joe') AND (active = 't')

Se quiser procurar incluindo os inativos:

  User.find_with_inactive :conditions => {:name => "joe"}
  SELECT * FROM users WHERE (name = 'joe')

O nome do campo booleano pode ser configurado da seguinte forma:

class User < ActiveRecord::Base
  acts_as_active :with => :nome_campo
end

É isso, um plugin simples, mas que me poupa um bom trabalho. Espero que gostem :).

Written by fernandoluizao

March 6, 2009 at 12:51 am

Insensitivity: O plugin insensível

leave a comment »

Consultas case insensitive e independentes de acentos que funcionem em qualquer SGBD são difíceis de conseguir, pois cada banco possui sua maneira específica de fazer isso. Se forem tomados alguns cuidados, é possível usar a sintaxe correta de acordo com o adaptador que estamos usando. No PostgreSQL por exemplo, para conseguir uma consulta case insensitive e independente de acentos, temos que fazer coisas assim ou assim.

O problema em usar conversões nas colunas é que, além delas não fazerem parte padrão SQL, perdemos os índices na coluna, o que vai fazer nossa consulta demorar um pouco mais (dependendo da quatidade de registros, usar ou não um índice pode fazer uma grande diferença). Para resolver esse problema, criei o plugin “Insensitivity“, que usa uma coluna extra para armazenar o valor sem acentos e em minúsculas. Assim, durante as consultas, o ActiveRecord irá utilizar essa coluna em vez da coluna com o valor real.

Vantagens dessa abordagem

Independente de banco
Se hover índices, serão usados

Desvantagens

Utiliza o dobro de espaço.

Instalando e usando o plugin

script/plugin install git://github.com/fernandoluizao/insensitivity.git

No seu model, você deve indicar quais campos usarão a pesquisa case insensitive:

class User < ActiveRecord:Base
  insensible :name, :email
end
&#91;/sourcecode&#93;

Depois, você <strong>DEVE</strong> adicionar à tabela, os campos com o mesmo nome dos que foram indicados e com o sufixo "_search". Se desejar, também coloque índices para melhorar a performance. Por exemplo:


script/generate migration add_search_fields

def self.up
  change_table :users do |t|
    t.string :name_search
    t.string :email_search
  end

  add_index :users, :name_search
  add_index :users, :email_search

  #Se sua tabela ja tiver dados, vc pode usar
  #User.make_insensible!
  #Isso vai inicializar os novos campos nos registros já existentes
end

def self.down
  change_table :users do |t|
    t.remove :name_search
    t.remove :email_search
  end
end

Para fazer as buscas, não muda nada. Veja as consultas geradas:

User.find_by_name('bla')
SELECT * FROM users WHERE name_search = 'bla'
User.find(:first, :conditions => ['name = ?', 'bla'], :order => 'name')
SELECT * FROM users WHERE name_search = 'bla' ORDER BY name LIMIT 1

Tudo funciona de forma transparente, sem vc precisar se preocupar =). Sua única preocupação deve ser passar o valor nas conditions sem acentos e em minusculas, para isso pode usar o método “insensible”, adicionado à classe String:

User.find(:first, :conditions => ['name LIKE ?', "%#{params[:name].insensible}%"])
SELECT * FROM users WHERE name_search LIKE '%bla%'

Bom, é isso, pesquisa sem sentimentos :). Quem quiser colaborar:

http://github.com/fernandoluizao/insensitivity/

PS: Testei apenas com rails 2.2.2. Pode não funcionar com versões mais antigas (< 2.0)

Written by fernandoluizao

February 10, 2009 at 11:25 pm