アソシエーション

  • 要は、テーブル間のリレーションを操作する仕組みね
    • アソシエーションを使えば、こんなデータ取得が、
      @book = Book.find(1)
      @reviews = Review.where(:book_id => @book.id)
      
    • こんな風に書ける
      @book = Book.find(1)
      @reviews = @book.reviews
      
  • で、このアソシエーションで特に大事なのは、命名規則!
    • 外部キー列は、「参照先モデル_id」の形式(book_idみたいな)
    • 中間テーブルは参照先テーブルを「_」で、辞書順に連結(authors_booksみたいな)
  • belongs_toは、単純な関連ね
    • こんな感じで、review.rbに書く
      class Review < ActiveRecord::Base
       belongs_to :book
      end
      
      • と、言いながら、実はScaffoldで「references」を指定していたので、自動でbelongs_toが設定されてたw
        rails g scaffold review book:references user:references body:text
        
    • そうすると、コントローラ側(record_controller.rb)にこう書いて
       def belongs
         @review = Review.find(3)
       end
      
    • View側(record/belongs.html.erb)にこう書けば
      <h2>「<%= @review.book.title %>」のレビュー </h2>
      <hr/>
      <p><%= @review.body %>(<%= @review.updated_at %>)</p>
      
    • こんなSQL発行されて、画面に本のタイトルとそのレビューの内容が出る
      SELECT "reviews".* FROM "reviews" WHERE "reviews"."id" = ? LIMIT 1 [["id", 3]]
      SELECT "books".* FROM "books" WHERE "books"."id" = 2 LIMIT 1
      
  • has_manyは、1:nの関連ね
    • こんな感じで、今度はbook.rbに書く
      class Book < ActiveRecord::Base
       has_many :reviews
      
    • で、コントローラ側(record_controller.rb)にこう書いて
       def has_many
         @book = Book.where(:isbn => '978-4-7980-2812-5' ).first
       end
      
    • View側(record/has_many.html.erb)にこう書けば
      <h2>「<%= @book.title %>」のレビュー </h2>
      <hr/>
      <ul>
      <% @book.reviews.each do |review| %>
      <li><%= review.body %>(<%= review.updated_at %>)</li>
      <% end %>
      </ul>
      
    • こんなSQL発行されて、画面に本のタイトルとそのレビューの 一覧 が出る
      SELECT "books".* FROM "books" WHERE "books"."isbn" = '978-4-7980-2812-5' LIMIT 1
      SELECT "reviews".* FROM "reviews" WHERE "reviews"."book_id" = 1
      
  • has_manyとbelongs_toは、1:nの関係を表現するのに、対になるものなんだねー。
  • has_oneは、1:1ね
    • サンプルだとusersとauthorsで、あるユーザが著者に割り当てられるイメージね
      • 一人のユーザが複数の著者にならないってことかー
      • なんで、より正確に言うなら、ユーザ:著者が、1:0..1って感じかな?
    • で、モデルuser側に、has_oneをこんな感じで書いて、
      class User < ActiveRecord::Base
       has_one :author
      
    • かつ、モデルauthor側に、belongs_toをこんな感じで書いて、
      class Author < ActiveRecord::Base
       belongs_to :user
      
      • おお、こっちも既にScaffoldで定義したから、既に書かれてた!
    • で、コントローラ側(record_controller.rb)にこう書いて、
       def has_one
         @user = User.where(:username => 'isatou').first
       end
      
    • View側(record/has_one.html.erb)にこう書けば、
      <ul>
      <li>ユーザ名:<%= @user.username %></li>
      <li>メールアドレス:<%= @user.email %></li>
      <% unless @user.author.nil? %>
      <li>著者:<%= @user.author.name %></li>
      <li>誕生日:<%= @user.author.birth %></li>
      <% end %>
      </ul>
      
    • こんなSQL発行されて、ユーザと著者の情報が出る
      SELECT "users".* FROM "users" WHERE "users"."username" = 'isatou' LIMIT 1
      SELECT "authors".* FROM "authors" WHERE "authors"."user_id" = 2 LIMIT 1
      
  • has_oneとbelongs_toは、どちらも1:1関係になるから、どっちを使うか悩むね。
    • まあ、主になるテーブル側にbelongs_to(従属する)って考えて、使うのが良さそうって、事か。そらそうか。
    • ここだと、ユーザに著者が従属するって、ことね。
  • has_and_belongs_to_manyは、中間テーブルを使う多対多(m:n)の関係かー
    • ここでようやく中間テーブルが出てくるのね。
    • サンプルだと、本と著者(booksとauthors)の関係で、中間テーブル名は自動的にauthors_booksになる、と。
    • 使い方の概要はこんな感じ
      • 中間テーブルのモデルとしてauthors_booksを作る
      • 中間テーブルのモデルの主キー(id)を無効化して、登録/更新日付を削除→3.1だといらない?
      • あとは、使う側の各モデルで、has_and_belongs_to_manyを定義
    • まずは、中間テーブル用のモデルを作成
      rails g model authors_books author:references book:references
      
    • 次に、マイグレーションファイル(****_create_authors_books.rb)を修正して、キー無効化と更新日付削除
      • あり?既に修正済み?これは…なんでだろう?Rails3.1だからか?
      • 念のため、マイグレーションファイルはこんな感じね。
        class CreateAuthorsBooks < ActiveRecord::Migration
         def change
           create_table :authors_books , :id => false do |t|
             t.references :author
             t.references :book
           end
           add_index :authors_books, :author_id
           add_index :authors_books, :book_id
         end
        end
        
    • で、両方のモデルで、has_and_belongs_to_manyを定義
      • book側
        class Book < ActiveRecord::Base
         has_and_belongs_to_many :authors
        
      • author側
        class Author < ActiveRecord::Base
         has_and_belongs_to_many :books
        
    • で、コントローラ側(record_controller.rb)にこう書いて、
       def has_and_belongs
         @book = Book.where(:isbn => '978-4-7741-4466-5').first
       end
      
    • View側(record/has_and_belongs.html.erb)にこう書けば、
      <h2>「<%= @book.title %>」の著者情報 </h2>
      <hr/>
      <ul>
      <% @book.authors.each do |author| %>
      <li><%= author.name %>(<%= author.birth %> | <%= author.address %> )</li>
      <% end %>
      </ul>
      
    • こんなSQL発行されて、本とその著者の一覧が出る、と。
      SELECT "books".* FROM "books" WHERE "books"."isbn" = '978-4-7741-4466-5' LIMIT 1
      SELECT "authors".* FROM "authors" 
       INNER JOIN "authors_books" 
          ON "authors"."id" = "authors_books"."author_id" 
       WHERE "authors_books"."book_id" = 2
      
  • has_many:throughも、多対多(m:n)の関係なんだけど、中間テーブルで関連付け以上の情報を持てる
    • 要するに、中間テーブルを通り抜けて他のテーブルへアクセスするってこと
      • サンプルの場合、booksとusersは、reviewsを挟んでのm:nの関係って感じ
        books <---1:0..n---> reviews <---0..n:1---> users
              <-------------0..m:0..n------------->
        
      • 使い方は、使う側の各モデルで、 必要なところで has_many:throughを定義
      • 今回だと、books→usersと、users→booksのとこね。
    • なので、まずは各モデルで、こんな風に定義
      • book側
        class Book < ActiveRecord::Base
         has_many :reviews
         has_many :users, :through => :reviews
        
      • reviews側
        class Review < ActiveRecord::Base
         belongs_to :book
         belongs_to :user
        
      • これも、既に書かれてた。Scaffold、もちっと頑張ってくれると、ほとんどこの辺、書かなくて済みそうだな…。
      • users側
        class User < ActiveRecord::Base
         has_many :reviews
         has_many :books, :through => :reviews
        
    • で、コントローラ側(record_controller.rb)にこう書いて、
       def has_many_through
         @user = User.where(:username => 'isatou').first
       end
      
    • View側(record/has_and_belongs.html.erb)にこう書けば、
      <ul>
      <li>ユーザ名:<%= @user.username %></li>
      <li>メールアドレス:<%= @user.email %></li>
      <% unless @user.books.empty? %>
      <li>レビューした本:
       <ul>
         <% @user.books.each do |book| %>
           <li><%= book.title %></li>
         <% end %>
       </ul>
      </li>
      <% end %>
      </ul>
      
    • こんなSQL発行されて、ユーザとそのレビューした本の一覧が出る、と。
      SELECT "users".* FROM "users" WHERE "users"."username" = 'isatou' LIMIT 1
      SELECT COUNT(*) FROM "books" INNER JOIN "reviews" ON "books"."id" = "reviews"."book_id" WHERE "reviews"."user_id" = 2
      SELECT "books".* FROM "books" INNER JOIN "reviews" ON "books"."id" = "reviews"."book_id" WHERE "reviews"."user_id" = 2
      
  • アソシエーションでは、いろんなメソッドが追加されるし、オプションもいろいろ
    • 上記の例だと「@book.reviews」とか「@user.books.empty?」とかが、自動で追加されてるって感じ
    • 面白そうなメソッドは、こんな感じかな?
      @book.reviews<<@review モデルを追加
      @book.reviews.build(:body => '本') モデルを生成(保存しない)
      @book.reviews.create(:body => '本') モデルを生成(保存する)
    • 面白そうなオプションだと、こんな感じ?
      :autosave 親モデルの保存/削除を行うか
      :dependent モデル削除時に、関連先のモデルも削除するか
      :touch モデル保存時に、create_at/update_atを更新するか
      • @todo:この辺、もちょっと後で追記するか…
    • この辺はRailsの説明でも、結構詳しそう。まあ、英語だけどニュアンスはつかめるかなー。
    • 本だと、P259辺りね。
  • アソシエーションは、ActiveRecordの機能だけど、一応joinでも似たような事は出来る。まあ、そりゃそうか。
    • ただ、いくらSELECTをJOINで誤魔化しても、バリデートとか考えるとちゃんとActiveRecord使った方がいい気がするよなー。
    • パフォーマンス対応では、使うかも?この辺はだんだん、微妙な世界だなー。

-
最終更新:2011年12月22日 07:25