【Rails】Active Record(モデル)の書き方《基本篇》

はじめに

ActiveRecord(モデル)は、MVCモデルのMに相当します。モデルはRailsアプリとデータベースの橋渡し役を担います。データベースの種類(MySQL、MariaDB、PostgreSQL、SQLite)にかかわらず、統一的なインターフェースでデータの操作(取得、作成、更新、削除)を行うことができます。

本記事では、ActiveRecord(モデル)の使い方についてまとめています。

モデルの基本

ORMフレームワーク

ORM (Object Relational Mapping) とは、データベースの種類を気にすることなくデータの操作(取得、作成、更新、削除)が行えること、またそのインターフェースのことをいいます。Railsはサードパーティ製のORMフレームワークを導入しなくても、ActiveRecord(モデル)がORMフレームワークの役割を担ってくれます。

モデルには以下の特徴があります。

  • コントローラーまたはビューからモデルを介してデータベースにアクセスできる。
  • 統一的なインターフェースでデータの操作(取得、作成、更新、削除)が行える。
  • 関連付けられているモデル同士の相互操作が容易に行える。
  • データ永続化の前にバリデーションチェックが行える。

モデルの作成

モデルを作成するには以下のコマンドを実行します。

$ rails generate model User name:string

モデル名は単数形(User)を指定しますが、実際に作成されるテーブル名は複数形(users)です。モデル名の後にカラム名と型をいくつでも指定することができます。

モデル作成コマンドを実行するとモデルの作成と同時にマイグレーションファイルも作成されます。作成されたマイグレーションファイルを基にマイグレーションを行うことで、データベースにテーブルが作成されます。マイグレーションについては以下の記事を参照してください。

モデルの削除

モデルを削除するには以下のコマンドを実行します。

$ rails destroy model User

モデル削除コマンドを実行してもデータベースのテーブルは削除されません。テーブルを残したままモデルを削除してしまうと不整合が起こるので、必ず該当のマイグレーションをロールバックなどして状態をdownにしてからモデルを削除してください。

データの操作

データの操作(取得、作成、更新、削除)はコントローラーとビューのどちらからでも行うことができます。ただし、特段の理由がなければ(MVCモデルの理念に従い)コントローラーから行うようにしたほうがいいでしょう。

データの取得

データを取得するメソッドはたくさんあります。ここでは、よく使うであろうメソッドのみ紹介します。

find

findは指定した主キーの値に一致するデータを取得します。主キーを条件にしているのでデータは必ず1件に絞り込まれます。

@user = User.find(10)

find_by

find_byは指定したカラムの値に一致するデータを取得します。条件によっては1件に絞り込まれるとは限りませんが、条件に一致したデータのうち、最初の1件のみを取得します。条件はカンマ(,)で区切って複数指定することができます。

@user = User.find_by(gender: 'male')

where

wherefind_byと似ていますが、条件に一致したデータをすべて取得する点が異なります。

@users = User.where(gender: 'male')

whereは条件を通常のSQLのように書くこともできます。値には引数を与えることもできます。

@users = User.where("gender = 'male' AND age > ?", params[:age])

# 以下はSQLインジェクションの危険があるため避ける
# @users = User.where("gender = 'male' AND age > #{params[:age]}")

order

orderは取得したデータを並べ替えます。ASC(昇順:デフォルト)またはDESC(降順)を指定します。

@users = User.all.order(name: :asc, updated_at: :desc)

find_each

find_eachは複数データをブロック(デフォルトで1000件)で取得し、ブロック単位で繰り返し処理を行います。最初のブロックの処理が完了したら次のブロックを取得し、また繰り返し処理を行います。最後のデータになるまでこれを繰り返します。

User.all.find_each do |user|
  ...
end

# 以下の書き方はメモリ消費が大きいので避ける
# User.all.each do |user|
#   ...
# end

Enum

値がenum型で設定されている場合、キーの名前を繋げるだけでデータを取得することができます。

# モデルに以下が定義されているとする
# enum gender: [male: 0, female: 1]

@users_male = User.male
@users_female = User.female

集計関数

一般的なSQLで用いられる集計関数(件数、平均、最小値、最大値、合計)を使うことができます。

@count = User.count
@average = User.average(:age)
@minimum = User.minimum(:age)
@maximum = User.maximum(:age)
@sum = User.sum(:age)

データの作成

データを作成するにはcreateまたはnewsaveを使います。createの代わりにcreate!を使うと、データの作成に失敗したときにActiveRecord::RecordInvalid例外が発生します。

