『Ruby on Rails5 アプリケーションプログラミング』学習日誌 5

前書き

今日も第5章モデルについて学習していきたいと思います。 ソースコードにもコメントで気づいたことについて注釈入れてます。あと、ここに書いてあることは単なる学習記録なので事実とは関係がない場合があります。ご了承ください。

ページ範囲

p.268-290

学習ポイント

アソシエーションについて

アソシエーションとはテーブル間のリレーションシップをモデル上の関係として操作できるようにする仕組みのこと。主キーはそのテーブル上の各データに一意についている番号のことで通常id。そして別のテーブルでその各データをそのテーブルのデータに関連づけたい時には外部キーと呼ばれるものを利用する。外部キーはrailsでは「参照先のモデル名(頭は小文字)_id」の形式で命名するという規則がある。外部キーは言ってみれば別のテーブルのデータが入っている変数のようなものだと理解している。アソシエーションを利用することで

@book = Book.find(1)
@reviews = @book.reviews

のような書き方で特定のデータに関連する別のテーブルのデータを取得することができる。
これの二行目は

@reviews = Review.where(book_id: @book.id)

に相当する。2番目の方から考える。Reviewモデルから.where メソッド(ActiveRecord::Relationのメソッド?)で第一引数にそのモデルの属性(book_id:)をとり第二引数にインスタンスのid属性を指定している。それらが==になるのを条件に(where)個別のレコードを@reviewsというテンプレート変数に(配列で?)格納している。
対して一個目の書き方では、@bookインスタンスからreviewモデルのメソッドが使えるようになっており、そのインスタンスに従属する値をにアクセスできるようになっている。これも外部キーの命名規則を定めていて、なおかつアソシエーションを定義しているのでこのようなことが可能になる。テーブルのリレーションで重要な中間テーブルの命名は参照先のテーブル名をアルファベット順に_で繋げたものとするという規則がある。

belongs_to アソシエーション

belongs_toメソッドの引数にはモデル名(頭は小文字)を渡す。これが参照先のテーブルを取得するためのアクセサメソッドにもなる。アクセサメソッドとは内部的に持っている値(変数)を外部から取得するためのメソッドのこと。belongs_toメソッドで参照先のモデルを定義することで参照元のモデルから参照先のモデルのメソッドを使うことができるようになる。

has_many アソシエーション

belongs_toメソッドのみでは参照元→参照先という一方通行の関係しか定義できなかったがhas_manyメソッドを使うことで1対多の双方向の関係を定義することができる。has_manyメソッドでは参照先である引数のモデルを複数形で記述する。has_manyを定義したモデルから参照先のモデルをメソッドとして呼び出すときも複数形にする。戻り値も複数あることが前提となるため、配列で返ってくる。

has_one アソシエーション

has_oneメソッドでは1対1の関係を表現できる。主たるモデルにhas_oneを定義し、従たるモデルにbelongs_toを定義する。引数は単数形とする。1対1の関係では主従関係がわかりにくいがどちらかは0or1になる場合があるのでそちらを従にする。

has_and_belongs_to_many アソシエーション

多対多の関係を表現することができる。リレーショナルデータベースでは多対多の関係を表現する方法が無いのでそれぞれのモデル名を複数形で_で繋げた名前の中間テーブルを用意する。中間テーブルにはそれぞれの外部キーだけ用意して置くことで、DB上の全部の関係を1対多までにとどめておくことができる(主キーもなし)。なお、参照元のテーブルには外部キーは必要ない。中間テーブルの名前を参照先のテーブル名を_で繋いだものにすることによってそれらの中間テーブルであることを表現することができるため。また中間テーブルはDBでの表現方法として用意したものに過ぎないので、モデルを作る必要がない。それぞれの元のモデルにおいてはhas_and_belongs_to_manyを定義することで関係を表現する。多対多の関係には主従関係がないので、それぞれのモデルに記述する。引数にとるモデルは複数形になる。これだけでモデル上では多対多の関係を作ることができる。

