Skip to content

Instantly share code, notes, and snippets.

@aalvesjr
Created October 23, 2012 16:26
Show Gist options
  • Select an option

  • Save aalvesjr/3939879 to your computer and use it in GitHub Desktop.

Select an option

Save aalvesjr/3939879 to your computer and use it in GitHub Desktop.
Rails: Otimizando STI com default_scope
fonte: http://fernandoluizao.wordpress.com/2009/08/12/rails-otimizando-sti-com-default_scope/
Não dá pra negar que STI (Single Table Inheritance) é um recurso bacana do Rails. Claro que não é bom abusar, mas em alguns casos, ela se encaixa muito bem. No rails, quando queremos usar STI, basta ter uma coluna type do tipo string na tabela “mãe”, e nessa coluna o Rails se encarrega de gravar o nome da classe do registro, e a partir daí tudo funciona normalmente (para mais detalhes sobre como funciona STI no Rails, veja a api). Porém, apesar da implementação do Rails ser muito elegante e funcionar bem, ela tem alguns problemas:
Comparações entre strings são lentas. Mesmo que haja um índice na coluna “type”, comparações entre strings ainda são bem mais lentas que comparações entre inteiros.
Caso o model seja renomeado, os dados da coluna “type” terão que ser atualizados, ou ficaremos com dados incorretos na tabela. Para corrigir podemos criar uma migration, mas é mais uma coisa para se preocupar
Vamos tentar melhorar isso!
Criando um projetinho de exemplo (NOTA: estou usando Rails 2.3.3. Se usa uma versão mais antiga, veja algumas considerações mais abaixo):
1
rails sti
2
cd sti
3
script/generate model pessoa nome:string documento:string tipo:integer
Estamos criando nossa tabela base, e em vez de usar uma coluna type do tipo string, vamos usar uma coluna chamada tipo (o nome da coluna poderia ser qualquer um), do tipo inteiro.
Vamos editar nossa migração e adicionar um índice na migration para melhorar a performance nas buscas:
01
class CreatePessoas < ActiveRecord::Migration
02
def self.up
03
create_table :pessoas do |t|
04
t.string :nome
05
t.string :documento
06
t.integer :tipo
07
t.timestamps
08
end
09
10
#adicionando o indice
11
add_index :pessoas, [:id, :tipo]
12
end
13
14
def self.down
15
drop_table :pessoas
16
end
17
end
Criando o banco e nossa tabela de testes:
1
rake db:create
2
rake db:migrate
Alterando o model Pessoa para adicionar algumas constantes com os tipos de pessoas:
1
#app/models/pessoa.rb
2
class Pessoa < ActiveRecord::Base
3
# tipos de pessoa
4
JURIDICA = 1
5
FISICA = 2
6
end
Agora, vamos criar dois models que herdam de Pessoa, PessoaFisica e PessoaJuridica, e definir os seus default_scopes:
1
#app/models/pessoa_fisica.rb
2
class PessoaFisica < Pessoa
3
default_scope :conditions => {:tipo => FISICA}
4
end
1
#app/models/pessoa_juridica.rb
2
class PessoaJuridica < Pessoa
3
default_scope :conditions => {:tipo => JURIDICA}
4
end
Só isso? Só . Aparentemente, o Rails está bem espertinho, e quando instânciamos um objeto ele já se encarrega de colocar o valor correto na coluna tipo, da mesma forma que STI faz:
1
Pessoa.new
2
#<Pessoa id: nil, nome: nil, documento: nil, tipo: nil>
3
PessoaJuridica.new
4
#<PessoaJuridica id: nil, nome: nil, documento: nil, tipo: 1>
5
PessoaFisica.new
6
#<PessoaFisica id: nil, nome: nil, documento: nil, tipo: 2>
Criando algumas pessoas:
1
Pessoa.create(:nome => "Pessoa indefinida")
2
PessoaFisica.create(:nome => "João da Silva", :documento => "123.456.789-10")
3
PessoaFisica.create(:nome => "José Oliveira", :documento => "000.111.222-33")
4
PessoaJuridica.create(:nome => "Padaria pão quentinho", :documento => "26.721.163/0001-52")
5
PessoaJuridica.create(:nome => "Transportadora Leva e Traz", :documento => "70.562.197/0001-31")
Buscando:
01
>> Pessoa.all
02
# SELECT * FROM "pessoas"
03
=> [#<Pessoa id: 1, nome: "Pessoa indefinida", documento: nil, tipo: nil, created_at: "2009-08-12 01:08:29", updated_at: "2009-08-12 01:08:29">, #<Pessoa id: 2, nome: "João da Silva", documento: "123.456.789-10", tipo: 2, created_at: "2009-08-12 01:08:29", updated_at: "2009-08-12 01:08:29">, #<Pessoa id: 3, nome: "José Oliveira", documento: "000.111.222-33", tipo: 2, created_at: "2009-08-12 01:08:29", updated_at: "2009-08-12 01:08:29">, #<Pessoa id: 4, nome: "Padaria pão quentinho", documento: "26.721.163/0001-52", tipo: 1, created_at: "2009-08-12 01:08:29", updated_at: "2009-08-12 01:08:29">, #<Pessoa id: 5, nome: "Transportadora Leva e Traz", documento: "70.562.197/0001-31", tipo: 1, created_at: "2009-08-12 01:08:29", updated_at: "2009-08-12 01:08:29">]
04
05
>> PessoaJuridica.all
06
# SELECT * FROM "pessoas" WHERE ("pessoas"."tipo" = 1)
07
=> [#<PessoaJuridica id: 4, nome: "Padaria pão quentinho", documento: "26.721.163/0001-52", tipo: 1, created_at: "2009-08-12 01:08:29", updated_at: "2009-08-12 01:08:29">, #<PessoaJuridica id: 5, nome: "Transportadora Leva e Traz", documento: "70.562.197/0001-31", tipo: 1, created_at: "2009-08-12 01:08:29", updated_at: "2009-08-12 01:08:29">]
08
09
>> PessoaFisica.all
10
# SELECT * FROM "pessoas" WHERE ("pessoas"."tipo" = 2)
11
=> [#<PessoaFisica id: 2, nome: "João da Silva", documento: "123.456.789-10", tipo: 2, created_at: "2009-08-12 01:08:29", updated_at: "2009-08-12 01:08:29">, #<PessoaFisica id: 3, nome: "José Oliveira", documento: "000.111.222-33", tipo: 2, created_at: "2009-08-12 01:08:29", updated_at: "2009-08-12 01:08:29">]
Para quem ainda usa uma versão do Rails menor que 2.3 e maior que 2.0, uma solução é instalar o plugin com o backport do default_scope:
1
script/plugin install git://github.com/duncanbeevers/default_scope.git
Além disso, será necessário usar uma callback para setar a coluna tipo com o valor correto. Por exemplo:
1
class PessoaFisica < Pessoa
2
default_scope :conditions => {:tipo => FISICA}
3
before_save { |p| p.tipo = FISICA }
4
end
Para quem usa uma versão do mais antiga que a 2, já passou da hora de atualizar hem .
Finalizando
Conseguimos o mesmo comportamento da STI, sem as desvantagens citadas anteriormente, usando apenas algumas linhas de código a mais (os default_scopes e as constantes que definimos, no total 4 linhas). Espero que essa dica seja útil, e lembre-se, aprecie STI com moderação .
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment