2015-03-27 14 views
10

Mam modelowane, użytkownik ma obecność w wydarzeniu.Aktywne łańcuchowanie zakresu rekordu, ignorowanie złączeń, jeśli jest już połączone

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} 

Moje pytanie dotyczy zapytań o określone zakresy i najlepszych praktyk.

Umieściłem większość moich zapytań o zakres w Wydarzeniu.

Chcę uzyskać wszystkie zdarzenia dla określonego użytkownika, gdzie attend_status = 0

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

Logicznie to myślę, to brzmi najlepiej i najrozsądniej

jednak, że dałoby mnie podwójny INNER DOŁĄCZ

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 

Oczywiście tworzy to duplikaty, które nie są tym, czego chciałem.

Więc opcje wiem, że mogę zrobić

1) Użyj MERGE

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

następnie wywołać

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

który daje mi sql

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

to co Chcę. Ale wydaje się to okropną składnią i jest mylące.

2) wykorzystanie obejmuje

Jeśli używam zawiera zamiast przyłączyć, nie dostać duplikat przyłączyć. Ponieważ ładuje zdarzenia osobno i jest wystarczająco inteligentny, aby nie powielać.

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

Jednak nie chcę dużego ładunku.

3) przyjąć tabela jest już dołączył poza zakresem

Wreszcie mogę założyć, że tabela jest już dołączył poza wywołaniem zakres,

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

Ale to wydaje się trochę głupie projekt do mnie i sprawia, że ​​ten nazwany zakres jest mniej przydatny do ponownego wykorzystania.

więc moje pytania

1) Jakie jest najlepsze podejście do tego? Najbardziej logiczne user.events.is_attending jest tym, którego idealnie chcę użyć.

2) Czy istnieje sposób na poinformowanie Rekordu Aktywnego, aby ignorował połączenia, jeśli już się zdarzyły?

Odpowiedz

0

Użyłbym czegoś zbliżonego do twojej pierwszej sugestii, z tym wyjątkiem, że scaliłbym zakres bezpośrednio na twoim wewnętrznym modelu (Obecność).

Więc zamiast:

Event.for_user (użytkownik) .merge (Event.is_attending)

pójdę do:

Event.for_user (użytkownik) .merge (Attendance.is_attending)

To jest jasna składnia. Twój zasięg is_attending nie musi mieć sprzężenia, a każda klasa jest odpowiedzialna za umiejętność filtrowania.

Jeśli używamy go często, można utworzyć aa zakresu for_attending_user(user) ukryć Merge:

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

możesz zarejestrować stowarzyszenie z is_attending warunku User modelu

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 

Teraz możesz odbieraj zdarzenia bez duplikowania dołączenia:

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]] 

To podejście ma tę wadę: nie można łączyć warunków. Ale ma sens, jeśli pracujesz z kolumnami statusu. Ponieważ to podejście odzwierciedla logikę relacji.