User.create(name: 'Jane', gender: 'female', age: 20)
User.create!(name: 'Jane', gender: 'female', age: 20)

@user = User.new(name: 'Jane', gender: 'female', age: 20)
@user.save
@user.save!

データの更新

データを更新するにはupdateを使います。updateの代わりにupdate!を使うと、データの更新に失敗したときにActiveRecord::RecordInvalid例外が発生します。

@user = User.find(params[:id])
@user.update(age: 30)
@user.update!(age: 30)

# 以下の書き方でもOK
# @user = User.find(params[:id])
# @user.age = 30
# @user.save

複数データを一気に更新するにはupdate_allを使います。

@users = User.find_by(gender: 'female')
@users.update_all(age: params[:new_age])

データの削除

データを削除するにはdestroyまたはdestroy_byを使います。

@user = User.find(params[:id])
@user.destroy

User.destroy_by(id: params[:id])

複数データを一気に削除するにはdestroy_allを使います。

User.destroy_all

モデルの関連付け

モデル同士が密接な関係にあるとき、モデルの関連付けを明示的に行うことでRailsによる様々な恩恵を受けることができます。例えば、特定のArticleが複数のCommentを所持するとき(=複数のCommentは特定のArticleに従属するとき)、Articleが削除されたらCommentも削除されなければなりません。モデルの関連付けを行っておくとCommentの削除はRailsが自動で行ってくれます。

関連付けの種類

Railsで行える関連付けの種類は以下の通りです。

関連付け 用途 意味
belongs_to 1対1 特定のモデルが特定のモデルに従属する。
has_one 1対1 特定のモデルが特定のモデルを所持する。
has_many 1対多 特定のモデルが複数のモデルを所持する。
has_many :through 多対多 複数のモデルが複数のモデルを所持する。
has_one :through 1対1 特定のモデルが特定のモデルを所持する。
has_and_belongs_to_many 多対多 複数のモデルが複数のモデルを所持する。

1対1の関連付け

1対1の関連付けを行うには、一方のモデルにbelongs_toを設定し、もう一方のモデルにhas_oneを設定します。belongs_tohas_oneも対象は特定のモデルなので、指定するモデル名は単数形にします。

belongs_tohas_oneをどちらのモデルに設定するかは、モデルの意味を考える必要があります。以下の例だと、「給料は従業員を所持する」ではなく「従業員は給料を所持する」と考えたほうが自然なので、Employeehas_oneを設定し、Salarybelongs_toを設定します。

# 従業員モデル
has_one :salary, dependent: destroy

# 給料モデル
belongs_to :employee

belongs_toを設定するテーブルには外部キーカラムを追加する必要があります。以下はsalariesテーブルにemployeeカラムを追加し、外部キーを設定している例です。

# テーブル作成のマイグレーション
t.references :employee, null: false, foreign_key: true

# カラム追加のマイグレーション
add_reference :salaries, :employee, foreign_key: true

モデルに外部キーを設定する方法について詳しくは以下の記事を参照してください。

1対多の関連付け

1対多の関連付けを行うには、一方のモデルにbelongs_toを設定し、もう一方のモデルにhas_manyを設定します。has_manyの対象は複数のモデルなので、指定するモデル名は複数形にします。

# 記事モデル
has_many :comments, dependent: destroy

# コメントモデル
belongs_to :article

多対多の関連付け

多対多の関連付けを行うには、一方のモデルにhas_many :throughを設定し、もう一方のモデルにもhas_may :throughを設定します。そして、それぞれの中間となるモデルにbelongs_toを設定します。

# 記事モデル
has_many :tagmaps, dependent: destroy
has_many :tags, through: tagmaps

# タグマップモデル
belongs_to :article
belongs_to :tag

# タグモデル
has_many :tagmaps, dependent: destroy
has_many :articles, through: tagmaps

多対多の関連付けはhas_and_belongs_to_manyを使う方法もあります。

# 記事モデル
has_and_belongs_to_many :tags

# タグモデル
has_and_belongs_to_many :articles

has_and_belongs_to_many関連付けを使った場合、中間となる結合テーブルを作成する必要があります。このテーブルに主キーを設定すると関連付けが正常に動かないことがあるため、必ずid: falseを設定する必要があります。

class CreateArticlesTagsJoinTable < ActiveRecord::Migration[6.0]
  def change
    create_table :articles_tags, id: false do |t|
      t.bigint :article_id
      t.bigint :tag_id
    end

    add_index :articles_tags, :article_id
    add_index :articles_tags, :tag_id
  end
end

create_join_tableメソッドを使うこともできます。

