Ruby on Rails(以降、Rails)は、MVC呼ばれるデザインパターンを採用したフレームワークです。
この記事では、そのMVCのうちのM、すなわちModelについて解説します。
- そもそもMVCとは
- そしてModelとは
- どのようにしてModelを作成するか
といった基本的な内容から、
- データの取り出し方
- データの更新方法
- データの新規作成
などの実務的な内容に関しても解説していきます。Modelの基礎を正しく理解し、スムーズに応用できるように、わかりやすく解説します!
Modelとは
Modelは、MVCを構成するコンポーネントの1つです。MVCは、以下の3つのコンポーネント(構成要素)から構成されるデザインパターンです。
コンポーネント(構成要素) | 説明 | 備考 |
---|---|---|
Model(モデル) | データベースを取り扱う | データベースへの格納方法は、Modelに隠ぺいする |
View(ビュー) | 画面表示を取り扱う | 表示方法は、Viewに隠ぺいする |
Controller(コントローラー) | (ユーザーの入力を受けて)ModelとViewにアレコレ指示する | データベースへの格納方法や表示方法は知らない |
Railsでは、Rails標準のライブラリであるActive Recordを利用して、Modelを実装します。つまり、Railsでは、Active Recordを使って、データベース(テーブル)にデータを格納したり更新したり、データベース(テーブル)に格納したデータを検索したりできるということです。
Modelの使い方を理解するために環境を準備しよう!
まずはRailsの開発環境を構築しよう
Modelの使い方を理解するために、Railsの開発環境を構築しておきましょう。Railsの開発環境の構築方法は、以下の記事で解説していますので、ぜひご覧ください。
この記事では、app/samurai/model-demoディレクトリを作成して開発環境を構築した場合を例に、説明を続けます。
hirb gemとhirb-unicode gemをインストールしよう
この後、Modelの使い方を確認する際、Railsコンソールを使用します。Railsコンソールで出力したデータの見栄えを良くにするために、以下の2つのgemをインストールしましょう。
gem | 説明 |
---|---|
hirb gem | 出力結果を表形式で出力する |
hirb-unicode gem | マルチバイト文字の表示を補正する |
(1)Gemfileの最終行に以下の内容を追記します。
gem 'hirb' gem 'hirb-unicode'
(2)以下のコマンドを入力します。
bundle install
これで、hirb gemとhirb-unicode gemがインストールされました。
Modelを作成する
RailsでModelを作成するには、以下の2つを作成します。
- モデルクラス(Active Record)
- データベース(テーブル)
この記事では、movieというModelと、directorというModelを作成します。movie Modelでは、以下の情報を格納します。
カラム | データ型 | 説明 |
---|---|---|
title | string | 映画のタイトル |
published | date | 映画が公開された日付 |
sales | bigint | 映画の興行収入(単位:ドル) |
rank | integer | 興行収入順位 |
director | references (belongs_toでも同じ) | 監督(外部キー) |
director Modelでは、以下の情報を格納します。
カラム | データ型 | 説明 |
---|---|---|
name | string | 監督名 |
Railsで上記のようなModelを簡単に作成するために、bin/rails generate modelコマンドと、bin/rails db:migrateコマンドが用意されています。まずは、bin/rails generate modelコマンドの基本構文を説明します。
rails generate model name field:type [...]
パラメータ | 説明 |
---|---|
name | 生成するモデルクラスの名前 |
field | フィールド名 |
type | データの型など 代表的な型は以下のとおりです。 ・string ・date ・integer ・bigint ・references / belongs_to |
基本構文をチラ見したら、bin/rails generate modelコマンドの使用例を見てみましょう。
(1)「端末」を起動して、以下のコマンドを1行ずつ順番に入力します。
cd app/samurai/model-demo/ bin/rails generate model movie title:string published:date sales:bigint rank:integer director:references
実行結果:
Running via Spring preloader in process 3683 invoke active_record create db/migrate/20180705061405_create_movies.rb create app/models/movie.rb invoke test_unit create test/models/movie_test.rb create test/fixtures/movies.yml
実行結果にあるとおり、4つのファイルが作成されていますが、この記事では、以下の3つのファイルに注目します。
ファイル名 | ファイルの種類 | 説明 |
---|---|---|
db/migrate/ 20180705061405_create_movies.rb | マイグレーションファイル | データベース(テーブル)の作成方法を記述したファイルです。 ファイル名の先頭の数字部分は、コマンドを実行した日時により異なります。 |
app/models/movie.rb | モデルクラスファイル | モデルクラスを定義するファイルです。 |
test/fixtures/movies.yml | フィクスチャファイル | データベース(テーブル)に格納するデータを記述するファイルです。 |
各ファイルの内容は以下のとおりです。マイグレーションファイル(db/migrate/20180705061405_create_movies.rb)
class CreateMovies < ActiveRecord::Migration[5.1] def change create_table :movies do |t| t.string :title t.date :published t.bigint :sales t.integer :rank t.references :director, foreign_key: true t.timestamps end end end
モデルクラス(app/models/movie.rb)
class Movie < ApplicationRecord belongs_to :director end
フィクスチャファイル(test/fixtures/movies.yml)
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html one: title: MyString published: 2018-07-05 sales: rank: 1 director: one two: title: MyString published: 2018-07-05 sales: rank: 1 director: two
続けて、director(外部キー)が参照するテーブルも作成しましょう。
(2)以下のコマンドを入力します。
bin/rails generate model director name:string
実行結果:
Running via Spring preloader in process 3775 invoke active_record create db/migrate/20180705061458_create_directors.rb create app/models/director.rb invoke test_unit create test/models/director_test.rb create test/fixtures/directors.yml
movieと同様、4つのファイルが作成されます。
次に、作成されたマイグレーションファイルを元に、データベース(movieテーブル、directorテーブル)を作成します。
(2)以下のコマンドを入力します。
bin/rails db:migrate
実行結果:
== 20180705061405 CreateMovies: migrating ===================================== -- create_table(:movies) -> 0.0118s == 20180705061405 CreateMovies: migrated (0.0120s) ============================ == 20180705061458 CreateDirectors: migrating ================================== -- create_table(:directors) -> 0.0017s == 20180705061458 CreateDirectors: migrated (0.0018s) =========================
以上で、モデルクラス(Active Record)とデータベース(movieテーブル、directorテーブル)が作成され、すなわちmovie Modelとdirector Modelが作成されたことになります。
マイグレーションファイルを作成しない方法について(–skip-migration)
ちなみに、マイグレーションファイルを作成せずに、それ以外のファイルだけを作成する場合は、以下のように–skip-migrationを指定します。
bin/rails generate model movie title:string published:date sales:bigint rank:integer director:references --skip-migration
実行結果:
Running via Spring preloader in process 4100 invoke active_record create app/models/movie.rb invoke test_unit create test/models/movie_test.rb create test/fixtures/movies.yml
上で紹介した実行結果と比べると、マイグレーションファイルが作成されていないことがわかりますね。
Modelに関する命名規約について
Modelに関連する命名規約をまとめました。頭文字の大文字/小文字と、単数形/複数形の違いを覚えておくとスムーズに作業を進められるでしょう。
ファイルの種類 | 命名規約 | 例 |
---|---|---|
モデルクラスファイル名 | 頭文字が小文字、単数形 | movie.rb |
モデルクラス名 | 頭文字が大文字、単数形 | Movie |
テーブル名 | 頭文字が小文字、複数形 | movies |
モデルクラスをカスタマイズする
bin/rails generate modelコマンドで作成されるモデルクラスは、極めて基本的な内容しか書かれていません。
モデルクラスには、データベースを取り扱う(データベースへの格納方法は、Modelに隠ぺいする)という役割がありますので、その役割から逸脱しないようにプログラムを書いていきましょう。ここでは、モデルクラスでよく使う機能を紹介します。
検証機能を付与する(validates)
モデルクラスをカスタマイズして、検証機能を付与できます。検証機能について詳しくは、以下の記事を参考にしてください。
クエリを定義する(scope)
モデルクラスでよく使うクエリを、モデルクラスファイルで定義できます。クエリを定義する方法について詳しくは、以下の記事を参考にしてください。
Modelを削除する
もし間違ったModelを作成してしまった場合は、モデルクラスとテーブルを削除します。
モデルクラスを削除する
モデルクラスを削除するには、以下のコマンドを入力します。
bin/rails destroy model movie
実行結果:
Running via Spring preloader in process 3946 invoke active_record remove db/migrate/20180705061405_create_movies.rb remove app/models/movie.rb invoke test_unit remove test/models/movie_test.rb remove test/fixtures/movies.yml
テーブルを削除する
モデルクラスを削除したら、テーブルを削除しましょう。
bin/rails db:drop:all
実行結果:
Dropped database 'db/development.sqlite3' Database 'db/test.sqlite3' does not exist Database 'db/production.sqlite3' does not exist
ここでは、すべてのテーブルを削除しましたが、必要なテーブルがある場合は、以下のコマンドを実行すると良いでしょう。
bin/rails db:migrate:reset
モデルクラスを利用する
Modelを作成できたところで、Modelを扱ってみましょう。RailsでModelを扱うには、モデルクラスを利用して、テーブルに格納したデータを検索したり更新します。
テストデータを準備する
まずは、テストデータを準備します。この記事では、テーブルへテストデータを格納するために、フィクスチャを利用します。
(1)test/fixtures/movies.ymlを以下のように編集します。
変更前:
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html one: title: MyString published: 2018-07-05 sales: rank: 1 director: one two: title: MyString published: 2018-07-05 sales: rank: 1 director: two
変更後:
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html one: title: アバター published: 2009-12-18 sales: 2787965087 rank: 0 director: jc two: title: タイタニック published: 1997-12-19 sales: 2187463944 rank: 0 director: jc three: title: スター・ウォーズ/フォースの覚醒 published: 2015-12-18 sales: 2068223624 rank: 0 director: jja
director:に指定したjcやjjaは、次のtest/fixtures/directors.ymlで指定するラベルに対応しています。
(2)test/fixtures/directors.ymlを以下のように編集します。
変更前:
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html one: name: MyString two: name: MyString
変更後:
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html jc: name: James Cameron jja: name: J.J. Abrams jr.ar: name: Joe Russo, Anthony Russo
以上がテストデータです。このテストデータをテーブルに格納しましょう。
(3)以下のコマンドを入力します。
bin/rails db:fixtures:load
テーブルに格納できたことを確認しましょう。
(4)以下のコマンドを1行ずつ順番に入力します。
bin/rails console Hirb.enable Movie.all
実行結果:
Movie Load (1.4ms) SELECT "movies".* FROM "movies" +-----------+------------------------+------------+------------+------+-------------+-----------------------+------------------------+ | id | title | published | sales | rank | director_id | created_at | updated_at | +-----------+------------------------+------------+------------+------+-------------+-----------------------+------------------------+ | 113629430 | スター・ウォーズ/フ... | 2015-12-18 | 2068223624 | 0 | 530990343 | 2018-07-05 06:25:1... | 2018-07-05 06:25:10... | | 298486374 | タイタニック | 1997-12-19 | 2187463944 | 0 | 175153456 | 2018-07-05 06:25:1... | 2018-07-05 06:25:10... | | 980190962 | アバター | 2009-12-18 | 2787965087 | 0 | 175153456 | 2018-07-05 06:25:1... | 2018-07-05 06:25:10... | +-----------+------------------------+------------+------------+------+-------------+-----------------------+------------------------+ 3 rows in set
(5)以下のコマンドを1行ずつ順番に入力します。
Director.all
実行結果:
Director Load (0.2ms) SELECT "directors".* FROM "directors" +-----------+--------------------------+-------------------------+-------------------------+ | id | name | created_at | updated_at | +-----------+--------------------------+-------------------------+-------------------------+ | 175153456 | James Cameron | 2018-07-05 06:25:10 UTC | 2018-07-05 06:25:10 UTC | | 530990343 | J.J. Abrams | 2018-07-05 06:25:10 UTC | 2018-07-05 06:25:10 UTC | | 615043578 | Joe Russo, Anthony Russo | 2018-07-05 06:25:10 UTC | 2018-07-05 06:25:10 UTC | +-----------+--------------------------+-------------------------+-------------------------+ 3 rows in set
無事にテストデータが格納されていますね!
(6)以下のコマンドを入力します。
exit
検索してデータを取り出す
モデルクラスを利用して、テーブルからデータを検索して取り出す方法を紹介します。ここではRailsコンソールを使っていますが、モデルクラスファイルでも同様のコードでデータを検索して取り出せます。
(1)以下のコマンドを入力します。
bin/rails console
ここでは、whereメソッドを使って、2001-01-01以降に公開された映画のデータを取り出してみましょう。
(2)以下のコマンドを1行ずつ順番に入力します。
Hirb.enable Movie.where('published > "2001-01-01"')
実行結果:
Movie Load (1.6ms) SELECT "movies".* FROM "movies" WHERE (published > "2001-01-01") +-----------+------------------------+------------+------------+------+-------------+-----------------------+------------------------+ | id | title | published | sales | rank | director_id | created_at | updated_at | +-----------+------------------------+------------+------------+------+-------------+-----------------------+------------------------+ | 113629430 | スター・ウォーズ/フ... | 2015-12-18 | 2068223624 | 0 | 530990343 | 2018-07-05 06:25:1... | 2018-07-05 06:25:10... | | 980190962 | アバター | 2009-12-18 | 2787965087 | 0 | 175153456 | 2018-07-05 06:25:1... | 2018-07-05 06:25:10... | +-----------+------------------------+------------+------------+------+-------------+-----------------------+------------------------+ 2 rows in set
該当するデータが2件取得できていますね。
(3)以下のコマンドを入力します。
exit
データを更新する
次に、テーブルのデータを更新してみましょう。やはりRailsコンソールを使います。
(1)以下のコマンドを入力します。
bin/rails console
ここでは、whereメソッドを使って、title(映画のタイトル)が「アバター」のデータを取り出して、rank(興行収入順位)を1にしてみましょう。
(2)以下のコマンドを1行ずつ順番に入力します。
Hirb.enable Movie.where('title = "アバター"').update(rank: '1')
実行結果:
Movie Load (1.8ms) SELECT "movies".* FROM "movies" WHERE (title = "アバター") (0.1ms) begin transaction Director Load (0.1ms) SELECT "directors".* FROM "directors" WHERE "directors"."id" = ? LIMIT ? [["id", 175153456], ["LIMIT", 1]] SQL (0.8ms) UPDATE "movies" SET "rank" = ?, "updated_at" = ? WHERE "movies"."id" = ? [["rank", 1], ["updated_at", "2018-07-05 06:27:35.943715"], ["id", 980190962]] (16.1ms) commit transaction +-----------+----------+------------+------------+------+-------------+-------------------------+-------------------------+ | id | title | published | sales | rank | director_id | created_at | updated_at | +-----------+----------+------------+------------+------+-------------+-------------------------+-------------------------+ | 980190962 | アバター | 2009-12-18 | 2787965087 | 1 | 175153456 | 2018-07-05 06:25:10 UTC | 2018-07-05 06:27:35 UTC | +-----------+----------+------------+------------+------+-------------+-------------------------+-------------------------+ 1 row in set
rankの値が「0」から「1」に変更されました。
(3)以下のコマンドを入力します。
exit
データを作成する
最後に、新しいデータを作成してテーブルに追加してみましょう。ここでもRailsコンソールを使います。
(1)以下のコマンドを入力します。
bin/rails console
(2)以下のコマンドを1行ずつ順番に入力します。
Hirb.enable movie = Movie.new(title: 'アベンジャーズ/インフィニティ・ウォー', published: '2018-04-27', sales: '2036816820', rank: '5', director_id: '175153456')
実行結果:
+----+---------------------------------------+------------+------------+------+-------------+------------+------------+ | id | title | published | sales | rank | director_id | created_at | updated_at | +----+---------------------------------------+------------+------------+------+-------------+------------+------------+ | | アベンジャーズ/インフィニティ・ウォー | 2018-04-27 | 2036816820 | 5 | 175153456 | | | +----+---------------------------------------+------------+------------+------+-------------+------------+------------+ 1 row in set
データベース(テーブル)に保存します。
(3)以下のコマンドを入力します。
movie.save
実行結果:
(0.1ms) begin transaction Director Load (0.1ms) SELECT "directors".* FROM "directors" WHERE "directors"."id" = ? LIMIT ? [["id", 175153456], ["LIMIT", 1]] SQL (1.4ms) INSERT INTO "movies" ("title", "published", "sales", "rank", "director_id", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?) [["title", "アベンジャーズ/インフィニティ・ウォー"], ["published", "2018-04-27"], ["sales", 2036816820], ["rank", 5], ["director_id", 175153456], ["created_at", "2018-07-05 06:29:34.358453"], ["updated_at", "2018-07-05 06:29:34.358453"]] (6.6ms) commit transaction => true
新しいデータが追加されていることを確認しましょう。
(4)以下のコマンドを入力します。
Movie.all
実行結果:
Movie Load (0.1ms) SELECT "movies".* FROM "movies" +-----------+------------------------+------------+------------+------+-------------+-----------------------+------------------------+ | id | title | published | sales | rank | director_id | created_at | updated_at | +-----------+------------------------+------------+------------+------+-------------+-----------------------+------------------------+ | 113629430 | スター・ウォーズ/フ... | 2015-12-18 | 2068223624 | 0 | 530990343 | 2018-07-05 06:25:1... | 2018-07-05 06:25:10... | | 298486374 | タイタニック | 1997-12-19 | 2187463944 | 0 | 175153456 | 2018-07-05 06:25:1... | 2018-07-05 06:25:10... | | 980190962 | アバター | 2009-12-18 | 2787965087 | 1 | 175153456 | 2018-07-05 06:25:1... | 2018-07-05 06:27:35... | | 980190963 | アベンジャーズ/イン... | 2018-04-27 | 2036816820 | 5 | 175153456 | 2018-07-05 06:29:3... | 2018-07-05 06:29:34... | +-----------+------------------------+------------+------------+------+-------------+-----------------------+------------------------+ 4 rows in set
データが4つになりましたね!
まとめ
この記事では、Modelの基礎として、Modelの役割を説明しました。また、データの検索や更新を行う方法など、実務的な内容についても説明しました。Modelには、この記事で説明した内容のほかにも様々な役割や使い方があり、学ぶ範囲がとても広いテーマです。
まずは、この記事でまとめたModelの基礎をしっかり理解したうえで、さらに難しい内容に取り組んでみてはいかがでしょうか?もし、Modelの基礎を忘れてしまったら、この記事を思い出してくださいね!