ハッカーを目指す白Tのブログ

夏は白のTシャツを着ています。ラジオが好きです。(ラジオネーム: 隠れキリジダン)

Railsのリレーションのオプションでできること、まとめてみた。

Railsのモデル間のリレーションのメソッドには、多くのオプションがある。
今日は、それらを使ってできることをまとめていきたい。

リレーションのオプションでできること一覧


1. ポリモーフィック関連の定義
2. ポリモーフィック関連が定義されたモデルと他のモデルの関連の定義
3. 親モデルに子モデルが依存するようにする
4. 親モデルのプライマリーキーを設定する
5. 外部キーのカラム名を設定する
6. 紐づくモデルのインスタンスを取得するメソッドの名前を設定する
7. 紐づくモデルのobjectに対してvalidationを実行しない
8. 2階層以上離れているモデルのインスタンスを取得する
9. 親モデルのオブジェクトがsaveされたとしても、loadされている子モデルのオブジェクトの変更はsaveされないようにする
10. ひとつのモデル(A)から他のモデルを通して、もう一度モデル(A)のインスタンスを取得したとき、それが、もとのモデル(A)のインスタンスと等しいと設定する

1.ポリモーフィック関連の定義

これには、:as , :polymorphicオプションを使う。
ポリモーフィック関連とは、複数の上位モデルの下にひとつの下位モデルが紐づき、かつ、各上位モデルと下位モデルの関係が同じような場合のとき、それらの関係を抽象化して名前付けすることで、下位モデルから各上位モデルに共通のメソッドでアクセス出来るようにモデルを関連付けることである。
これを行うことで、2つの利点がある。それは、
1.下位モデルにおいて、上位モデルが増える度にbelongs_toを記述する必要がなくなる
2.下位モデルから複数の上位モデルに一括でアクセスできるようになる
である。
例えば、UserモデルとCompanyモデルがあり、UserとCompany両方の写真が保存されているImageモデルがあるとすると、ポリモーフィック関連は以下のように記述することができる。

#上位モデルでは、 :as オプションを記述する(imageableの部分は、その関係を分かりやすく表す名前をつけると良い)

#User.rb
has_many  :images, :as => imageable

#Company.rb
has_many :images, :as => imageable

#下位モデルでは、:polymorphic オプションを記述する

#Image.rb
belongs_to : imageable, :polymorphic =>true

ポリモーフィック関係における外部キーは、idとtypeの2種類設定される。Idには、上位モデルのidが入り、typeには、上位モデル名が入る。したがって、images table のカラムには、imageable_idとimageable_typeが追加される。
上記のリレーションにより、

Image.where(id:1).first.imageable

で、id = 1のImageモデルのインスタンスに紐づく、複数の上位モデルのインスタンスを一括で取得できる。
また、上位モデルから下位モデルのインスタンスを取得するには、

User.where(id: 1).first.imageable
Company.where(id: 1).first.imageable

で、idが1のUser、Companyモデルに紐づくImageモデルのインスタンスを取得できる。

ポリモーフィック関連について、詳しくたい場合は、こちら:2010-06-19 - 篳篥日記

2.ポリモーフィック関連が定義されたモデルと他のモデルの関連の定義

これには、:sourceや:source_typeオプションを使う。
1.の例で用いた、Company, User, Imageモデルに加え、imagesを複数持つPhotographerモデルが存在する場合を例にとり説明する。

#User.rb
has_many  :images, :as => imageable

#Company.rb
has_many :images, :as => imageable

#Image.rb
belongs_to : imageable, :polymorphic =>true

#Photographer.rb
has_many :images 

Photographerモデルに紐づく、Company, Userモデルのインスタンスを取得したい場合
、リレーションは以下のように記述する。

#Photographer.rb
has_many :images
has_many :companies, through: :images, :source :imageable, :source_type 'Comapany'
has_many :users, through: :images, :source :imageable, :source_type 'User' 
#sourceで、ポリモーフィック関連で定義した関係の名前(:as オプションで指定したもの)を指定する
#source_typeで、取得したいポリモーフィック関連の上位モデルの名前を指定する

これによって、

Photographer.where(id: 1).first.companies

で、idが1のPhotographerモデルに紐づくCompanyモデルのインスタンスを取得することができる。
捕捉として、:sourceオプションのみを使用する場合について説明する。
:source オプションのみを使用する場合は、以下の2例である。
1.他のモデルからポリモーフィック関連を定義した上位モデルに一括でのみアクセスする場合
2.他のモデルからポリモーフィック関連を定義した上位モデルがひとつしかない場合

3.親モデルに子モデルが依存するようにする

これには、dependentオプションを使う。
:dependentオプションによる、親モデルと子モデルの依存関係は5種類ある。
1.親objectが削除されたとき、それに紐づく子objectが同時にdestroyされる 
 => dependent: :destroy
2.親objectが削除されたとき、それに紐づく子objectがdatabaseから直接削除される。よって、コールバックは発生しない。
 => dependent: :delete_all
3.親objectが削除されたとき、それに紐づく子objectの外部キーをnullにする
 => dependent: :nullify
4. 親objectが削除されたとき、それに紐づく子objectがある場合、例外を発生させる
 =>dependent: :restrict_with_exception
5.親objectが削除されたとき、それに紐づく子objectがある場合、エラーを発生させる
 => dependent: :restrict_with_error

4.プライマリーキーを設定する

これには、:primary_keyオプションを使う。
カラム名を記述することで、プライマリーキーとなるカラムを設定できる。
例えば、Categoryモデルが複数のArticleモデルをもっている場合、

#Category.rb
has_many :articles, :primary_key :name

#Article.rb
belongs_to :category