class CreateArticlesTagsJoinTable < ActiveRecord::Migration[6.0]
  def change
    create_join_table :articles, :tags do |t|
      t.index :article_id
      t.index :tag_id
    end
  end
end

関連付けモデルのデータ操作

モデルに適切な関連付けを設定していると、一方のモデルのインスタンスから関連付けられているもう一方のモデルのデータを操作することができます。

1対1のデータ操作

1対1の関連付けでは、一方のモデルからもう一方のモデルに対して、同じようなメソッドを使って相互にデータ操作することができます。以下はEmployeeモデルとSalaryモデルの例です。両者には従属関係(has_onebelongs_to)が存在しますが、それを意識することなくデータ操作することができます。とはいえ、従属元モデル(belongs_toを設定したモデル)から従属先モデル(has_oneを設定したモデル)のデータ操作を行うことはほぼありません。

モデル同士は1対1のため、関連するモデルのデータ操作をするときはモデル名を単数形にします。また、関連するモデルのデータを作成するときは、build_associationcreate_associationのように、末尾に関連するモデル名を付与したメソッドを使います。

### データの取得 ###

# Employeeに関連するSalaryを取得
@salary = @employee.salary

# Salaryに関連するEmployeeを取得
@employee = @salary.employee


### データの作成 ###

# Employeeに関連するSalaryを作成
@salary = @employee.build_salary(salary_params)
@salary.save
# or
@employee.create_salary(salary_params)
@employee.create_salary!(salary_params)

# Salaryに関連するEmployeeを作成
@employee = @salary.build_employee(employee_params)
@employee.save
# or
@salary.create_employee(employee_params)
@salary.create_employee!(employee_params)


### データの更新 ###

# Employeeに関連するSalaryを更新
@salary = @employee.salary
@salary.reward = params[:reward]
@salary.update

# Salaryに関連するEmployeeを更新
@employee = @salary.employee
@employee.class = params[:class]
@employee.update

1対多のデータ操作

1対多の関連付けでは、複数のモデルを所持するモデル(has_manyを設定したモデル)と特定のモデルに従属するモデル(belongs_toを設定したモデル)とでデータ操作の方法が異なるため、1対多のデータ操作ではモデル同士の従属関係を意識する必要があります。とはいえ、従属元モデル(belongs_toを設定したモデル)から従属先モデル(has_oneを設定したモデル)のデータ操作を行うことはほぼありません。

モデル同士は1対多のため、従属先モデル(has_manyを設定したモデル)から従属元モデル(belongs_toを設定したモデル)のデータ操作をするときはモデル名を複数形にします。

### データの取得 ###

# Articleに関連するすべてのCommentを取得
@comments = @article.comments

# Commentに関連するArticleを取得
@article = @comment.article


### データの作成 ###

# Articleに関連するCommentを作成
@comment = @article.comments.build(comment_params)
@comment.save
# or
@article.comments.create(comment_params)
@article.comments.create!(comment_params)

# Commentに関連するArticleを作成
@article = @comment.build_article(article_params)
@article.save
# or
@comment.create_article(article_params)
@comment.create_article!(article_params)


### データの更新 ###

# Aarticleに関連するCommentを更新
@comment = @article.comments.find(10)
@comment.enable = false
@comment.update

# Commentに関連するArticleを更新
@article = @comment.article
@article.title = params[:title]
@article.update

多対多のデータ操作

多対多の関連付けでは、相互に複数のモデルを所持するモデル(has_manyを設定したモデル)のデータ操作を行います。多対多の関連付けでは相互にデータ操作することがあります。

モデル同士は多対多のため、相互のデータ操作をするときはモデル名を複数形にします。

### データの取得 ###

# Articleに関連するすべてのTagを取得
@tags = @article.tags

# Tagに関連するすべてのArticleを取得
@articles = @tag.articles


### データの作成 ###

# Articleに関連するTagを作成
@tag = @article.tags.build(tag_params)
@tag.save
# or
@article.tags.create(tag_params)
@article.tags.create!(tag_params)

# Tagに関連するArticleを作成
@article = @tag.articles.build(article_params)
@article.save
# or
@tag.articles.create(article_params)
@tag.articles.create!(article_params)


### データの更新 ###

# Aarticleに関連するTagを更新
@tag = @article.tags.where(name: 'Rails')
@tag.name = 'Ruby on Rails'
@tag.update

# Tagに関連するArticleを更新
@article = @tag.articles.find(20)
@article.author = params[:author]
@article.update

テーブルやカラムにコメントをつける

