Rails のモデルについて基本的な内容をまとめました。
この記事は以下の構成になっています。
モデルとは
モデルとは、MVC アーキテクチャの M にあたるもので、主にデータを操作やビジネスロジックを管理する役割を持つ。
ビジネスロジックとは、アプリの要件を実現するデータ処理のロジックのこと。
モデルはデータベースのテーブルに対応し、レコードの作成、読み込み、更新、削除等の処理などを行う。
モデルファイルの作成方法
モデルのファイルを作成するには以下のコマンドを実行する。
rails g model [モデル名]
rails g model User
rails g model Product name price:integer active:boolean
成功すると、以下の出力のようにモデルファイル以外にもマイグレーションやテストなど複数のファイルが作られる。
rails generate model User
invoke active_record
create db/migrate/20240213131600_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
create test/fixtures/users.yml
app/models/user.rb
が作られたモデルファイルで、内容は以下のようになっている。
class User < ApplicationRecord
end
User
クラスの定義しか記述されていないが、ApplicationRecord
クラスを継承していることによって、ApplicationRecord
クラスのメソッドを使える。
ApplicationRecord
ApplicationRecord とは全てのモデルの親クラスとして機能するクラス。全てのモデルで共通したい処理や設定はこのクラスに書く。
ApplicationRecord はapp/models/application_record.rb
で定義されており、以下の内容になっている。
class ApplicationRecord < ActiveRecord::Base
primary_abstract_class
end
ApplicationRecord はさらにActiveRecord::Base
を継承している。
Active Record
Active Record とは、Rails に組み込まれている ORM のことで、ActiveRecord::Base
を継承することで機能する。
ORM とは、オブジェクトリレーショナルマッピングの略で、簡単にいうと Ruby のオブジェクトを使って、SQL を書かずにデータベースを操作するというもの。
CRUD 処理は、Create、Read、Update、Delete の頭文字をとったもので、基本的なデータを操作する処理のこと。
Active Record によって自動的に CRUD 処理に関連するメソッドが作られているので、簡単に使うことができる。
Create
Create はデータベースにデータを保存する処理。
Create に関連するメソッドは主にcreate
、save
、new
がある。
create
create
はレコードを作成しデータベースにデータを保存するメソッド。保存が失敗するとfalse
を返す。
User.create(name: 'test', email: 'test@example.com')
create!
とすると保存が失敗した時にfalse
ではなく、例外を返す。
!
による違いはsave
やupdate
などでも同じ。
new
new
はモデルのインスタンスを作成するメソッドで、データの保存は行われない。
user = User.new(name: 'test', email: 'test@example.com')
また、モデルのオブジェクトの属性は以下のような記述の仕方で変更できる。
user.name = 'hoge'
new
で作成したインスタンスをデータベースに保存するにはsave
メソッドを使う。
save
save
はすでにレコードがあれば更新し、なければ作成するメソッド。
user = User.new(name: 'test', email: 'test@example.com')
user.save
Read
Read はデータベースのデータを取得する処理。
Read に関連するメソッドは主にall
、first
、find
、find_by
、where
がある。
all
all
は全てのデータを取得するメソッド。
users = User.all
first
first
はデータベースに保存されている最初のデータを取得するメソッド。
first_user = User.first
find
find
は指定した id に一致するデータを取得するメソッド。
user = User.find(1)
find_by
find_by
は指定した条件に一致する最初のデータを取得するメソッド。最初のデータなので 1 つだけ。
test_user = User.find_by(name: 'test')
where
where
は条件に一致するすべてのデータを取得するメソッド。
users = User.where(name: 'test')
Update
Update
はデータベースのデータを更新する処理。
Update
に関連するメソッドは主にupdate
、update_all
がある。
update
update
はデータを更新するメソッド。
user = find(1)
user.update(name: 'hoge')
update_all
update_all
は条件に一致した全てのデータを一括で更新するメソッド。
User.where(active: true).update_all(active: false)
Delete
Delete はデータベースのデータを削除する処理。
Delete に関連するメソッドは主にdestroy
、destroy_by
、destroy_all
がある。
destroy
destroy
はデータを削除するメソッド。
user = find(1)
user.destroy
destroy_by
destroy_by
は指定した条件に一致するデータを削除するメソッド。
User.destroy_by(name: 'test')
destroy_all
destroy_all
は全てのデータを削除するメソッド。
User.destroy_all
order
order
は指定したカラムによってデータを並べ替えるメソッド。
:desc
を指定すると降順、:asc
を指定すると昇順になる。
latest_users = User.order(created_at: :desc)
limit
limit
は取得するデータの件数を指定するメソッド。
users = User.all.limit(10)
offset
offset
はデータの取得時に何件目から取得するかを指定するメソッド。
users = User.all.offset(10)
上記の記述で、最初のユーザー 10 件を省いて、11 件目からユーザーを取得する。
count
count
はデータの件数を数えるメソッド。
User.count
avarage
average
はデータの平均値を計算するメソッド。
Product.average('price')
sum
sum
はデータの合計を計算するメソッド。
Product.sum('price')
maximum
maximum
はデータの最大値を取得するメソッド。
Product.maximum('price')
minimum
minimum
はデータの最小値を取得するメソッド。
Product.minimum('price')
バリデーション
バリデーションはデータの保存や更新時に適切な値かどうかチェックする機能。
基本的にvalidates
を使って以下のようにデータのカラムごとに指定する。
class User < ApplicationRecord
validates :name, presence: true, length: { maximum: 50 }
validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :password, presence: true, length: { minimum: 8 }
end
例では以下のことをチェックしている。
- 名前が空でないこと
- 名前の文字数が 50 文字以内であること
- メールアドレスが空でないこと
- メールアドレスが一意であること(同じメールアドレスが存在しないこと)
- メールアドレスの形式が正しいこと
- パスワードが空でないこと
- パスワードの文字数が 8 文字以上であること
コールバック
コールバックは、モデルオブジェクトの変更前か変更後に自動的に処理を実行するためのメソッド。
コールバックは以下のように使う。
class User < ApplicationRecord
before_save :downcase_email
private
def downcase_email
self.email = email.downcase
end
end
ここではbefore_save
というコールバックを使って、オブジェクトが保存される前にメールアドレスを小文字にするという処理を行なっている。
他にもバリデーションの前後や、オブジェクトの作成前後、レコードの作成前後、オブジェクトの削除前後に実行できるコールバックがある。
アソシエーション
アソシエーションとは、モデル同士の関係のことで、例えばユーザーごとの投稿を扱うといった時に使う。
テーブルに関しても関係を持たせる。ユーザーごとの投稿の場合は、投稿が誰のものか表すために Posts テーブルにuser_id
という外部キーを持たせる。
1 対 1
モデル同士の関係が、例えばユーザーのプロフィールといった場合、1 対 1 の関係になる。1 対 1 の関係を設定したい場合はhas_one
を使う。
プロフィールがユーザーを持っているのではなく、ユーザーがプロフィールを持っていると考えるのが普通なので、この場合は User モデルでhas_one
を以下のように記述する。
class User < ApplicationRecord
has_one :profile
end
そして Profile モデルには以下のように記述する。
class Profile < ApplicationRecord
belongs_to :user
end
belongs_to
を使うことで、どのモデルに従属しているかを設定できる。つまり外部キーを持つモデルに記述する。
1 対多
モデル同士の関係が、先ほどの例で挙げたユーザーの投稿といった場合は、1 人のユーザーが複数の投稿をもつことが普通なので、1 対多という関係になる。1 対多の関係を設定したい場合はhas_many
を使う。
class User < ApplicationRecord
has_many :posts
end
複数なのでモデル名は複数形にする。
class Post < ApplicationRecord
belongs_to :user
end
Post モデルにはbelongs_to
を設定する。これによってユーザーが持つ複数の投稿と反対に、投稿のユーザーの情報を取得できる。
多対多
モデル同士の関係が複数のデータを持つ場合、多対多の関係になる。
例えば、投稿のいいね機能では、ユーザーは複数の投稿をいいねすることができ、投稿は複数のユーザーからいいねされる。
このような多対多の関係を作るには中間テーブルとそのモデルを作成する。
中間テーブルはそれぞれのテーブルの外部キーを保存して、多対多の関係を作るためのテーブル。
いいね機能では User と Post モデルがあるという前提で、中間テーブルと Like モデルを作る。
以下のコマンドを実行。
rails g model Like user:references post:references
中間テーブルのマイグレーションファイルを作成し、マイグレーションを実行。
class CreateLikes < ActiveRecord::Migration[6.1]
def change
create_table :likes do |t|
t.references :user, null: false, foreign_key: true
t.references :post, null: false, foreign_key: true
t.timestamps
end
end
end
likes テーブルでuser_id
を使って検索するとそのユーザーがどの投稿にいいねをしたかがわかり、post_id
を使って検索するとその投稿に誰がいいねしているかがわかる。
User、Post、Like それぞれのモデルで、テーブル同士の関係を設定する。
user.rb
class User < ApplicationRecord
has_many :likes
has_many :liked_posts, through: :likes, source: :post
end
post.rb
class Post < ApplicationRecord
has_many :likes
has_many :liked_users, through: :likes, source: :user
end
like.rb
class Like < ApplicationRecord
belongs_to :user
belongs_to :post
end
enum は状態や種別、評価などの一連の値を扱うためのもの。
具体的には、以下のような値。
enum は以下のように記述する。
class User < ApplicationRecord
enum status: { active: 0, banned: 1, withdrawn: 2 }
end
class Article < ApplicationRecord
enum status: { draft: 0, unpublished: 1, published: 3 }
end
enum を使うことで、状態を数値ではなく意味のあるシンボルで管理することができ、user.active?
のような状態に応じたスコープやメソッドも自動で生成される。また内部的にはシンボルではなく数値で保存されるためデータベースの効率も良い。
enum を使わない場合は、integer か string でstatus: 0
のように管理する必要があるので、どんな状態かがわかりづらくなる。
scope
scope
は SQL のクエリを再利用するためのもの。
例えば、最新のデータを取得するというのはよく使う検索の仕方なので、以下のように書くことで使いまわせるようになる。
scope :latest, -> { order(created_at: :desc) }
Post モデルであれば、Post.latest
とすることで、created_at
の降順で並び替えられたデータを取得できる。
テーブル結合
テーブル結合は複数のテーブルを結合してデータを取得する方法。
Rails ではjoins
やincludes
などを使ってテーブル結合を行う。
以下のようにjoins
を使えば INNER JOIN と同じように、内部結合で関連があるデータのみを取得できる。
User.joins(:posts)
includes
では LEFT OUTER JOIN と同じように、外部結合で関連がない場合でも主テーブルのデータを取得できる。またincludes
は N+1 問題を防ぐ役割もある。
User.includes(:posts)
トランザクションはデータベースのデータを変更する複数の処理を一連の処理としてまとめることで、データの一貫性や整合性を保つ仕組みのこと。
トランザクションの処理は全て成功すれば完了とみなし、1 つでも失敗すれば全ての変更がロールバックされる。
トランザクションは以下のようにActiveRecord::Base.transaction
を使って書く。
ActiveRecord::Base.transaction do
user.save!
order.save!
payment.save!
end
トランザクションの例では、EC サイトのような商品を購入して決済が行われる処理が挙げられる。
商品の購入と決済では、商品の在庫を減らす処理と、口座の金額を減らす処理などが必要になるので、トランザクションでまとめる必要がある。
もしトランザクションを使わずに単体で処理を行うと、どちらかの処理だけが失敗したときに、商品の購入ができているのにお金が支払われていなかったり、商品を購入できていないのに残高が減っているというような状態が起こりうる。
生の SQL を使う
ORM を使わずに、生の SQL を使うこともできる。
生の SQL を使うには、find_by_sql
やconnection.execute
などを使う。
find_by_sql
は生の SQL でデータを取得するときに使う。
users = User.find_by_sql("SELECT * FROM users WHERE status = 'active'")
connection.execute
は SQL を直接データベースに実行して結果を返す。
ActiveRecord::Base.connection.execute("DELETE FROM users WHERE status = 'inactive'")
ただし生の SQL は SQL インジェクションのような攻撃のリスクがあり、 セキュリティ上よくないので、基本的には ORM を使うべき。
どうしても生の SQL を使う場合は以下のように SQL の値の部分にプレースホルダーを使って SQL インジェクションを防ぐ。
users = User.find_by_sql(["SELECT * FROM users WHERE status = ?", 'active'])