とすることで、CategoryモデルとArticleモデルの関係におけるCategoryモデルのプライマリーキーは、nameカラムとなる。
したがって、Articleモデルのcategory_idには、Categoryモデルのnameが入ることになる。

5.外部キーのカラム名を設定する

これには、:foreign_keyオプションを使う。
カラム名を記述することで、外部キーのカラムを設定できる。
3.のプライマリーキーの設定と共に用い、以下のように記述すると、

#Category.rb
has_many :articles, :primary_key :name, :foreign_key :category_name

#Article.rb
belongs_to :category

Categoryモデルのプライマリーキーがname、外部キーがcategory_nameと設定することができる。よって、Articleモデルのcategory_nameにCategoryモデルのnameが入り、モデル間の関係わかりやすくすることができる。

6.紐づくモデルのインスタンスを取得するメソッドの名前を設定する

これには、class_nameオプションを使う。
ActiveRecordは、

belongs_to :model
#defaultでbelongs_toの第一引数に、そのモデルに紐づくモデル名を全て小文字で単数系で設定

has_many :models
#has_manyの第一引数に、そのモデルに紐づくモデル名を全て小文字で複数系で設定

以上のように設定すると、そのモデルに紐づくモデルのインスタンスを取得するメソッド名をそのモデル名で定義してくれる。このメソッド名を変更したい場合に、このオプションを用いる。具体的に、メソッド名を変更する場面として、Model名のaliasを設定する場面や、モデルの一部のデータに新しく名前をつけ意味づけする場面が挙げられる。
例えば、「1対多」の関係のCategoryとArticleモデルで、Categoryに紐づくArticleモデルのインスタンスを取得するメソッド名をfeedsにしたい場合、以下のように記述する。

#Category.rb
has_many :feeds, class_name:  'Article'
#第一引数でメソッド名、class_nameオプションで紐づくモデル名を記述する

#Article.rb
belongs_to :category  

これにより、

Category.where(id: 1).first.feeds

で、idが1のCategoryモデルのインスタンスに紐づくArticleモデルのインスタンスを取得できる。
また、上記の例で、belongs_toにclass_nameを指定した場合、

#Category.rb
has_many :articles

#Article.rb
belongs_to :type, class_name:  'Category'  
#第一引数でメソッド名、class_nameオプションで紐づくモデル名を記述する

と、設定することで、

Article.where(id: 1).first.type

で、idが1のArticleモデルのインスタンスに紐づくCategoryモデルのインスタンスを取得できる。
この際、articlesテーブルのtype_idカラムを外部キーとして、Categoryモデルのインスタンスを取得している。

7.紐づくモデルのオブジェクトに対してバリデーションを実行しない

これには、:validateオプションを使う。
:validateはdefaultでtrueになっているので、これをfalseにすると、モデルに紐づくモデルのオブジェクトに対してのバリデーションを実行しなくなる。

#Category.rb
has_many :feeds, validate: :false

#Article.rb
belongs_to :category  

8.2階層以上離れているモデルのインスタンスを取得する

これには、:throughオプションを使う。
Categoryモデルに紐づくArticleモデルにさらに紐づくCommentモデルがある場合、以下のように記述することで、Categoryモデルに紐づくCommentモデルのインスタンスを取得できる。

#Category.rb
has_many :articles
has_many :comments, through: :articles

#Article.rb
belongs_to :category  
has_many :comments

#Comment.rb
belongs_to :article

注意すべき点として、throughオプションに引き渡されるシンボルが単数か、複数かということがある。
これは、どちらの場合もあり、2階層以上離れているモデルまでの間で、すべての関係が「1対1」の場合が単数で、それ以外の場合が複数となる。

9. 親モデルのオブジェクトがsaveされたとしても、loadされている子モデルのオブジェクトの変更はsaveされないようにする

これには、:autosaveオプションを使う。
ActiveRecordでは、defaultで親モデルのオブジェクトがsaveされると、loadされている子モデルのオブジェクトの変更も保存される。
:autosaveの値をfalseにすることで、親モデルのオブジェクトがsaveされたとしても、loadされている子モデルのオブジェクトの変更がsaveされないようにすることができる。

has_many :models, autosave: false

10. ひとつのモデル(A)から他のモデルを通して、もう一度モデル(A)のインスタンスを取得したとき、それが、もとのモデル(A)のインスタンスと等しいと設定する

これには、inverse_ofオプションを使う。
「1対多」の関係にあるCategoryモデルとArticleモデルを例にとると、

#Category.rb
has_many :articles, inverse_of: :category
#inverse_ofの値は、この場合Categoryモデルのインスタンスはひとつであるから、単数形

#Article.rb
belongs_to :category, inverse_of: :articles 
#inverse_ofの値は、この場合Articleモデルのインスタンスは複数ある可能性があるから、複数形

と、することによって、本来inverse_ofオプションを指定しないと、

Category.where(id: 1) == Category.where(id: 1).first.articles.category
    #=> false(異なるインスタンスと認識される)

と、なるのに対し、

Category.where(id: 1) == Category.where(id: 1).first.articles.category
    #=> true(同一インスタンスと認識される)

と、することができる。
それでは、inverse_ofオプションを設定することには、一体どのような意味があるのだろう。それには、以下の二つのメリットがある。
1. 同一オブジェクトと認識されるので、メモリの使用量を減らすことができる
2. クエリー数を減らし、処理速度を上げることができる
また、inverse_ofオプションを設定する際に注意するべき点があり、それは、
1.:throughオプションと同時に設定できない
2.:as,:polymorphicオプションと同時に設定できない
である。

参照サイト:Active Record Associations — Ruby on Rails Guides

以上