Yanonoblog!

こつこつと

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モデルに対してクエリを実行する動作を分離することにより、クエリをより柔軟に構築できるようにします。

具体的な動作は以下になります。

# 実行
pry(main)> User.all.class

# 結果
=> User::ActiveRecord_Relation
# 実行 (メソッドチェーンする例)
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

このコードでは、UserRoleの間にUserRoleという中間テーブルがあります。

UserモデルはUserRoleとの1対多の関係を持ち、Roleモデルとは中間テーブルを介して多対多の関係を持ちます。

RoleモデルはUserRoleとの1対多の関係を持ち、Userモデルとは中間テーブルを介して多対多の関係を持ちます。

UserRoleモデルは、中間テーブルであるため、UserRoleの両方に対して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つのモデル(RoleUser)の間に中間テーブルである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です♪

https://runteq.jp/r/ohtFwbjW

こちらのリンクを経由すると1万円引きになります。

RUNTEQを通じて開発学習の末、受託開発企業をご紹介いただき、現在も双方とご縁があります。

もし、興味がありましたらお気軽にコメントか、TwitterのDMでお声掛けください。

https://twitter.com/outputky

参考