has_many
zu erklären, erstellen wir eine
Buch-Datenbank. In dieser Datenbank gibt es ein Model mit Büchern (books) und ein
Model mit Autoren (authors). Da
ein Buch mehrere Autoren haben kann, benötigen wir zum Abbilden eine
1:n-Verknüpfung (one-to-many
association). Sie werden sehen, wie einfach das Ganze mit
ActiveRecord ist.MacBook:~ xyz$ rails new bookshelf [...] MacBook:~ xyz$ cd bookshelf MacBook:bookshelf xyz$
MacBook:bookshelf xyz$ rails generate model book title
invoke active_record
create db/migrate/20120506132424_create_books.rb
create app/models/book.rb
invoke test_unit
create test/unit/book_test.rb
create test/fixtures/books.yml
MacBook:bookshelf xyz$
book
) mit angehängtem _id
gesetzt:MacBook:bookshelf xyz$ rails generate model author book_id:integer first_name last_name
invoke active_record
create db/migrate/20120506132619_create_authors.rb
create app/models/author.rb
invoke test_unit
create test/unit/author_test.rb
create test/fixtures/authors.yml
MacBook:bookshelf xyz$
MacBook:bookshelf xyz$ rake db:migrate
== CreateBooks: migrating ====================================================
-- create_table(:books)
-> 0.0014s
== CreateBooks: migrated (0.0015s) ===========================================
== CreateAuthors: migrating ==================================================
-- create_table(:authors)
-> 0.0015s
== CreateAuthors: migrated (0.0015s) =========================================
MacBook:bookshelf xyz$
MacBook:bookshelf xyz$ rails console Loading development environment (Rails 3.2.3) 1.9.3p194 :001 > Book => Book(id: integer, title: string, created_at: datetime, updated_at: datetime) 1.9.3p194 :002 > Author => Author(id: integer, book_id: integer, first_name: string, last_name: string, created_at: datetime, updated_at: datetime) 1.9.3p194 :003 > exit MacBook:bookshelf xyz$
app/models/book.rb
die Option
has_many
ein:class Book < ActiveRecord::Base
attr_accessible :title
has_many :authors
end
app/models/author.rb
die Option
belongs_to
ein:class Author < ActiveRecord::Base
attr_accessible :book_id, :first_name, :last_name
belongs_to :book
end
MacBook:bookshelf xyz$ rake db:reset
-- create_table("authors", {:force=>true})
-> 0.0151s
-- create_table("books", {:force=>true})
-> 0.0024s
-- initialize_schema_migrations_table()
-> 0.0033s
-- assume_migrated_upto_version(20120506132619, ["/Users/xyz/bookshelf/db/migrate"])
-> 0.0033s
MacBook:bookshelf xyz$
book_id
:MacBook:bookshelf xyz$ rails console Loading development environment (Rails 3.2.3) 1.9.3p194 :001 > book = Book.create(:title => 'Homo faber') (0.1ms) begin transaction SQL (5.0ms) INSERT INTO "books" ("created_at", "title", "updated_at") VALUES (?, ?, ?) [["created_at", Sun, 06 May 2012 13:47:45 UTC +00:00], ["title", "Homo faber"], ["updated_at", Sun, 06 May 2012 13:47:45 UTC +00:00]] (1.2ms) commit transaction => #<Book id: 1, title: "Homo faber", created_at: "2012-05-06 13:47:45", updated_at: "2012-05-06 13:47:45"> 1.9.3p194 :002 > author = Author.create(:book_id => 1, :first_name => 'Max', :last_name => 'Frisch') (0.1ms) begin transaction SQL (0.7ms) INSERT INTO "authors" ("book_id", "created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?, ?) [["book_id", 1], ["created_at", Sun, 06 May 2012 13:48:17 UTC +00:00], ["first_name", "Max"], ["last_name", "Frisch"], ["updated_at", Sun, 06 May 2012 13:48:17 UTC +00:00]] (3.1ms) commit transaction => #<Author id: 1, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-05-06 13:48:17", updated_at: "2012-05-06 13:48:17"> 1.9.3p194 :003 > Book.all Book Load (0.3ms) SELECT "books".* FROM "books" => [#<Book id: 1, title: "Homo faber", created_at: "2012-05-06 13:47:45", updated_at: "2012-05-06 13:47:45">] 1.9.3p194 :004 > Author.all Author Load (0.3ms) SELECT "authors".* FROM "authors" => [#<Author id: 1, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-05-06 13:48:17", updated_at: "2012-05-06 13:48:17">] 1.9.3p194 :005 > exit MacBook:bookshelf xyz$
book_id
quasi manuell einzutragen, ist
natürlich sehr unpraktisch und fehleranfällig. Deshalb gibt es die
Methode „create“.create
von authors
zu jedem
Book
-Objekt neue Autoren hinzufügen. Diese werden
automatisch korrekt mit der book_id
bestückt:MacBook:bookshelf xyz$ rake db:reset [...] MacBook:bookshelf xyz$ rails console Loading development environment (Rails 3.2.3) 1.9.3p194 :001 > book = Book.create(:title => 'Homo faber') (0.1ms) begin transaction SQL (4.9ms) INSERT INTO "books" ("created_at", "title", "updated_at") VALUES (?, ?, ?) [["created_at", Sun, 06 May 2012 13:52:28 UTC +00:00], ["title", "Homo faber"], ["updated_at", Sun, 06 May 2012 13:52:28 UTC +00:00]] (2.6ms) commit transaction => #<Book id: 1, title: "Homo faber", created_at: "2012-05-06 13:52:28", updated_at: "2012-05-06 13:52:28"> 1.9.3p194 :002 > author = book.authors.create(:first_name => 'Max', :last_name => 'Frisch') (0.1ms) begin transaction SQL (0.6ms) INSERT INTO "authors" ("book_id", "created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?, ?) [["book_id", 1], ["created_at", Sun, 06 May 2012 13:52:52 UTC +00:00], ["first_name", "Max"], ["last_name", "Frisch"], ["updated_at", Sun, 06 May 2012 13:52:52 UTC +00:00]] (0.8ms) commit transaction => #<Author id: 1, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-05-06 13:52:52", updated_at: "2012-05-06 13:52:52"> 1.9.3p194 :003 > Book.all Book Load (0.3ms) SELECT "books".* FROM "books" => [#<Book id: 1, title: "Homo faber", created_at: "2012-05-06 13:52:28", updated_at: "2012-05-06 13:52:28">] 1.9.3p194 :004 > Author.all Author Load (0.3ms) SELECT "authors".* FROM "authors" => [#<Author id: 1, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-05-06 13:52:52", updated_at: "2012-05-06 13:52:52">] 1.9.3p194 :005 > exit MacBook:bookshelf xyz$
authors.create()
hinter das
Book.create()
setzen:MacBook:bookshelf xyz$ rake db:reset [...] MacBook:bookshelf xyz$ rails console Loading development environment (Rails 3.2.3) 1.9.3p194 :001 > Book.create(:title => 'Homo faber').authors.create(:first_name => 'Max', :last_name => 'Frisch') (0.1ms) begin transaction SQL (5.2ms) INSERT INTO "books" ("created_at", "title", "updated_at") VALUES (?, ?, ?) [["created_at", Sun, 06 May 2012 13:56:38 UTC +00:00], ["title", "Homo faber"], ["updated_at", Sun, 06 May 2012 13:56:38 UTC +00:00]] (3.6ms) commit transaction (0.1ms) begin transaction SQL (0.6ms) INSERT INTO "authors" ("book_id", "created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?, ?) [["book_id", 1], ["created_at", Sun, 06 May 2012 13:56:38 UTC +00:00], ["first_name", "Max"], ["last_name", "Frisch"], ["updated_at", Sun, 06 May 2012 13:56:38 UTC +00:00]] (1.1ms) commit transaction => #<Author id: 1, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-05-06 13:56:38", updated_at: "2012-05-06 13:56:38"> 1.9.3p194 :002 > Book.all Book Load (0.3ms) SELECT "books".* FROM "books" => [#<Book id: 1, title: "Homo faber", created_at: "2012-05-06 13:56:38", updated_at: "2012-05-06 13:56:38">] 1.9.3p194 :003 > Author.all Author Load (0.3ms) SELECT "authors".* FROM "authors" => [#<Author id: 1, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-05-06 13:56:38", updated_at: "2012-05-06 13:56:38">] 1.9.3p194 :004 > exit MacBook:bookshelf xyz$
create
statt nur einem Hash alternativ auch ein Array von Hashes akzeptiert, können Sie auch mehrere
Autoren für ein Buch auf einmal anlegen:MacBook:bookshelf xyz$ rake db:reset [...] MacBook:bookshelf xyz$ rails console Loading development environment (Rails 3.2.3) 1.9.3p194 :001 > Book.create(:title => 'Beispiel').authors.create([{:last_name => 'A'}, {:last_name => 'B'}, {:last_name => 'C'}]) (0.1ms) begin transaction SQL (5.0ms) INSERT INTO "books" ("created_at", "title", "updated_at") VALUES (?, ?, ?) [["created_at", Sun, 06 May 2012 14:03:27 UTC +00:00], ["title", "Beispiel"], ["updated_at", Sun, 06 May 2012 14:03:27 UTC +00:00]] (2.7ms) commit transaction (0.1ms) begin transaction SQL (0.6ms) INSERT INTO "authors" ("book_id", "created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?, ?) [["book_id", 1], ["created_at", Sun, 06 May 2012 14:03:27 UTC +00:00], ["first_name", nil], ["last_name", "A"], ["updated_at", Sun, 06 May 2012 14:03:27 UTC +00:00]] (0.9ms) commit transaction (0.0ms) begin transaction SQL (0.4ms) INSERT INTO "authors" ("book_id", "created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?, ?) [["book_id", 1], ["created_at", Sun, 06 May 2012 14:03:27 UTC +00:00], ["first_name", nil], ["last_name", "B"], ["updated_at", Sun, 06 May 2012 14:03:27 UTC +00:00]] (0.8ms) commit transaction (0.0ms) begin transaction SQL (0.4ms) INSERT INTO "authors" ("book_id", "created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?, ?) [["book_id", 1], ["created_at", Sun, 06 May 2012 14:03:27 UTC +00:00], ["first_name", nil], ["last_name", "C"], ["updated_at", Sun, 06 May 2012 14:03:27 UTC +00:00]] (1.0ms) commit transaction => [#<Author id: 1, book_id: 1, first_name: nil, last_name: "A", created_at: "2012-05-06 14:03:27", updated_at: "2012-05-06 14:03:27">, #<Author id: 2, book_id: 1, first_name: nil, last_name: "B", created_at: "2012-05-06 14:03:27", updated_at: "2012-05-06 14:03:27">, #<Author id: 3, book_id: 1, first_name: nil, last_name: "C", created_at: "2012-05-06 14:03:27", updated_at: "2012-05-06 14:03:27">] 1.9.3p194 :002 > Book.all Book Load (0.3ms) SELECT "books".* FROM "books" => [#<Book id: 1, title: "Beispiel", created_at: "2012-05-06 14:03:27", updated_at: "2012-05-06 14:03:27">] 1.9.3p194 :003 > Author.all Author Load (0.3ms) SELECT "authors".* FROM "authors" => [#<Author id: 1, book_id: 1, first_name: nil, last_name: "A", created_at: "2012-05-06 14:03:27", updated_at: "2012-05-06 14:03:27">, #<Author id: 2, book_id: 1, first_name: nil, last_name: "B", created_at: "2012-05-06 14:03:27", updated_at: "2012-05-06 14:03:27">, #<Author id: 3, book_id: 1, first_name: nil, last_name: "C", created_at: "2012-05-06 14:03:27", updated_at: "2012-05-06 14:03:27">] 1.9.3p194 :004 > exit MacBook:bookshelf xyz$
build
ähnelt
create
. Allerdings wird der Datensatz nicht
abgespeichert. Dies erfolgt erst nach einem
save
:MacBook:bookshelf xyz$ rake db:reset [...] MacBook:bookshelf xyz$ rails console Loading development environment (Rails 3.2.3) 1.9.3p194 :001 > book = Book.create(:title => 'Homo faber') (0.1ms) begin transaction SQL (4.9ms) INSERT INTO "books" ("created_at", "title", "updated_at") VALUES (?, ?, ?) [["created_at", Sun, 06 May 2012 14:05:40 UTC +00:00], ["title", "Homo faber"], ["updated_at", Sun, 06 May 2012 14:05:40 UTC +00:00]] (3.9ms) commit transaction => #<Book id: 1, title: "Homo faber", created_at: "2012-05-06 14:05:40", updated_at: "2012-05-06 14:05:40"> 1.9.3p194 :002 > author = book.authors.build(:first_name => 'Max', :last_name => 'Frisch') => #<Author id: nil, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: nil, updated_at: nil> 1.9.3p194 :003 > author.new_record? => true 1.9.3p194 :004 > author.save (0.1ms) begin transaction SQL (1.7ms) INSERT INTO "authors" ("book_id", "created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?, ?) [["book_id", 1], ["created_at", Sun, 06 May 2012 14:06:29 UTC +00:00], ["first_name", "Max"], ["last_name", "Frisch"], ["updated_at", Sun, 06 May 2012 14:06:29 UTC +00:00]] (3.3ms) commit transaction => true 1.9.3p194 :005 > author.new_record? => false 1.9.3p194 :006 > exit MacBook:bookshelf xyz$
create
und
build
müssen natürlich logische
Abhängigkeiten beachtet werden, sonst gibt es einen Fehler. So kann
man nicht zwei build
-Methoden miteinander
verketten. Beispiel:MacBook:bookshelf xyz$ rails console Loading development environment (Rails 3.2.3) 1.9.3p194 :001 > Book.build(:title => 'Beispiel').authors.build(:last_name => 'A') NoMethodError: undefined method `build' for #<Class:0x007ff8c52d2330> from /Users/xyz/.rvm/gems/ruby-1.9.3-p194/gems/activerecord-3.2.3/lib/active_record/dynamic_matchers.rb:50:in `method_missing' from (irb):1 from /Users/xyz/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.2.3/lib/rails/commands/console.rb:47:in `start' from /Users/xyz/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.2.3/lib/rails/commands/console.rb:8:in `start' from /Users/xyz/.rvm/gems/ruby-1.9.3-p194/gems/railties-3.2.3/lib/rails/commands.rb:41:in `<top (required)>' from script/rails:6:in `require' from script/rails:6:in `<main>' 1.9.3p194 :002 > exit MacBook:bookshelf xyz$
db/seeds.rb
mit folgendem Inhalt:# ruby encoding: utf-8 Book.create(:title => 'Homo faber').authors.create(:first_name => 'Max', :last_name => 'Frisch') Book.create(:title => 'Der Besuch der alten Dame').authors.create(:first_name => 'Friedrich', :last_name => 'Dürrenmatt') Book.create(:title => 'Julius Shulman: The Last Decade').authors.create([ {:first_name => 'Thomas', :last_name => 'Schirmbock'}, {:first_name => 'Julius', :last_name => 'Shulman'}, {:first_name => 'Jürgen', :last_name => 'Nogai'} ]) Book.create(:title => 'Julius Shulman: Palm Springs').authors.create([ {:first_name => 'Michael', :last_name => 'Stern'}, {:first_name => 'Alan', :last_name => 'Hess'} ]) Book.create(:title => 'Photographing Architecture and Interiors').authors.create([ {:first_name => 'Julius', :last_name => 'Shulman'}, {:first_name => 'Richard', :last_name => 'Neutra'} ]) Book.create(:title => 'Der Zauberberg').authors.create(:first_name => 'Thomas', :last_name => 'Mann') Book.create(:title => 'In einer Familie').authors.create(:first_name => 'Heinrich', :last_name => 'Mann')
db/seeds.rb
befüllen:MacBook:bookshelf xyz$ rake db:reset
[...]
MacBook:bookshelf xyz$
MacBook:bookshelf xyz$ rails console Loading development environment (Rails 3.2.3) 1.9.3p194 :001 > Book.first Book Load (0.1ms) SELECT "books".* FROM "books" LIMIT 1 => #<Book id: 1, title: "Homo faber", created_at: "2012-05-06 14:23:43", updated_at: "2012-05-06 14:23:43"> 1.9.3p194 :002 > Book.first.authors Book Load (0.3ms) SELECT "books".* FROM "books" LIMIT 1 Author Load (0.2ms) SELECT "authors".* FROM "authors" WHERE "authors"."book_id" = 1 => [#<Author id: 1, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-05-06 14:23:43", updated_at: "2012-05-06 14:23:43">] 1.9.3p194 :003 > exit MacBook:bookshelf xyz$
MacBook:bookshelf xyz$ rails console Loading development environment (Rails 3.2.3) 1.9.3p194 :001 > Author.first Author Load (0.1ms) SELECT "authors".* FROM "authors" LIMIT 1 => #<Author id: 1, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-05-06 14:23:43", updated_at: "2012-05-06 14:23:43"> 1.9.3p194 :002 > Author.first.book Author Load (0.3ms) SELECT "authors".* FROM "authors" LIMIT 1 Book Load (0.2ms) SELECT "books".* FROM "books" WHERE "books"."id" = 1 LIMIT 1 => #<Book id: 1, title: "Homo faber", created_at: "2012-05-06 14:23:43", updated_at: "2012-05-06 14:23:43"> 1.9.3p194 :003 > exit MacBook:bookshelf xyz$
Book
-Klasse ist ganz einfach. Und da es sich nur um
einen einzigen Datensatz handelt (belongs_to
),
wird hier die Singularform genommen.Book
den Wert
nil
aus.db/seeds.rb
mit folgendem
Inhalt:# ruby encoding: utf-8 Book.create(:title => 'Homo faber').authors.create(:first_name => 'Max', :last_name => 'Frisch') Book.create(:title => 'Der Besuch der alten Dame').authors.create(:first_name => 'Friedrich', :last_name => 'Dürrenmatt') Book.create(:title => 'Julius Shulman: The Last Decade').authors.create([ {:first_name => 'Thomas', :last_name => 'Schirmbock'}, {:first_name => 'Julius', :last_name => 'Shulman'}, {:first_name => 'Jürgen', :last_name => 'Nogai'} ]) Book.create(:title => 'Julius Shulman: Palm Springs').authors.create([ {:first_name => 'Michael', :last_name => 'Stern'}, {:first_name => 'Alan', :last_name => 'Hess'} ]) Book.create(:title => 'Photographing Architecture and Interiors').authors.create([ {:first_name => 'Julius', :last_name => 'Shulman'}, {:first_name => 'Richard', :last_name => 'Neutra'} ]) Book.create(:title => 'Der Zauberberg').authors.create(:first_name => 'Thomas', :last_name => 'Mann') Book.create(:title => 'In einer Familie').authors.create(:first_name => 'Heinrich', :last_name => 'Mann')
db/seeds.rb
befüllen:MacBook:bookshelf xyz$ rake db:reset
[...]
MacBook:bookshelf xyz$
MacBook:bookshelf xyz$ rails console Loading development environment (Rails 3.2.3) 1.9.3p194 :001 > Book.count (0.1ms) SELECT COUNT(*) FROM "books" => 7 1.9.3p194 :002 >
1.9.3p194 :002 > Author.count (0.2ms) SELECT COUNT(*) FROM "authors" => 11 1.9.3p194 :003 > exit MacBook:bookshelf xyz$
MacBook:bookshelf xyz$ rails console Loading development environment (Rails 3.2.3) 1.9.3p194 :001 > Book.joins(:authors).where(:authors => {:last_name => 'Mann'}) Book Load (0.2ms) SELECT "books".* FROM "books" INNER JOIN "authors" ON "authors"."book_id" = "books"."id" WHERE "authors"."last_name" = 'Mann' => [#<Book id: 6, title: "Der Zauberberg", created_at: "2012-05-07 07:14:08", updated_at: "2012-05-07 07:14:08">, #<Book id: 7, title: "In einer Familie", created_at: "2012-05-07 07:14:08", updated_at: "2012-05-07 07:14:08">] 1.9.3p194 :002 > Book.joins(:authors).where(:authors => {:last_name => 'Mann'}).count (0.4ms) SELECT COUNT(*) FROM "books" INNER JOIN "authors" ON "authors"."book_id" = "books"."id" WHERE "authors"."last_name" = 'Mann' => 2 1.9.3p194 :003 > exit MacBook:bookshelf xyz$
joins
ein
INNER JOIN
ausführt.MacBook:bookshelf xyz$ rails console Loading development environment (Rails 3.2.3) 1.9.3p194 :001 > Author.joins(:book).where(:books => {:title => 'Homo faber'}) Author Load (0.2ms) SELECT "authors".* FROM "authors" INNER JOIN "books" ON "books"."id" = "authors"."book_id" WHERE "books"."title" = 'Homo faber' => [#<Author id: 1, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-05-07 07:14:08", updated_at: "2012-05-07 07:14:08">] 1.9.3p194 :002 > exit MacBook:bookshelf xyz$
includes
ähnelt sehr der Methode
joins
(siehe „joins“). Auch damit kann man innerhalb einer
1:n-Verknüpfung suchen. Suchen wir noch mal alle Bücher mit einem Autor,
der als Nachnamen 'Mann' hat:MacBook:bookshelf xyz$ rails console Loading development environment (Rails 3.2.3) 1.9.3p194 :001 > Book.includes(:authors).where(:authors => {:last_name => 'Mann'}) SQL (0.4ms) SELECT "books"."id" AS t0_r0, "books"."title" AS t0_r1, "books"."created_at" AS t0_r2, "books"."updated_at" AS t0_r3, "authors"."id" AS t1_r0, "authors"."book_id" AS t1_r1, "authors"."first_name" AS t1_r2, "authors"."last_name" AS t1_r3, "authors"."created_at" AS t1_r4, "authors"."updated_at" AS t1_r5 FROM "books" LEFT OUTER JOIN "authors" ON "authors"."book_id" = "books"."id" WHERE "authors"."last_name" = 'Mann' => [#<Book id: 6, title: "Der Zauberberg", created_at: "2012-05-07 07:14:08", updated_at: "2012-05-07 07:14:08">, #<Book id: 7, title: "In einer Familie", created_at: "2012-05-07 07:14:08", updated_at: "2012-05-07 07:14:08">] 1.9.3p194 :002 > exit MacBook:bookshelf xyz$
joins
-Abfrage unterscheidet.joins
liest nur die
Book
-Datensätze ein und
includes
liest auch noch die dazugehörigen
Authors
. Schon bei unserem kleinen Beispiel sieht
man, dass dies natürlich länger dauert (0.2 ms vs. 0.4 ms).includes
dann
überhaupt Sinn machen? Wenn Sie schon bei der Abfrage wissen, dass Sie
später alle Autorendaten benötigen, dann ist ein
includes
sinnvoll, weil dann nur eine
Datenbankanfrage gestellt wird. ActiveRecord „cach't“ nämlich die Antwort.includes
zu arbeiten? Nein, es kommt immer auf
den konkreten Fall an. Denn bei der Benutzung von
includes
werden ja initial viel mehr Daten
transportiert. Diese müssen von Ruby gecached und verarbeitet werden.
Die Verarbeitung dauert also länger und verbraucht mehr
Resourcen.destroy
,
destroy_all
, delete
und
delete_all
kann man, wie in Abschnitt 4.12, „Einen Datensatz löschen“ beschrieben, Datensätze löschen. Im
Kontext von has_many
bedeutet das, dass man die
zu einem Book
gehörigen
Author
-Datensätze in einem Streich löschen
kann:MacBook:bookshelf xyz$ rails console Loading development environment (Rails 3.2.3) 1.9.3p194 :001 > book = Book.find_by_title('Julius Shulman: The Last Decade') Book Load (0.3ms) SELECT "books".* FROM "books" WHERE "books"."title" = 'Julius Shulman: The Last Decade' LIMIT 1 => #<Book id: 3, title: "Julius Shulman: The Last Decade", created_at: "2012-05-07 07:36:41", updated_at: "2012-05-07 07:36:41"> 1.9.3p194 :002 > book.authors Author Load (0.2ms) SELECT "authors".* FROM "authors" WHERE "authors"."book_id" = 3 => [#<Author id: 3, book_id: 3, first_name: "Thomas", last_name: "Schirmbock", created_at: "2012-05-07 07:36:41", updated_at: "2012-05-07 07:36:41">, #<Author id: 4, book_id: 3, first_name: "Julius", last_name: "Shulman", created_at: "2012-05-07 07:36:41", updated_at: "2012-05-07 07:36:41">, #<Author id: 5, book_id: 3, first_name: "Jürgen", last_name: "Nogai", created_at: "2012-05-07 07:36:41", updated_at: "2012-05-07 07:36:41">] 1.9.3p194 :003 > book.authors.destroy_all (0.1ms) begin transaction SQL (5.5ms) DELETE FROM "authors" WHERE "authors"."id" = ? [["id", 3]] SQL (0.0ms) DELETE FROM "authors" WHERE "authors"."id" = ? [["id", 4]] SQL (0.0ms) DELETE FROM "authors" WHERE "authors"."id" = ? [["id", 5]] (3.2ms) commit transaction => [#<Author id: 3, book_id: 3, first_name: "Thomas", last_name: "Schirmbock", created_at: "2012-05-07 07:36:41", updated_at: "2012-05-07 07:36:41">, #<Author id: 4, book_id: 3, first_name: "Julius", last_name: "Shulman", created_at: "2012-05-07 07:36:41", updated_at: "2012-05-07 07:36:41">, #<Author id: 5, book_id: 3, first_name: "Jürgen", last_name: "Nogai", created_at: "2012-05-07 07:36:41", updated_at: "2012-05-07 07:36:41">] 1.9.3p194 :004 > book.authors => [] 1.9.3p194 :005 > exit MacBook:bookshelf xyz$
http://rails.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
bzw. auf Ihrem System auf der Shell mit ri
ActiveRecord::Associations::ClassMethods aufrufen können.belongs_to
ist::touch
=> :true
Author
automatisch das Feld
updated_at
des Eintrags in der Tabelle
Book
auf die aktuelle Uhrzeit gesetzt. Das
sähe in der app/models/author.rb
folgendermaßen aus:class Author < ActiveRecord::Base
attr_accessible :book_id, :first_name, :last_name
belongs_to :book, :touch => true
end
has_many
::order
=> :last_name
app/models/book.rb
erreichen:class Book < ActiveRecord::Base
attr_accessible :title
has_many :authors, :order => :last_name
end
MacBook:bookshelf xyz$ rails console Loading development environment (Rails 3.2.3) 1.9.3p194 :001 > Book.create(:title => 'Test').authors.create([{:last_name => 'Z'}, {:last_name => 'A'}]) (0.1ms) begin transaction SQL (5.9ms) INSERT INTO "books" ("created_at", "title", "updated_at") VALUES (?, ?, ?) [["created_at", Mon, 07 May 2012 07:45:34 UTC +00:00], ["title", "Test"], ["updated_at", Mon, 07 May 2012 07:45:34 UTC +00:00]] (3.6ms) commit transaction (0.1ms) begin transaction SQL (0.7ms) INSERT INTO "authors" ("book_id", "created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?, ?) [["book_id", 8], ["created_at", Mon, 07 May 2012 07:45:34 UTC +00:00], ["first_name", nil], ["last_name", "Z"], ["updated_at", Mon, 07 May 2012 07:45:34 UTC +00:00]] (1.1ms) commit transaction (0.1ms) begin transaction SQL (0.4ms) INSERT INTO "authors" ("book_id", "created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?, ?) [["book_id", 8], ["created_at", Mon, 07 May 2012 07:45:34 UTC +00:00], ["first_name", nil], ["last_name", "A"], ["updated_at", Mon, 07 May 2012 07:45:34 UTC +00:00]] (0.9ms) commit transaction => [#<Author id: 12, book_id: 8, first_name: nil, last_name: "Z", created_at: "2012-05-07 07:45:34", updated_at: "2012-05-07 07:45:34">, #<Author id: 13, book_id: 8, first_name: nil, last_name: "A", created_at: "2012-05-07 07:45:34", updated_at: "2012-05-07 07:45:34">] 1.9.3p194 :002 > Book.last.authors Book Load (0.4ms) SELECT "books".* FROM "books" ORDER BY "books"."id" DESC LIMIT 1 Author Load (0.4ms) SELECT "authors".* FROM "authors" WHERE "authors"."book_id" = 8 ORDER BY last_name => [#<Author id: 13, book_id: 8, first_name: nil, last_name: "A", created_at: "2012-05-07 07:45:34", updated_at: "2012-05-07 07:45:34">, #<Author id: 12, book_id: 8, first_name: nil, last_name: "Z", created_at: "2012-05-07 07:45:34", updated_at: "2012-05-07 07:45:34">] 1.9.3p194 :003 > exit MacBook:bookshelf xyz$
has_many :authors, :order => 'title DESC'
:dependent
=>
:destroy
:dependent =>
:destroy
in der app/models/book.rb
realisiert werden:class Book < ActiveRecord::Base
attr_accessible :title
has_many :authors, :dependent => :destroy
end
MacBook:bookshelf xyz$ rails console Loading development environment (Rails 3.2.3) 1.9.3p194 :001 > Book.first Book Load (0.2ms) SELECT "books".* FROM "books" LIMIT 1 => #<Book id: 1, title: "Homo faber", created_at: "2012-05-07 07:44:53", updated_at: "2012-05-07 07:44:53"> 1.9.3p194 :002 > Book.first.authors Book Load (0.3ms) SELECT "books".* FROM "books" LIMIT 1 Author Load (0.2ms) SELECT "authors".* FROM "authors" WHERE "authors"."book_id" = 1 => [#<Author id: 1, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-05-07 07:44:53", updated_at: "2012-05-07 07:44:53">] 1.9.3p194 :003 > Book.first.destroy Book Load (0.3ms) SELECT "books".* FROM "books" LIMIT 1 (0.1ms) begin transaction Author Load (0.2ms) SELECT "authors".* FROM "authors" WHERE "authors"."book_id" = 1 SQL (4.6ms) DELETE FROM "authors" WHERE "authors"."id" = ? [["id", 1]] SQL (0.2ms) DELETE FROM "books" WHERE "books"."id" = ? [["id", 1]] (3.8ms) commit transaction => #<Book id: 1, title: "Homo faber", created_at: "2012-05-07 07:44:53", updated_at: "2012-05-07 07:44:53"> 1.9.3p194 :004 > Author.exists?(1) Author Exists (0.2ms) SELECT 1 FROM "authors" WHERE "authors"."id" = 1 LIMIT 1 => false 1.9.3p194 :005 > exit MacBook:bookshelf xyz$
:has_many .. :through
authors
-Tabelle eintragen. Im Sinne einer guten
Normalisierung ist das nicht. Es wäre schöner, jeden Autor nur
einmal in der authors-Tabelle einzutragen und die Verknüpfung mit
den Büchern über eine Zwischentabelle zu regeln. Genau dafür gibt
es has_many …
, :through =>
…
.MacBook:~ xyz$ rails new bookshelf2 [...] MacBook:~ xyz$ cd bookshelf2 MacBook:bookshelf2 xyz$
Book
und Author
an.
Beim Author lassen wir aber die book_id
weg:MacBook:bookshelf2 xyz$ rails generate model book title invoke active_record create db/migrate/20120507085137_create_books.rb create app/models/book.rb invoke test_unit create test/unit/book_test.rb create test/fixtures/books.yml MacBook:bookshelf2 xyz$ rails generate model author first_name last_name invoke active_record create db/migrate/20120507085158_create_authors.rb create app/models/author.rb invoke test_unit create test/unit/author_test.rb create test/fixtures/authors.yml MacBook:bookshelf2 xyz$
Authorship
, das ein Feld
book_id
und ein Feld
author_id
enthält:MacBook:bookshelf2 xyz$ rails generate model authorship book_id:integer author_id:integer
invoke active_record
create db/migrate/20120507085358_create_authorships.rb
create app/models/authorship.rb
invoke test_unit
create test/unit/authorship_test.rb
create test/fixtures/authorships.yml
MacBook:bookshelf2 xyz$
MacBook:bookshelf2 xyz$ rake db:migrate
[...]
MacBook:bookshelf2 xyz$
Authorship
verknüpfen wir per
belongs_to
mit
Book
und Author
in
der Datei
app/models/authorship.rb:
class Authorship < ActiveRecord::Base
attr_accessible :author_id, :book_id
belongs_to :author
belongs_to :book
end
app/models/book.rb
verknüpfen
wir jetzt mit has_many die books
mit den
authorships
und danach mit has_many
:authors, :through => :authorships
die
authors
über die authoships
mit den books
:class Book < ActiveRecord::Base
attr_accessible :title
has_many :authorships
has_many :authors, :through => :authorships
end
app/models/author.rb
das gleiche
Konstrukt ein:class Author < ActiveRecord::Base
attr_accessible :first_name, :last_name
has_many :authorships
has_many :books, :through => :authorships
end
MacBook:bookshelf2 xyz$ rails console Loading development environment (Rails 3.2.3) 1.9.3p194 :001 > book = Book.create(:title => 'Homo faber') (0.1ms) begin transaction SQL (4.8ms) INSERT INTO "books" ("created_at", "title", "updated_at") VALUES (?, ?, ?) [["created_at", Mon, 07 May 2012 09:34:34 UTC +00:00], ["title", "Homo faber"], ["updated_at", Mon, 07 May 2012 09:34:34 UTC +00:00]] (3.8ms) commit transaction => #<Book id: 1, title: "Homo faber", created_at: "2012-05-07 09:34:34", updated_at: "2012-05-07 09:34:34"> 1.9.3p194 :002 >
1.9.3p194 :002 > book.authors
Author Load (0.2ms) SELECT "authors".* FROM "authors" INNER JOIN "authorships" ON "authors"."id" = "authorships"."author_id" WHERE "authorships"."book_id" = 1
=> []
1.9.3p194 :003 >
build
oder
create
kann ich einen Autor für dieses
Buch anlegen:1.9.3p194 :003 > book.authors.create(:first_name => 'Max', :last_name => 'Frisch')
(0.1ms) begin transaction
SQL (0.7ms) INSERT INTO "authors" ("created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?) [["created_at", Mon, 07 May 2012 09:36:33 UTC +00:00], ["first_name", "Max"], ["last_name", "Frisch"], ["updated_at", Mon, 07 May 2012 09:36:33 UTC +00:00]]
SQL (0.4ms) INSERT INTO "authorships" ("author_id", "book_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["author_id", 1], ["book_id", 1], ["created_at", Mon, 07 May 2012 09:36:33 UTC +00:00], ["updated_at", Mon, 07 May 2012 09:36:33 UTC +00:00]]
(2.6ms) commit transaction
=> #<Author id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-05-07 09:36:33", updated_at: "2012-05-07 09:36:33">
1.9.3p194 :004 >
1.9.3p194 :004 > Book.all Book Load (0.3ms) SELECT "books".* FROM "books" => [#<Book id: 1, title: "Homo faber", created_at: "2012-05-07 09:34:34", updated_at: "2012-05-07 09:34:34">] 1.9.3p194 :005 > Author.all Author Load (0.3ms) SELECT "authors".* FROM "authors" => [#<Author id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-05-07 09:36:33", updated_at: "2012-05-07 09:36:33">] 1.9.3p194 :006 > Authorship.all Authorship Load (0.3ms) SELECT "authorships".* FROM "authorships" => [#<Authorship id: 1, book_id: 1, author_id: 1, created_at: "2012-05-07 09:36:33", updated_at: "2012-05-07 09:36:33">] 1.9.3p194 :007 >
authorships
mit den Verknüpfungen vom
Book
zum Author
gefüllt.Book.first.authors
alle Autoren des
ersten Buches anzeigen lassen:1.9.3p194 :007 > Book.first.authors
Book Load (0.2ms) SELECT "books".* FROM "books" LIMIT 1
Author Load (0.2ms) SELECT "authors".* FROM "authors" INNER JOIN "authorships" ON "authors"."id" = "authorships"."author_id" WHERE "authorships"."book_id" = 1
=> [#<Author id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-05-07 09:36:33", updated_at: "2012-05-07 09:36:33">]
1.9.3p194 :008 >
<<
verknüpfen. Wenn wir z. B. die
in diesem Kapitel benutzte db/seeds.rb
auf
die von uns jetzt erstellte has_many ... through-Verknüpfung
umstellen wollen, so sähe die Datei so aus:# ruby encoding: utf-8 Book.create(:title => 'Homo faber').authors << Author.find_or_create_by_first_name_and_last_name('Max', 'Frisch') Book.create(:title => 'Der Besuch der alten Dame').authors << Author.find_or_create_by_first_name_and_last_name('Friedrich', 'Dürrenmatt') Book.create(:title => 'Julius Shulman: The Last Decade').authors << [ Author.find_or_create_by_first_name_and_last_name('Thomas', 'Schirmbock'), Author.find_or_create_by_first_name_and_last_name('Julius', 'Shulman'), Author.find_or_create_by_first_name_and_last_name('Jürgen', 'Nogai') ] Book.create(:title => 'Julius Shulman: Palm Springs').authors << [ Author.find_or_create_by_first_name_and_last_name('Michael', 'Stern'), Author.find_or_create_by_first_name_and_last_name('Alan', 'Hess') ] Book.create(:title => 'Photographing Architecture and Interiors').authors << [ Author.find_or_create_by_first_name_and_last_name('Julius', 'Shulman'), Author.find_or_create_by_first_name_and_last_name('Richard', 'Neutra') ] Book.create(:title => 'Der Zauberberg').authors << Author.find_or_create_by_first_name_and_last_name('Thomas', 'Mann') Book.create(:title => 'In einer Familie').authors << Author.find_or_create_by_first_name_and_last_name('Heinrich', 'Mann')
find_or_create
können wir hier
sicherstellen, dass ein Autor nicht zweimal angelegt wird.[25] Falls Sie sich für den theoretischen Hintergrund zu
Joins interessieren,
finden Sie weitere Informationen unter: http://de.wikipedia.org/wiki/SQL#Abfrage_mit_verkn.C3.BCpften_Tabellen
,
http://en.wikipedia.org/wiki/Join_(SQL)
,
http://de.wikipedia.org/wiki/Relationale_Algebra#Join.