2012-05-21 11 views
5

Aquí están mis modelos ActiveRecord, con Rails 3.2:Relación sobre el alcance de ActiveRecord

class User < ActiveRecord::Base 
    has_one :criterion 
    has_many :user_offer_choices 
end 

class Offer < ActiveRecord::Base 
    has_many :user_offer_choices 

    def seen 
     user_offer_choices.where(seen: true) 
    end 

    def accepted 
     user_offer_choices.where(accepted: true) 
    end 
end 

class Criterion < ActiveRecord::Base 
    belongs_to :user 
end 

class UserOfferChoice < ActiveRecord::Base 
    belongs_to :user 
    belongs_to :offer 
end 

quiero conseguir todos los criterios de los usuarios que han visto una oferta. Algo así como:

Offer.find(11).seen.users.criterions 

pero no sé cómo a ella con ActiveRecord

Sé que puedo hacer algo como:

Criterion.joins(user: { user_offer_choices: :offer }).where(user: { user_offer_choices: {accepted: true, offer_id: 11} }) 

Pero yo quiero ser capaz de usar mis alcances en las ofertas (visto & aceptado). Entonces, ¿cómo puedo hacerlo?

Editar: he encontrado lo que estaba buscando, el método de combinación de Arel: http://benhoskin.gs/2012/07/04/arel-merge-a-hidden-gem

Respuesta

3

En primer lugar, lo que realmente quiere es definir un ámbito de sus opciones.

class UserOfferChoice < ActiveRecord::Base 
    belongs_to :user 
    belongs_to :offer 

    scope :seen, where(seen: true) 
    scope :accepted, where(accepted: true) 
end 

que le permite hacer esto

Offer.find(11).user_offer_choices.seen 

y para obtener los criterios:

Offer.find(1).user_offer_choices.seen.map{|choice| choice.user}.map{|user| user.criterion} 

Ahora bien, esto podría ser limpiado con una cuenta con muchos a través de la clase Oferta.

class Offer < ActiveRecord::Base 
    has_many :user_offer_choices 
    has_many :users, :through => :user_offer_choices 
end 

pero eso nos lleva al usuario, pero omitiendo el alcance.

Offer.find(1).users 

Ahora, hay un truco que puede hacer con Rails 3 alcances, que no se podía hacer con rieles 2.3.5 named_scopes. Los named_scopes tomaron un hash como argumentos pero devolvieron una relación. Los ámbitos de Rails 3 toman una relación, a partir de métodos de consulta como dónde. Para que pueda definir un ámbito en los usuarios, usando el alcance definido en su clase de opciones!

class User < ActiveRecord::Base 
    has_one :criterion 
    has_many :user_offer_choices 
    has_many :offers, :through => :user_offer_choices 

    scope :seen, UserOfferChoice.seen 
    scope :accepted, UserOfferChoice.accepted 
end 

Eso nos permite hacer esto:

Offer.find(1).users.seen 

El mapa ahora se ve así:

Offer.find(1).users.seen.map{|user| user.criterion} 

Por cierto, el plural de criterio es criterios. Escuchar los criterios en mi cabeza cuando lo leo, duele. Puede hacer esto para que Rails conozca el plural:

config/initializers/inflections.rb 
ActiveSupport::Inflector.inflections do |inflect| 
    inflect.plural /^criterion$/i, 'criteria' 
end 
+0

No quiero usar el mapa (u otro método Enum), porque es simple que Ruby y ActiveRecord no generarán SQL. –

+0

¿Por qué es importante usar SQL en lugar de mapa? Eso me parece un requisito artificial arbitrario. Si lo notas, te demostré cómo pasar de anidar dos llamadas a mapa (lo cual es engorroso y difícil de leer) a una solución que se deshace de un mapa y usa SQL y tus ámbitos. Puede intentar extender esa técnica, pero creo que esa solución será engorrosa. (Más sobre el mapa vs SQL en el siguiente comentario).) –

+0

Usar el mapa tiene un inconveniente: pasa por la colección, pero luego hace una llamada a la base de datos en el bucle para eliminar la referencia de la asociación. Cuando cambié el mapa de elección por el has-many-through formulé una llamada SQL para el objeto Oferta ofrecido y creamos la unión para nosotros. Sin embargo, no me preocuparía el rendimiento hasta que determinamos que toda la respuesta web tomó demasiado tiempo. –

Cuestiones relacionadas