has_many through アソシエーション

has_and_belongs_to_manyメソッドでは中間テーブル以上の意味をもつ中間テーブルを扱うことができない。(中間テーブルに命名規則が必要になるため。)例えばbooks, users, reviewsのテーブルがあったとして、books↔︎reviewsは1対多、users↔︎reviewsも1対多、books↔︎usersは多対多の関係になる。このような関係に置いてreviewsは自然とbooks↔︎users間の中間テーブルの役割を果たしているがそれ以上にreviewを管理するテーブルとしての意味が強い。reviewsテーブルもモデルとしてアクセスできる必要があるため名前を変えることができない。そうした場合に利用できるのがhas_manyメソッドの第二引数にthroughをとる方法。

class User < ApplicationRecord
  has_many :reviews
  has_many :books, throuth: :reviews
end

のようにhas_many :reviewsでreviewとの関係を定義した上でhas_many :books, throuth: :reviewsのようにreviewsを中間テーブルに見立てたbooksとの多対多の関係を定義する。このようにすることによってuserモデル↔︎bookモデル間のメソッドの呼び出しを直接行うことができる。

アソシエーションで追加されるメソッドについて

上記のメソッドでアソシエーションということはモデル新しいメソッドを追加することを意味する。例えばbelongs_toで宣言したモデルからアクセサメソッドとして参照元のモデルを呼び出すことができるのもその一つ。他にもempty?やbuildというメソッドもアソシエーションから生まれる。@book.reviews.buildのように使う。このbuildは新しいモデルを生成するメソッドでこの場合は新しいreviewを一つ作ることになる(保存はされない)。@bookに対して、reviewが一つ増えることになるので1対多か多対多の関係を前提としている。どの関係からどんなメソッドが生まれるのかはそれぞれ違う。これらのメソッドは@book.buildのようには使えない(bookモデルが新しく作られたりはしない)。アソシエーションを定義した上で@book.reviewsを前提として初めて使用することができる。たぶん@book.reviewsとすることでreviewモデルのメソッド + buildなどの新しいメソッドを取得する感じになるのだと思う。(ただ、pryでempty?メソッドを探そうとしたけど見つけられなかった。探し方が悪いんだと思うけど。)いずれにせよ、buildやempty?などのメソッドはアソシエーションという関係性のなかで生まれるメソッドということができるようだ。ちょっと注意が必要そう。

ポリモーフィック関連

一つの子モデルが複数の親モデルをもつ関係性のこと。このような場合は子モデルのテーブルに〇〇_typeと〇〇_idという二つのカラムを用意する。これによって親モデルと外部キーを分けて管理することができる。モデル側の定義としては親モデルでasオプション付きのhas_manyメソッドを宣言し、子モデルでpolymorphicオプション付きのbelongs_toメソッドを宣言することでポリモーフィック関連を定義することができる。

アソシエーションで利用できるオプション

上記のポリモーフィック関連以外にもアソシエーションで利用できるオプションはたくさんある。これらをうまく使うことによってモデルによるデータの扱いがすごく簡単になるようだ。本では表でまとめられているので実際の開発で使えそうなのがあったら積極的に使っていきたい。

よく使うのによく忘れるコマンド(メモ)

PRAGMA TABLE_INFO(TABLE_NAME);

sqliteカラム名を調べるのに使う

rails db:fixtures:load

テスト用データをデータベースに入れるのに使う。
scaffoldで生成されたテストデータには主キーの指定がない。そのままloadするとなんか変な数字が入るので注意。

今日の感想

ついにアソシエーションで追加されるメソッドについて学習することができて嬉しい。前によくわかってないのに使おうとしてハマったことがあったので。モデルの関係性のなかで新しいメソッドが使えるようになるという考え方が面白いと思う。それ以外でもモデルで不思議だったところが前に比べてかなり知ることができて嬉しい一日だった。