4.11. Polymorphe Assoziationen
(polymorphic associations)
Ich zeige Ihnen ein Beispiel, in dem wir ein Model für Autos
(
Car
) und ein Model für Fahrräder
(
Bike
) erstellen. Um ein Auto oder ein Fahrrad zu
beschreiben, verwenden wir ein Model zum Auszeichnen
(
Tag
). Ein Auto und ein Fahrrad kann beliebig viele
tags
haben. Die Applikation:
MacBook:~ xyz$ rails new example
[...]
MacBook:~ xyz$ cd example
MacBook:example xyz$
Jetzt die drei benötigten Modele:
MacBook:example xyz$ rails generate model car name
[...]
MacBook:example xyz$ rails generate model bike name
[...]
MacBook:example xyz$ rails generate model tag name taggable_type taggable_id:integer
[...]
MacBook:example xyz$ rake db:migrate
[...]
MacBook:example xyz$
Car
und Bike
sind klar.
Bei Tag
benutzen wir die Felder
taggable_type
und
taggable_id
, um ActiveRecord eine Möglichkeit zu
geben, die Zuordnung für die polymorphic association abzuspeichern. Dies
müssen wir im Model entsprechend eintragen.
app/models/tag.rb
class Tag < ActiveRecord::Base
attr_accessible :name, :taggable_id, :taggable_type
belongs_to :taggable, :polymorphic => true
end
app/models/car.rb
class Car < ActiveRecord::Base
attr_accessible :name
has_many :tags, :as => :taggable
end
app/models/bike.rb
class Bike < ActiveRecord::Base
attr_accessible :name
has_many :tags, :as => :taggable
end
Wir benutzen bei Car und Bike ein zusätzliches :as =>
:taggable
bei der Definition von has_many. Bei
Tag
benutzen wir belongs_to :taggable,
:polymorphic => true
, um ActiveRecord die polymorphic association
anzuzeigen.
Tipp
Das Suffix „able“
(…
bar) beim Namen „taggable“ ist Rails-üblich, muss aber
nicht sein. Wir brauchen ja zum Verknüpfen jetzt nicht nur die ID des
Eintrags, sondern müssen auch noch wissen, um welches Model es sich eigentlich handelt. Da macht
der Begriff „taggable_type“ halbwegs Sinn.
Gehen wir mal in die
Console und legen ein Auto und ein Fahrrad
an:
MacBook:example xyz$ rails console
Loading development environment (Rails 3.2.3)
1.9.3p194 :001 > golf = Car.create(:name => 'VW Golf')
(0.1ms) begin transaction
SQL (5.2ms) INSERT INTO "cars" ("created_at", "name", "updated_at") VALUES (?, ?, ?) [["created_at", Tue, 08 May 2012 07:20:45 UTC +00:00], ["name", "VW Golf"], ["updated_at", Tue, 08 May 2012 07:20:45 UTC +00:00]]
(2.4ms) commit transaction
=> #<Car id: 1, name: "VW Golf", created_at: "2012-05-08 07:20:45", updated_at: "2012-05-08 07:20:45">
1.9.3p194 :002 > mountainbike = Bike.create(:name => 'Mountainbike')
(0.1ms) begin transaction
SQL (0.6ms) INSERT INTO "bikes" ("created_at", "name", "updated_at") VALUES (?, ?, ?) [["created_at", Tue, 08 May 2012 07:20:51 UTC +00:00], ["name", "Mountainbike"], ["updated_at", Tue, 08 May 2012 07:20:51 UTC +00:00]]
(3.0ms) commit transaction
=> #<Bike id: 1, name: "Mountainbike", created_at: "2012-05-08 07:20:51", updated_at: "2012-05-08 07:20:51">
1.9.3p194 :003 >
Jetzt definieren wir jeweils ein Tag mit der Farbe des entsprechenden
Objektes:
1.9.3p194 :004 > golf.tags.create(:name => 'blau')
(0.1ms) begin transaction
SQL (0.6ms) INSERT INTO "tags" ("created_at", "name", "taggable_id", "taggable_type", "updated_at") VALUES (?, ?, ?, ?, ?) [["created_at", Tue, 08 May 2012 07:23:21 UTC +00:00], ["name", "blau"], ["taggable_id", 1], ["taggable_type", "Car"], ["updated_at", Tue, 08 May 2012 07:23:21 UTC +00:00]]
(1.2ms) commit transaction
=> #<Tag id: 1, name: "blau", taggable_type: "Car", taggable_id: 1, created_at: "2012-05-08 07:23:21", updated_at: "2012-05-08 07:23:21">
1.9.3p194 :005 > mountainbike.tags.create(:name => 'schwarz')
(0.1ms) begin transaction
SQL (0.6ms) INSERT INTO "tags" ("created_at", "name", "taggable_id", "taggable_type", "updated_at") VALUES (?, ?, ?, ?, ?) [["created_at", Tue, 08 May 2012 07:27:11 UTC +00:00], ["name", "schwarz"], ["taggable_id", 1], ["taggable_type", "Bike"], ["updated_at", Tue, 08 May 2012 07:27:11 UTC +00:00]]
(3.1ms) commit transaction
=> #<Tag id: 2, name: "schwarz", taggable_type: "Bike", taggable_id: 1, created_at: "2012-05-08 07:27:11", updated_at: "2012-05-08 07:27:11">
1.9.3p194 :006 >
Beim Golf fügen wir noch ein weiteres
Tag
hinzu:
1.9.3p194 :006 > golf.tags.create(:name => 'Automatik')
(0.1ms) begin transaction
SQL (1.3ms) INSERT INTO "tags" ("created_at", "name", "taggable_id", "taggable_type", "updated_at") VALUES (?, ?, ?, ?, ?) [["created_at", Tue, 08 May 2012 07:28:12 UTC +00:00], ["name", "Automatik"], ["taggable_id", 1], ["taggable_type", "Car"], ["updated_at", Tue, 08 May 2012 07:28:12 UTC +00:00]]
(3.7ms) commit transaction
=> #<Tag id: 3, name: "Automatik", taggable_type: "Car", taggable_id: 1, created_at: "2012-05-08 07:28:12", updated_at: "2012-05-08 07:28:12">
1.9.3p194 :007 >
Schauen wir uns jetzt alle
Tag
-Einträge
an:
1.9.3p194 :007 > Tag.all
Tag Load (0.4ms) SELECT "tags".* FROM "tags"
=> [#<Tag id: 1, name: "blau", taggable_type: "Car", taggable_id: 1, created_at: "2012-05-08 07:23:21", updated_at: "2012-05-08 07:23:21">, #<Tag id: 2, name: "schwarz", taggable_type: "Bike", taggable_id: 1, created_at: "2012-05-08 07:27:11", updated_at: "2012-05-08 07:27:11">, #<Tag id: 3, name: "Automatik", taggable_type: "Car", taggable_id: 1, created_at: "2012-05-08 07:28:12", updated_at: "2012-05-08 07:28:12">]
1.9.3p194 :008 >
Und jetzt alle Tags des Golfs:
1.9.3p194 :008 > golf.tags
Tag Load (0.3ms) SELECT "tags".* FROM "tags" WHERE "tags"."taggable_id" = 1 AND "tags"."taggable_type" = 'Car'
=> [#<Tag id: 1, name: "blau", taggable_type: "Car", taggable_id: 1, created_at: "2012-05-08 07:23:21", updated_at: "2012-05-08 07:23:21">, #<Tag id: 3, name: "Automatik", taggable_type: "Car", taggable_id: 1, created_at: "2012-05-08 07:28:12", updated_at: "2012-05-08 07:28:12">]
1.9.3p194 :009 >
Natürlich können Sie sich auch anzeigen lassen, zu welchem Objekt das
letzte
Tag
gehört:
1.9.3p194 :009 > Tag.last.taggable
Tag Load (0.3ms) SELECT "tags".* FROM "tags" ORDER BY "tags"."id" DESC LIMIT 1
Car Load (0.2ms) SELECT "cars".* FROM "cars" WHERE "cars"."id" = 1 LIMIT 1
=> #<Car id: 1, name: "VW Golf", created_at: "2012-05-08 07:20:45", updated_at: "2012-05-08 07:20:45">
1.9.3p194 :010 > exit
MacBook:example xyz$
Polymorphic associations sind immer praktisch, wenn man die
Datenbankstruktur normalisieren will. Wir hätten in diesem Beispiel ja auch
ein Model CarTag
und BikeTag
definieren können, aber da Tag
für beide gleich ist,
macht hier eine polymorphic association mehr Sinn.
Anmerkung
Polymorphic
Associations sind sehr praktisch. Man sollte aber auch
immer daran denken, dass sie mehr Last auf der Datenbank erzeugen als eine
normale 1:n-Verknüpfung. Normalerweise macht das den Bock nicht fett, aber
man sollte es bei der Planung im Hinterkopf haben.