ようやくリレーションについて。
belongs_to, has_one, has_manyの基本
今日読んだ範囲では特に新しいことはなかった。
has_and_belongs_to_many
has_and_belongs_to_many(habtm)は利用シーンがすごく限られていて、覚えていてもあんまり使えないんじゃないかなーという気がする。結合テーブルを作ったら、そこに情報をいろいろ付加したくなるし、リレーションの片方を検索してそこからもう片方をfindするような時は、結合テーブルに検索情報の列を付加してあげた方がクエリの発行回数が少なくなるし。同様にhas_many :hoge, :through => :hogehgoeもあんまり使いどころがない気がする。
この話題に関してはかなりもやもやしている最中なので、いずれもう少し掘り下げて書きたいと思います。
アソシエーションの拡張
アソシエーションの拡張っていうのは下記のように、has_many*1にブロックを渡してあげて、その中でメソッドを定義することで実現できます。
class User < ActiveRecord::Base has_many :readings has_many :articles, :through => :readings do def rated_at_or_above(rating) find :all, :conditions => ["rating >= ?", rating] end end end
通常はhas_manyを指定するだけで、下記のようなメソッドが複数定義されるようになるのだけど、それを上記のコードで拡張できるという訳です。
user.articles.build(:name => name)
アソシエーションの拡張を複数のモデルで使い回したいときは、モジュールにメソッドを記述して:extendパラメータで指定してあげるといい。
has_many :articles, :extend => RatingFinder
単一テーブル継承
親と子の関係がある複数のモデルを一つのテーブルで管理する方法。
まず、テーブルにtypeという名前の列を作ります。
create_table :people do |t| t.column :type, :string # 全てのモデルに共通の列 t.string :name t.string :email # Customerモデル専用列 t.decimal :balance # Employeeモデル専用列 t.integer :reports_to t.integer :dept end
その次にこんな感じでモデルを作ります
class Person < ActiveRecord::Base # ... end class Customer < Person # ... end class Employee < Person # ... end class Manager < Employee # ... end
こうすると、それぞれのモデルをDBに保存するときに、railsが自動でtype列を入力してくれます。そして下記のようにfindを使うと、railsが自動でtype列を参照して、type列に対応するクラスとして取得してくれます。
Manager.create(:name => "manager") manager = Person.find_by_name("manager") p manager.class #=> Manager
これは利用頻度が結構ありそうだし、モデルをスマートに管理できそうなので覚えておこうっと。
ポリモーフィックアソシエーション
単一テーブル継承は、各モデルで共通する列がある程度あれば使えるけど、共通する列が少ないときは使いにくい。そういうときにはポリモーフィックアソシエーションを使うといいらしい。(2008/7/14追記と修正を行いました)
例
「catalog_entryにはarticle, sound, imageのいずれか一つのコンテンツが含まれる」といった場合に、それらをまとめるresourceという仮の抽象モデル(のようなもの)を定義してポリモーフィックアソシエーションを設定してあげるとスマートに書ける。具体的には下記のようにする。
まず、テーブルに抽象モデルの外部キーとタイプを設定する。
create_table :catalog_entries do |t| t.string :name t.datetime :acquired_at t.integer resource_id t.string resource_type end
次に、各々のモデルのテーブル定義をする。
create_table :articles do |t| t.column :content, :text end create_table :sounds do |t| t.column :content, :binary end create_table :images do |t| t.column :content, :binary end
そして、各々のモデルには下記のように書いてあげる。
class CatalogEntry < ActiveRecord:Base belongs_to :resource, :polymorphic => true end class Article < ActiveRecord:Base has_one :catalog_entry, :as => :resource end class Sound < ActiveRecord:Base has_one :catalog_entry, :as => :resource end class Image < ActiveRecord:Base has_one :catalog_entry, :as => :resource end
このようにすると、catalogentry.resouceにArticle, Sound, Imageを設定したときに、railsが自動的にresource_idとresource_typeを入れてくれる。取得時にもrailsがresource_typeを見て、適切なモデルオブジェクトとして取得してくれる。
a = Article.new(:content => "hoge") c = CatalogEntry.new(:name = "entry", :acquired_at => Time.now) c.resource = a c.save! p c.resource_type #=> "Article"
参考書籍
RailsによるアジャイルWebアプリケーション開発 第2版
- 作者: Dave Thomas,David Heinemeier Hansson,Leon Breedt,Mike Clark,Andreas Schwarz,James Duncan Davidson,Justin Gehtland,前田修吾
- 出版社/メーカー: オーム社
- 発売日: 2007/10/26
- メディア: 大型本
- 購入: 18人 クリック: 300回
- この商品を含むブログ (138件) を見る
*1:has_oneやbelongs_toでもできるみたい