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

2022年6月16日 18:19

はじめに

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(ビュー)の使い方を覚えていただければと思います。

関連記事

【Ruby】Bundlerを使ってRubyGemsを作成/公開する方法
# はじめに Bundlerを使ってRubyGemsを作成および公開する方法について説明します。Bundlerを使わずにRubyGemsを作成/公開する方法については以下の記事を参照してください。 <iframe class="hatena [...]
2022年7月12日 23:18
【Ruby】RubyGemsを作成/公開する方法
# はじめに RubyGemsを作成および公開する方法について説明します。Bundlerを使ってRubyGemsを作成する方法については以下の記事を参照してください。 <iframe class="hatenablogcard" style [...]
2022年7月11日 21:52
【Rails】M1チップ搭載MacでRuby on Railsの開発環境構築
# はじめに M1チップ搭載MacにRuby on Railsの開発環境を構築する手順を記載します。 - MacBook Air (M1, 2020) - macOS Monterey 12.3.1 # Homebrew ## [...]
2022年5月5日 11:56
【Rails】Rakeタスクの基本情報と作成・実行方法
# はじめに Railsには標準でRakeというGemが同梱されています。RakeはRubyで実装されたMake(UNIX系のOSで使用できるコマンド)のようなビルド作業を自動化するツールです。Ruby Make、略してRakeというわけですね。 [...]
2022年3月7日 22:12
【Rails】モデルに外部キーを設定する方法とよく起こるエラー内容について
# はじめに Railsでモデルに外部キーを設定する方法について説明します。 # モデルに外部キーを設定する ## リレーションシップ 今回は1つのブログ記事は複数のコメントを持つ1対多のリレーションシップを例に説明します。現在は` [...]
2022年2月10日 14:18
【Rails】Capybaraのfill_inメソッドを実行すると「既存レコードの内容+指定した内容」がセットされる事象の原因と対処【RSpec】
# はじめに RSpec + Capybaraを使用して、Railsアプリの統合テストを実装しています。とあるモデルの編集画面において、入力フォームの内容を書き換えた上で送信し、レコードが更新されることを確認します。 入力フォームの内容を書 [...]
2022年1月27日 21:22
【Rails】GitHubのセキュリティアラートで発見された脆弱性を解消する方法
# はじめに GitHubにはセキュリティアラートという機能があります。セキュリティアラートはリポジトリに含まれるライブラリやパッケージの脆弱性を定期的にチェックし、脆弱性のあるライブラリやパッケージが発見されたらアラートで知らせてくれるという機 [...]
2022年1月16日 10:36
【Rails】devise-two-factorを使った2段階認証の実装方法【初学者】
# はじめに Railsアプリで2段階認証を実装するには、「rotp」というGemを使う方法の他に、「devise-two-factor」というGemを使う方法があります。「devise-two-factor」はその名の通り、IDとパスワードによ [...]
2021年12月12日 17:58