2015-03-27 6 views
10

Ho modellato, Utente ha presenze in un evento.Concatenamento ambito record attivo, ignora join, se già iscritto

class User 
    has_many :attendances 
    has_many :events, through: :attendances 

class Event 
    has_many :attendances 
    scope :is_attending, -> { joins(:attendances).where(attendances:{attend_status: Attendance.attend_statuses[:attending] })} 

class Attendance 
    belongs_to :event 
    belongs_to :user 
    enum attend_status: { attending: 0, not_attending: 1} 

La mia domanda riguarda query mirate e best practice.

Ho messo la maggior parte delle mie query sull'oscilloscopio sull'evento.

voglio ottenere tutti gli eventi per un utente specifico in cui attend_status = 0

user = User.find(...) 
user.events.is_attending 

Logicamente penserei, questa legge il migliore e ha più senso

Tuttavia che mi avrebbe dato un doppio INNER JOIN

SELECT "events".* FROM "events" 
INNER JOIN "attendances" "attendances_events" ON "attendances_events"."event_id" = "events"."id" 
INNER JOIN "attendances" ON "events"."id" = "attendances"."event_id" 
WHERE "attendances"."user_id" = $1 AND "attendances"."attend_status" = 0 

Ovviamente questo crea duplicati che non è quello che volevo.

Così opzioni so che posso fare

1) USO MERGE

Event 
    scope :for_user, -> (user){ joins(:attendances).where(attendances: {user: user})} 

quindi chiamare

Event.for_user(user).merge(Event.is_attending) 

che mi dà la sql

SELECT "events".* FROM "events" INNER JOIN "attendances" ON "attendances"."event_id" = "events"."id" WHERE "attendances"."user_id" = 59 AND "attendances"."attend_status" = 0 

Questo è ciò che Voglio. Ma questa sembra una sintassi terribile e confonde.

2) USO COMPRENDE

Se uso include, invece di unire, non vengo duplicato unisco. Poiché carica eventi separatamente ed è abbastanza intelligente da non duplicare.

Event 
    scope :is_attending, -> { includes(:attendances).where(attendances: {attend_status: Attendance.attend_statuses[:attending] })} 

Tuttavia, non desidero caricare carico.

3) assumere tabella è già unito al di fuori del campo di applicazione

Finalmente posso supporre che la tabella è già unito al di fuori di chiamare il campo di applicazione,

Event 
    scope :is_attending, -> { where(attendances: {attend_status: Attendance.attend_statuses[:attending] })} 

ma questo sembra il design un pò sciocco a me, e rende meno riutilizzabile questo ambito denominato.

Così le mie domande

1) Qual è l'approccio migliore per questo? Il più logico user.events.is_attending è quello che idealmente voglio usare.

2) C'è un modo per dire a Active Record di ignorare i join se sono già accaduti?

risposta

0

Vorrei usare qualcosa vicino al tuo primo suggerimento, tranne che vorrei unire un ambito direttamente sul modello interno (Partecipazione).

Così, invece di:

Event.for_user (utente) .merge (Event.is_attending)

vorrei andare per:

Event.for_user (utente) .merge (Attendance.is_attending)

Questa è una sintassi chiara. Il tuo ambito is_attending non ha bisogno di avere un join e ogni classe è responsabile di sapere come filtrare se stessa.

Se lo si utilizza spesso, è possibile creare aa for_attending_user(user) scopo di nascondere l'unione:

class Event 
    scope :for_attending_user, -> user { for_user(user).merge(Attendance.is_attending) } 
    ... 
0

Puoi aggiungere un'associazione con is_attending condizione User modello:

class Attendance < ActiveRecord::Base 
    belongs_to :event 
    belongs_to :user 
    enum attend_status: { attending: 0, not_attending: 1} 

    scope :is_attending, -> { where(attend_status: attend_statuses[:attending]) } 
end 

class User < ActiveRecord::Base 
    has_many :attendances 
    has_many :events, through: :attendances 

    has_many :attending_events, -> { Attendance.is_attending }, through: :attendances, source: :event 
end 

Ora si può ricevere eventi partecipanti senza duplicazione di join:

u.attending_events 

SELECT "events".* FROM "events" 
INNER JOIN "attendances" ON "events"."id" = "attendances"."event_id" 
WHERE "attendances"."user_id" = 1 AND "attendances"."attend_status" = 0 [["user_id", 1], ["attend_status", 0]] 

Questo approccio ha un inconveniente: non è possibile combinare le condizioni. Ma ha senso se stai lavorando con colonne di stato. Perché questo approccio riflette la logica delle relazioni.