2016-10-25 4 views
0

Meine Tabellen sind so eingerichtet, dass Child eine 1:1 Beziehung mit Parent hat.Erweitern einer ActiveRecord Polymorphic Association mit JOIN-Anweisungen?

teilen Sie einen Primärschlüssel (id):

  • Parent hat id und type und name
  • Child hat id und health

Die Child ist ein polymorpher Erbe des Parent. Mein Ziel ist, dass Child.find(1) ein Objekt Child zurückgeben sollte, das sowohl auf name als auch auf health reagiert. Die SQL-Anweisung würde hypothetisch wie folgt aussehen:

SELECT parents.id, parents.name, childs.health FROM parents LEFT JOIN childs ON childs.id = Parents.id WHERE parents.id = 1 AND parents.type = 'Child' LIMIT 1

So habe ich versucht Polymorphe Vererbung in Activerecord zu verwenden:

class Parent < ApplicationRecord 
class Child < Parent 

Wenn ich versuche, Child.find(1) auszuführen, ich sehe:

SELECT `parents`.* FROM `parents` WHERE `parents`.`type` IN ('Child') AND `parents`.`ID` = 1 LIMIT 1 => #<Child id: 1, type: "Child", name: "Hello">

Bemerkenswerterweise gibt es keine JOIN auf der child Tabelle, aber ich erhalte ein Child Objekt zurück. Dies führt zu dem unerwarteten Verhalten, dass das Objekt Child nicht auf health reagiert. Seltsamerweise, wenn ich eine explizite Tabelle Assoziation zum Child Klasse (self.table_name = "childs") hinzufügen, dann ändert sich die Abfragemuster:

> c = Child.find(1) Obtainable Load (0.3ms) SELECT `childs`.* FROM `childs` WHERE `childs`.`ID` = 2 LIMIT 1

Jetzt kann ich die health zugreifen, aber nicht die type oder name.

Wie kann ich diese JOIN-Verknüpfung korrekt erstellen, sodass ein Versuch, ein Child-Objekt erfolgreich zu laden, die Daten aus dem übergeordneten verknüpft?


Edit: diese Tabellen außerhalb eines Active Migration erstellt wurden (sie werden auch von anderen, bereits bestehenden, nicht-Ruby-Anwendungen zugegriffen wird), so habe ich keine Möglichkeit, ihr Schema zu ändern. Ich kann mir einige ausgefallene Metaprogrammierungsansätze vorstellen, wie zum Beispiel die Antwort auf method_missing, die mir vielleicht die fehlenden Attribute durch einen Join laden lassen würden ... aber ich fürchte, dass ich am Ende ein paar ActiveRecord neu implementieren muss, wie delete, create usw. (was zu Fehlern führen wird). Also suche ich nach einem nativen/sauberen (ish) Weg, dies zu erreichen.

Antwort

2

Dies ist kein typischen Rails polymorphe Assoziation wie hier beschrieben: http://guides.rubyonrails.org/association_basics.html#polymorphic-associations

Also, in diesem Fall, wenn Tabellen durch eine andere App früher erstellt wurden, schlage ich vor, dass Sie so etwas tun:

class Child < ApplicationRecord 
    self.table_name = "childs" 
    belongs_to :parent, foreign_key: :id, dependent: :destroy 
    delegate :name, :type, to: :parent 
    delegate :name=, to: :parent, allow_nil: true 

    after_initialize do 
    self.build_parent(type: "Child") if parent.nil? 
    end 
end 

class Parent < ApplicationRecord 
    self.inheritance_column = 'foo' # otherwise, type column will be used for STI 
    has_one :child, foreign_key: :id 
    delegate :health, to: :child 
end 

und jetzt können Sie die Gesundheit, die Art und den Namen zugreifen:

> c = Child.joins(:parent).find(1) 
    Child Load (0.2ms) SELECT "childs".* FROM "childs" INNER JOIN "parents" ON "parents"."id" = "childs"."id" WHERE "childs"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] 
=> #<Child id: 1, health: "Good", created_at: "2016-10-27 21:42:55", updated_at: "2016-10-27 21:44:08"> 
irb(main):002:0> c.health 
=> "Good" 
irb(main):003:0> c.type 
    Parent Load (0.1ms) SELECT "parents".* FROM "parents" WHERE "parents"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] 
=> "Child" 
irb(main):004:0> c.name 
=> "Hello" 

und ähnliches ist für die Eltern:

p = Parent.joins(:child).find(1) 
    Parent Load (0.1ms) SELECT "parents".* FROM "parents" INNER JOIN "childs" ON "childs"."id" = "parents"."id" WHERE "parents"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] 
=> #<Parent id: 1, type: nil, name: "Hello", created_at: "2016-10-27 21:40:35", updated_at: "2016-10-27 21:40:35"> 
irb(main):003:0> p.name 
=> "Hello" 
irb(main):004:0> p.health 
    Child Load (0.1ms) SELECT "childs".* FROM "childs" WHERE "childs"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] 
=> "Good" 

Wenn Sie möchten, können Sie LEFT JOIN angeben wie folgt aus:

irb(main):003:0> p = Parent.joins("LEFT JOIN childs ON (childs.id = parents.id)").select("parents.id, parents.name, childs.health").find(1) 
    Parent Load (0.2ms) SELECT parents.id, parents.name, childs.health FROM "parents" LEFT JOIN childs ON (childs.id = parents.id) WHERE "parents"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] 
=> #<Parent id: 1, name: "Hello"> 
irb(main):004:0> p.name 
=> "Hello" 
irb(main):005:0> p.health 
    Child Load (0.1ms) SELECT "childs".* FROM "childs" WHERE "childs"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] 
=> "Good" 
+0

Excellent! Große Lösung für ein nicht standardmäßiges Problem. Schnelles Follow-up aber: 'Child.new.save' erzeugt derzeit kein' Parent'-Objekt, und ebenfalls 'delete' wird die verbundenen Tabellen nicht bereinigen. Ich beabsichtige, 'before_ *' Filter zu verwenden, um die Buchhaltung selbst zu implementieren, aber ich bin neugierig, wenn Sie eine bessere Idee haben. –

+1

Ich habe 'Child'-Klasse bearbeitet, so dass automatisch Eltern-Datensatz erstellt und gelöscht wird. 'c = Child.new' und dann' c.name = "Foo" '' c.save' und 'c.destroy' löschen –

+1

Hinweis: Dies ist definitiv kein typischer" Rails-Weg ", aber es könnte funktionieren dieses Nicht-Standard-Problem. –

Verwandte Themen