Rails 5からテーブルやカラムにコメントをつけることができるようになりました。つけたコメントはdb/schema.rbで確認できるので、別途テーブル設計書などのドキュメントを参照しなくてもよくなりました。

テーブル作成時

テーブル作成時にコメントをつけるには、マイグレーションファイルを以下のように修正します。

YYYYMMDDHHMMSS_create_articles.rb

class CreateArticlets < ActiveRecord::Migration
  def change
    create_table :articles, comment: '記事' do |t|
      t.string :title, comment: 'タイトル'
      t.text :content, comment: '本文'

      t.timestamps
    end
  end
end

テーブル作成後

テーブル作成後にコメントを追加するには、以下のようなマイグレーションファイルを作成します。

YYYYMMDDHHMMSS_add_comments_to_articles.rb

class AddCommmentsToStaffEmail < ActiveRecord::Migration
  def change
    change_table_comment :articles, '記事'

    change_column_comment :articles, :title, 'タイトル'
    change_column_comment :articles, :content, '本文'
  end
end

カラムの追加と同時にコメントを追加するには、マイグレーションファイルを以下のように修正します。

YYYYMMDDHHMMSS_add_category_to_articles.rb

class AddCategoryToArticles < ActiveRecord::Migration
  def change
    add_column :articles, :category, :string, comment: 'カテゴリー'
  end
end

まとめ

Webアプリケーションにデータベースはほとんど必須と言っても過言ではありません。実際、世の中に存在するほとんどのWebアプリケーションはデータベースを持ち独自のデータを保持しています。WebアプリケーションにとってなくてはならないデータベースとRailsアプリを繋ぐActiveRecord(ビュー)の使い方を把握しておく必要があります。

本記事を参考にして、ActiveRecord(ビュー)の使い方を覚えていただければと思います。

関連記事

【Rails】Paranoiaを使用した論理削除(ソフトデリート)
# はじめに Paranoiaは、Railsアプリケーションで論理削除(ソフトデリート)を実現するためのGemです。 論理削除は、データベースのレコードを物理的に削除するのではなく、削除フラグを設定することで「削除済み」とみなす方法です。こ [...]
2024年7月20日 21:33
【Rails】activerecord-multi-tenantを使用したマルチテナントアプリケーションの作成
# はじめに マルチテナントアプリケーションでは、複数の顧客(テナント)が同じアプリケーションを利用するため、データの分離が必要です。 activerecord-multi-tenantは、このようなマルチテナント環境をサポートするための便 [...]
2024年7月18日 16:50
【Rails】RubyとRailsにおけるattr_reader, attr_writer, attr_accessorの概念と使用方法
# はじめに RubyとRailsの開発において、`attr_reader`,`attr_writer`,`attr_accessor`は非常に便利なメソッドです。これらは、クラス内でインスタンス変数に対するゲッターおよびセッターメソッドを簡単に [...]
2024年7月17日 18:11
【Rails】RubyとRailsにおけるyieldの概念と使用方法
# はじめに RubyとRailsにおける`yield`は、メソッドやテンプレートの中で動的にコードブロックを実行する能力を提供し、これによってコードの再利用性と拡張性が大幅に向上します。本記事では、RubyとRailsにおける`yield`の概 [...]
2024年7月17日 13:15
【Rails】AASMを使用してオブジェクトの状態遷移を効率的に管理
# はじめに Railsアプリケーションにおいて、オブジェクトの状態管理は重要な課題の一つです。AASM (Acts As State Machine) gemは、複雑な状態遷移を効率的に管理します。本記事では、AASMの基本的な使い方を解説して [...]
2024年7月16日 18:00
【Rails】RSpec + Swagger + rswagでアプリケーションのAPIをテストおよびドキュメント化する方法
# はじめに Railsアプリケーションの開発において、APIのテストとドキュメント化は重要な要素です。 RSpecはテストフレームワークとして広く利用されており、SwaggerはAPIの設計とドキュメント化を支援します。これらを統合するr [...]
2024年7月16日 14:27
【Rails】mailcatcherを使用して開発環境でメール送信をテストする方法
# はじめに mailcatcherは、開発環境でのメール送信をキャプチャするためのツールです。ローカルで送信されたメールをブラウザ上で簡単に確認できるようにします。mailcatcherをRailsアプリケーションで使用する方法について説明しま [...]
2024年7月15日 16:37
【Rails】impressionistを使用してページビューやクリック数を追跡する方法
# はじめに impressionist Gemを使用してRailsアプリケーションでページビューやクリック数を追跡する方法について説明します。 # 実装方法 ## impressionist Gemのインストール まず、impre [...]
2024年7月15日 14:18