2010-09-20 14 views
15

Saludos,filtrado de objetos secundarios en un has_many: a través de la relación en Rails 3

Tengo una aplicación donde Companies y Users tienen que pertenecer el uno al otro a través de un modelo de CompanyMembership, que contiene información adicional sobre el número de miembros (en concreto, si el Usuario es o no administrador de la empresa, a través de un valor booleano admin). Una versión simple del código:

class CompanyMembership < ActiveRecord::Base 
    belongs_to :company 
    belongs_to :user 
end 

class Company < ActiveRecord::Base 
    has_many :company_memberships 
    has_many :users, :through => :company_memberships 
end 

class User < ActiveRecord::Base 
    has_many :company_memberships 
    has_many :companies, :through => :company_memberships 
end 

Por supuesto, esto hace que sea sencillo para obtener todos los miembros de una empresa a través de company.users.all, et al. Sin embargo, estoy tratando de obtener una lista de todos los Usuarios de una Compañía que son administradores de esa Compañía (y también para probar si un usuario es un administrador de una compañía determinada). Mi primera solución fue la siguiente en company.rb:

def admins 
    company_memberships.where(:admin => true).collect do |membership| 
    membership.user 
    end 
end 

def is_admin?(user) 
    admins.include? user 
end 

Aunque esto funciona, algo se siente ineficaz al respecto (se itera sobre cada miembro, ejecutar SQL cada vez, a la derecha O es la relación más inteligente que eso??), Y yo No estoy seguro de si hay una forma mejor de hacerlo (tal vez usando los ámbitos o los nuevos y lujosos objetos Relation que utiliza Rails 3).

¡Cualquier consejo sobre la mejor manera de proceder (preferiblemente utilizando las mejores prácticas de Rails 3) sería muy apreciado!

Respuesta

16

creo que iba de este por el camino equivocado, especificando las condiciones de company_memberships en lugar de users, que era lo que realmente quería (una lista de Users, no una lista de CompanyMemberships). La solución creo que estaba buscando es:

users.where(:company_memberships => {:admin => true}) 

que genera el siguiente código SQL (la empresa con el ID de 1):

SELECT "users".* FROM "users" 
    INNER JOIN "company_memberships" 
    ON "users".id = "company_memberships".user_id 
    WHERE (("company_memberships".company_id = 1)) 
    AND ("company_memberships"."admin" = 't') 

No estoy seguro de si yo lo necesitaré , pero el método includes() llevará a cabo la carga con ganas de mantener bajo el número de consultas SQL si es necesario:

Active Record le permite especificar en antemano todas las asociaciones que son va a ser cargado. Esto es posible especificando el método includes de llamada Model.find. Con includes, Active Record garantiza que todas las asociaciones especificadas se carguen utilizando la mínima cantidad posible de consultas de . RoR Guides: ActiveRecord Querying

(todavía estoy abierto a cualquier sugerencia de cualquier persona que piensa que esto no es el mejor/más eficaz/derecha manera de ir sobre esto.)

2

¿Qué tal algo como esto:

Company.find(:id).company_memberships.where(:admin => true).joins(:user) 
+1

¡Un paso más cerca! Si bien no es la respuesta que estaba buscando, me llevó a eso :) –

7

Una forma aún más limpio sería añadir una asociación para su modelo de empresa, algo como esto:

has_many :admins, :through => :company_memberships, :class_name => :user, :conditions => {:admin => true} 

puede que tenga que cavar en el rails doc para obtener el derecho de sintaxis exacta.

No debería necesitar: incluir, a menos que tenga otras clases asociadas con: usuario al que pueda hacer referencia en su vista.

Cuestiones relacionadas