Vermeiden von STI in Rails
-
21-09-2019 - |
Frage
class User < ActiveRecord::Base
has_one :location, :dependent => :destroy, :as => :locatable
has_one :ideal_location, :dependent => :destroy, :as => :locatable
has_one :birthplace, :dependent => :destroy, :as => :locatable
end
class Location < ActiveRecord::Base
belongs_to :locatable, :polymorphic => true
end
class IdealLocation < ActiveRecord::Base
end
class Birthplace < ActiveRecord::Base
end
Ich kann nicht wirklich einen Grund sehen Subklassen in dieser Situation zu haben. Das Verhalten der Lage Objekte sind identisch, der einzige Punkt von ihnen ist die Assoziationen leicht zu machen. Ich würde auch die Daten als int speichern bevorzugen und nicht eine Zeichenfolge als es dem Datenbank-Indizes kann kleiner sein.
Ich sehe so etwas wie die folgenden, aber ich kann den Gedanken nicht abgeschlossen:
class User < ActiveRecord::Base
LOCATION_TYPES = { :location => 1, :ideal_location => 2, :birthplace => 3 }
has_one :location, :conditions => ["type = ?", LOCATION_TYPES[:location]], :dependent => :destroy, :as => :locatable
has_one :ideal_location, :conditions => ["type = ?", LOCATION_TYPES[:ideal_location]], :dependent => :destroy, :as => :locatable
has_one :birthplace, :conditions => ["type = ?", LOCATION_TYPES[:birthplace]], :dependent => :destroy, :as => :locatable
end
class Location < ActiveRecord::Base
belongs_to :locatable, :polymorphic => true
end
Mit diesem Code der folgenden fehlschlägt, im Grunde macht es nutzlos:
user = User.first
location = user.build_location
location.city = "Cincinnati"
location.state = "Ohio"
location.save!
location.type # => nil
Dies ist offensichtlich, da es keine Möglichkeit gibt, die zu übersetzen. Bedingungen Optionen auf der has_one Erklärung in den Typen-1-gleich
Ich konnte die ID in der Ansicht überall diese Felder erscheinen einbetten, aber dies scheint falsch zu:
<%= f.hidden_field :type, LOCATION_TYPES[:location] %>
Gibt es eine Möglichkeit die zusätzliche Unterklassen zu vermeiden oder die LOCATION_TYPES nähert Arbeit machen?
In unserem speziellen Fall ist die Anwendung sehr Lage bewusst und Objekte viele verschiedene Arten von Standorten haben. Bin ich nur seltsam zu sein, alle jene Unterklassen nicht wollen?
Irgendwelche Vorschläge Sie geschätzt haben, sagen Sie mir, ich bin verrückt, wenn Sie wollen, aber würden Sie wollen 10+ verschiedene Standortmodelle im Umlauf app / Modelle sehen?
Lösung
Soweit ich es sehen kann, ist ein Ort ein Ort ein Ort ist. Die verschiedenen „Unterklassen“ Sie sich beziehen (IdealLocation, Geburtsort) scheinen nur um die Beziehung zu einem bestimmten Standort des Benutzers zu beschreiben. Hör auf mich, wenn ich das Teil falsch haben.
Das Wissen, dass kann ich zwei Lösungen für diese sehen.
Das ist zuerst zu behandeln Stellen als Wert-Objekte statt Einheiten. (Weitere Informationen zu den Bedingungen: Wert vs Entity-Objekten (Domain Driven Design) ). In dem obigen Beispiel, scheinen Sie den Speicherort „Cincinnati, OH“ zu Einstellung, anstatt ein „Cincinnati, OH“ Objekt aus der Datenbank zu finden. In diesem Fall, wenn viele verschiedene Benutzer in Cincinnati gab, dann würden Sie nur so viele identische „Cincinnati, OH“ Standorte in Ihrer Datenbank haben, wenn man es nur Cincinnati, OH. Für mich, das ist ein klares Zeichen dafür, dass Sie mit einem Wert Objekt arbeiten, keine Einheit.
Wie würde diese Lösung aussehen? Wahrscheinlich eine einfache Lage Objekt wie folgt verwendet:
class Location
attr_accessor :city, :state
def initialize(options={})
@city = options[:city]
@state = options[:state]
end
end
class User < ActiveRecord::Base
serialize :location
serialize :ideal_location
serialize :birthplace
end
@user.ideal_location = Location.new(:city => "Cincinnati", :state => "OH")
@user.birthplace = Location.new(:city => "Detroit", :state => "MI")
@user.save!
@user.ideal_location.state # => "OH"
Die andere Lösung, die ich sehen kann, ist Ihr bestehende Lage Active Modell zu verwenden, sondern einfach zu verwenden, um die Beziehung mit Benutzern die Beziehung „Typen“ zu definieren, etwa so:
class User < ActiveRecord::Base
belongs_to :location, :dependent => :destroy
belongs_to :ideal_location, :class_name => "Location", :dependent => :destroy
belongs_to :birthplace, :class_name => "Location", :dependent => :destroy
end
class Location < ActiveRecord::Base
end
Alles, was Sie brauchen würden, zu tun, um diese Arbeit zu machen ist, umfasst location_id, ideal_location_id und birthplace_id Attribute in Ihrem User-Modell.
Andere Tipps
Warum nicht named_scopes verwenden?
So etwas wie:
class User
has_many :locations
end
class Location
named_scope :ideal, :conditions => "type = 'ideal'"
named_scope :birthplace, :conditions => "type = 'birthplace" # or whatever
end
Dann im Code:
user.locations.ideal => # list of ideal locations
user.locations.birthplace => # list of birthplace locations
Sie würden immer noch zu behandeln haben die Art bei der Erstellung Einstellung, denke ich.
Versuchen BEFORE_SAVE Haken Hinzufügen
class Location
def before_save
self.type = 1
end
end
und ebenso für die anderen Arten von Standort
Sie können das Verhalten von Location kapseln Objekte Module verwendet werden, und einige Makro verwenden, um die Beziehung zu erstellen:
has_one <location_class>,: conditions => [ "type =?" LOCATION_TYPES [: location]],: dependent =>: destroy,: as =>: locatable
Sie können in Ihrem Modul etwas wie folgt verwenden:
module Orders
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def some_class_method(param)
end
def some_other_class_method(param)
end
module InstanceMethods
def some_instance_method
end
end
end
end
Vielleicht etwas Wichtiges hier fehlt mir, aber ich dachte, dass Sie Ihre Beziehungen wie diese nennen könnte:
class User < ActiveRecord::Base
has_one :location, :dependent => :destroy
#ideal_location_id
has_one :ideal_location, :class_name => "Location", :dependent => :destroy
#birthplace_id
has_one :birthplace, :class_name => "Location", :dependent => :destroy
end
class Location < ActiveRecord::Base
belongs_to :user # user_id
end