Created
October 23, 2012 16:26
-
-
Save aalvesjr/3939879 to your computer and use it in GitHub Desktop.
Rails: Otimizando STI com default_scope
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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