2016-09-29 3 views
5

Ich habe ein Event-Modell mit parent_id und date Attribute:Rails: Selbstreferenz Eltern/Kind-Hierarchie, ohne durch Tabelle

Event.rb

has_many :children, :class_name => "Event" 
belongs_to :parent, :class_name => "Event" 

Ich habe keine Probleme event.parent oder event.children aufrufen. Ein Child-Event hat niemals selbst ein Kind.

Ich versuche, diesem Modell einen Bereich hinzuzufügen, damit ich das Kind mit dem nächsten zukünftigen Datum für jedes Elternelement zurückgeben kann. Etwas wie:

scope :future, -> { 
    where("date > ?", Date.today) 
    } 

scope :closest, -> { 
    group('"parent_id"').having('date = MAX(date)') 
} 

Event.future.closest ==> returns the closest child event from every parent 

Aber der oben :closest Umfang zurückkehrt mehr als ein Kind pro Elternteil.

+0

Brauchen Sie mehr als die unmittelbaren Kinder (Enkel, Enkel usw.)? – max

+0

Nein - ein Kind kann kein Kind haben. –

+0

Die Schienen Anleitungen erklären, wie dies zu tun ist: http://guides.rubyonrails.org/association_basics.html#self-joins. Es gibt auch einen Railscast. – baron816

Antwort

1

Ignorieren Rails für einen Moment, was Sie in SQL tun, ist das Problem. Hier sind lots of solutions. Ich würde entweder DISTINCT ON oder LEFT OUTER JOIN LATERAL wählen. Hier ist, wie es in Rails aussehen könnte:

scope :closest, -> { 
    select("DISTINCT ON (parent_id) events.*"). 
    order("parent_id, date ASC") 
} 

Dies wird Ihnen geben das Kind Objekte. (Sie möchten wahrscheinlich auch eine Bedingung, um Zeilen ohne parent_id auszuschließen.) Von Ihren eigenen Lösungen klingt es wie das, was Sie wollen. Wenn Sie stattdessen die übergeordneten Objekte mit einem optionalen angefügten untergeordneten Objekt verwenden möchten, verwenden Sie einen lateralen Join. Das ist jedoch etwas schwieriger, in ActiveRecord zu übersetzen. Wenn es akzeptabel ist es in zwei Abfragen zu tun, das sieht aus wie es funktionieren soll (Kleben mit DISTINCT ON):

has_one :closest_child, -> { 
    select("DISTINCT ON (parent_id) events.*"). 
    order("parent_id, date ASC") 
}, class_name: Event, foreign_key: "parent_id" 

Dann können Sie Event.includes(:closest_child) sagen. Auch hier möchten Sie wahrscheinlich alle Nichteltern herausfiltern.

0

ich landete mit:

scope :closest, -> { 
    where(id: Event.group(:parent_id).minimum(:date).keys) 
    } 
0

Ihre eigene Antwort sieht gut aus, aber ich würde es die folgende Art und Weise verfeinern:

scope :closest, -> { 
    where.not(parent_id: nil).group(:parent_id).minimum(:date) 
} 

Und ganz wichtig oder auch in der Produktion würden Sie die immer erhalten Einsatzdatum wie Date.today, weil es nur in der Entwicklung neu laden wird:

scope :future, -> { 
    where("date > ?", Proc.new { Date.today }) 
} 
Verwandte Themen