Skip to content
julho 14, 2008 / cassiomarques

Não confunda ActiveRecord.find com Enumerable.find no Rails!

Eu primeiro aprendi Ruby para depois começar a brincar com o Rails. Esse deveria ser o processo natural e correto, mas sei que nem todo mundo tem começado a usar Rails dessa forma. De qualquer maneira, antes de escrever meus primeiros pet-projects com o Rails eu escrevi muito código Ruby, basicamente scripts de propósito geral.

Uma coisa com a qual me habituei bastante foi a utilizar o método find do módulo Enumerable. Esse método é uma mensagem que pode ser enviada a qualquer objeto de uma classe que inclua o módulo Enumerable em sua definição e serve, óbviamente, para procurar por algo dentro deste objeto. O caso mais comum é a classe Array, ou seja, uma lista de objetos quaisquer.

array = [1, 2, 3, 4, 5]
resultado = array.find { |i| i%2 == 0 }

No exemplo acima o método find vai retornar o primeiro elemento da lista que seja par. Retornaria nil caso nenhum valor atendesse à condição valor%2 == 0.

O problema é que eu fui tentar usar algo parecido dentro de um model em um dos projetos Rails no qual venho trabalhando.
Esse model segue um esquema mais ou menos assim:

class User < ActiveRecord::Base
   has_many :roles, :through => :permissions
end

class Role < AtiveRecord::Base
   has_many :users, :through => :permissions
end

class Permission < ActiveRecord::Base
   belongs_to :user
   belongs_to :role
end
&#91;/source&#93;

Em um determinado momento eu preciso verificar se um usuário possui um determinado role, entao tentei, ingenuamente, fazer algo como
&#91;source language="ruby"&#93;
class User < ActiveRecord::Base
   def has_role?(role)
      roles.find{ |r| r.description == role }
   end
end
&#91;/source&#93;

Simples, deveria funcionar perfeitamente. Só que quando eu rodava esse código, recebia uma mensagem do tipo "can't find Role without an id". Hummmmmm... Bingo! O problema é que quando acesso uma lista que representa um associação com outro objetos de outro model, o active record faz um pesquisa no banco e usa joins em cima dos ids dos objetos, ou seja, o código acima espera receber algo do tipo
&#91;source language="ruby"&#93;
def has_role?(role)
   roles.find :conditions => "blablabla..."
end

Dessa forma o active record usa o valor recebido em :conditions para criar a cláusula “where” do sql enviado ao banco… E não é isso que eu quero, não quero que ele busque filtrando pelo banco, quero que ele busque todos os roles do usuário de uma vez só e pesquise na aplicação.

Mas se eu não posso usar o método find, porque o dito cujo existe na classe ActiveRecord::Base e no módulo Enumerable com o mesmo nome mas funcionalidade completamente diferente, o que fazer?

Simples: O método Enumerable.find na verdade é um alias (um sinônimo) do método Enumerable.detect. Ou seja: se eu enviar para minha lista a mensagem minha_lista.detect { …} eu tenho a funcioalidade que eu quero. E o código fica assim:

def has_role?(role)
   roles.detect { |r| r.description == role }
end

Problema resolvido e mais uma lição aprendida.

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: