Railsのモデルのコールバックやメソッド、クラスについて
はじめに
本記事では「パーフェクト Ruby on Rails」で学んだ内容と別途気になって調べた内容や知識も含めアウトプットしています。
書籍に記載されている内容を順序立ててまとめるというよりは、整理しておきたい周辺の知識と深ぼった内容を雑多に書いています。
ActiveRecord::Relation
概要 - ActiveRecord::Relation
ActiveRecordのクエリインターフェースを表すクラスを指します。
以下の表がクエリインターフェイスと呼ばれるメソッドの例になります。メソッドは、ActiveRecord::Relationインスタンスのメソッドとしてチェインすることができ、最終的にSQLクエリの表現を作成することができます。
ActiveRecordメソッド | 対応するSQL | 解説 |
---|---|---|
all | SELECT * FROM table_name | モデルに関連するすべてのレコードを取得します。 |
select | SELECT column_name FROM table_name | 指定された列を選択してレコードを取得します。 |
where | SELECT * FROM table_name WHERE condition | 指定された条件に基づいてレコードをフィルタリングします。 |
order | SELECT * FROM table_name ORDER BY column_name ASC/DESC | 指定された列を使用してレコードをソートします。 |
limit | SELECT * FROM table_name LIMIT number | 指定された数のレコードを取得します。 |
offset | SELECT * FROM table_name OFFSET number | 指定された位置からレコードを取得します。 |
joins | SELECT * FROM table_name INNER/OUTER JOIN table_name2 ON condition | 指定されたテーブルを結合してレコードを取得します。 |
includes | SELECT * FROM table_name LEFT OUTER JOIN table_name2 ON condition | 指定された関連付けされたテーブルのレコードをプリロードして取得します。 |
group | SELECT column_name FROM table_name GROUP BY column_name | 指定された列を使用してレコードをグループ化します。 |
having | SELECT column_name FROM table_name HAVING condition | 指定された条件に基づいてグループ化されたレコードをフィルタリングします。 |
動作について - ActiveRecord::Relation
ActiveRecord::Relationは、クエリの構築とActiveRecordモデルに対してクエリを実行する動作を分離することにより、クエリをより柔軟に構築できるようにします。
具体的な動作は以下になります。
- ActiveRecordに対してクエリインターフェイスが呼ばれるとActiveRecord::Relationのインスタンスが生成される
# 実行 pry(main)> User.all.class # 結果 => User::ActiveRecord_Relation
- ActiveRecord::Relationに対して繰り返しクエリインターフェイスを呼び出すことが出来る(メソッドチェーン)
- 繰り返し呼び出したクエリインターフェイスはActiveRecord::Relationに蓄積されてどんなSQLを発行するかの情報が更新され、蓄積された情報をもとにSQLが発行され、データを取得
# 実行 (メソッドチェーンする例) pry(main)> User.all.order(name: :desc) # 結果 (チェーンした場合、実行時にそれらのSQLが反映される) User Load (7.4ms) SELECT `users`.* FROM `users` ORDER BY `users`.`name` DESC
上記は内部的にto_sqlメソッドが実行されて、SQL文が作成され、loadメソッドが呼び出されることで生成されたSQL文がデータベースに送信されてレコードが取得されます。
上記の例でもわかる通り、取得したレコードはActiveRecord::Relationクラスのオブジェクトとして返されます。
Scope
概要 - scope
よく利用する検索条件(クエリ)に名前をつけてひとまとめにできる機能です。
コードの重複を減らし、可読性を高めることができます。
使用例 - scope
書籍のコード例です。
モデルに以下のように記述することで、Book.costlyを呼び出すことが出来ます。
class Book < ApplicationRecord scope :costly, -> { where("price >?", 3000) }
注意点: nilとなる場合に正しくnilが返されない - scope
scopeとクラスメソッドは似た挙動の定義になりますが、nilが返ってしまうロジックのケースで挙動が異なります。
クラスメソッドで定義した場合は想定通りnilが帰りますが、Scopeでnilが返される場合は、該当Scopeの検索条件を除外したクエリを発行して、必ずActiveRecord::Relationを返すという動作になります。
意図しない動作になるため、nilを返す必要がある場合などはクラスメソッドとして定義することがおすすめされていました。
多対多の関係
Railsにおける多対多の関係の例です。
1人のユーザーは複数の役割を持つことができ、1つの役割は複数のユーザーに割り当てることが出来る仕様にする場合は多対多の関係になるため、中間テーブルを使用します。
class User < ApplicationRecord has_many :user_roles has_many :roles, through: :user_roles end class UserRole < ApplicationRecord belongs_to :user belongs_to :role end class Role < ApplicationRecord has_many :user_roles has_many :users, through: :user_roles end
このコードでは、UserとRoleの間にUserRoleという中間テーブルがあります。
UserモデルはUserRoleとの1対多の関係を持ち、Roleモデルとは中間テーブルを介して多対多の関係を持ちます。
RoleモデルはUserRoleとの1対多の関係を持ち、Userモデルとは中間テーブルを介して多対多の関係を持ちます。
UserRoleモデルは、中間テーブルであるため、UserとRoleの両方に対してbelongs_toの関連付けを持ちます。
throughオプションによって使えるようになるメソッド
class Role < ApplicationRecord has_many :user_roles has_many :users, through: :user_roles end
Roleを例にすると使えるようになるメソッドの一般的な例です。
メソッド | 説明 | ユースケース |
---|---|---|
role.users | その役割に関連する全てのユーザーを返します。 | ある役割に関連するユーザーを取得する場合に使用します。 |
role.users << user | 指定されたユーザーを役割に関連付けます。 | 新しいユーザーを役割に追加する場合に使用します。 |
role.users.delete(user) | 役割から指定されたユーザーを削除します。 | 役割からユーザーを削除する場合に使用します。 |
role.user_ids | 役割に関連する全てのユーザーのIDを配列で返します。 | 役割に関連するユーザーのIDを取得する場合に使用します。 |
throughオプションが設定されていないと以下のようにNoMethodError
が発生します。
[2] pry(main)> User.first.roles User Load (2.7ms) SELECT `users`.* FROM `users` WHERE `users`.`deleted_at` IS NULL ORDER BY `users`.`id` ASC LIMIT 1 NoMethodError: undefined method `roles' for #<User id: 1, ...
このように中間テーブルのモデル名を指定することで、Railsは、2つのモデル(Role
とUser
)の間に中間テーブルであるUserRole
が存在することを自動的に認識します。
モデルのコールバックメソッド
Active Recordによって提供される主要なコールバックメソッドの一部です。
各コールバックの使い分けのイメージが出来るように具体的なメソッド例を挙げて解説しています。
以下はChatGPTが提示してくれた内容を編集したものです。
コールバック | 解説 | 具体的なメソッド例 | 解説 |
---|---|---|---|
before_validation | バリデーションが実行される前に実行される | strip_whitespace | 文字列の前後の空白を除去する |
after_validation | バリデーションが実行された後に実行される | update_search_index | 検索インデックスを更新する |
before_save | レコードが保存される前に実行される | encrypt_password | DB保存前にパスワードを暗号化しセキュリティを強化 |
after_save | レコードが保存された後に実行される | send_email_notification | メール通知を送信する |
before_create | 新しいレコードが作成される前に実行される | generate_unique_code | ユニークなコードを生成する |
after_create | 新しいレコードが作成された後に実行される | send_welcome_email | ウェルカムメールを送信する |
before_update | レコードが更新される前に実行される | check_authorization | 認証を確認する |
after_update | レコードが更新された後に実行される | send_notification_email | 通知メールを送信する |
before_destroy | レコードが削除される前に実行される | log_deletion | 削除をログに記録する |
after_destroy | レコードが削除された後に実行される | update_cache | キャッシュを更新する |
ざっとユースケースのイメージだけ頭に入れておくと良いかもしれません。
続く…
コメント
本記事の内容は以上になります!
書籍の続きのアウトプットも随時更新したいと思います。
プログラミングスクールのご紹介 (卒業生より)**
お世話になったプログラミングスクールであるRUNTEQです♪
こちらのリンクを経由すると1万円引きになります。
RUNTEQを通じて開発学習の末、受託開発企業をご紹介いただき、現在も双方とご縁があります。
もし、興味がありましたらお気軽にコメントか、TwitterのDMでお声掛けください。