Ruby On Rails 学習記録 [3] Model編

model

モデル は データベースや外部サービスへのアクセスをはじめ、ビジネスロジック全般を担当するコンポーネントのことです。

Rails 3 以降では 以前のバージョンと比べて Modularity が向上した結果、モデル構築にも様々なコンポーネントを利用できるようになっています。

今回は、標準のO/Rマッパである ActiveRecord を利用していきます。

ActiveRecordRails の初期バージョンから Rails の 標準的な モデルコンポーネントとして提供されているライブラリで、リレーショナル・データベースをオブジェクト経由で操作するための手段を提供します。

O/Rマッパとは

リレーショナルデータベースとオブジェクト指向言語との橋渡しを受け持つライブラリのことです。

オブジェクトモデル(階層的)と データベースの採用しているリレーショナルモデルとは決定的に構造が異なっています。そのため、以前はデータベースから取得した表形式の結果を手動でオブジェクトのプロパティに割り当てたり、逆に、データベースに登録するべき値をオブジェクトから一つ一つ取り出したりする手順が必要であったわけです。このような作業は、単純ではありますが、コード量の膨張を招いており、開発生産性を低下支える一員にもなっていました。このようなアプリとデータベースとの構造的なギャップのことを インピーダンスミスマッチ といいます。

O/R マッパとは、このようなミスマッチを解消するためのツールです。 ActiveRecord では データベースのテーブル1つを1つのモデルクラスとして表現します。モデルクラスのインスタンスは、レコード一件に対応するオブジェクトとなり、オブジェクトのプロパティは、そのままテーブルのフィールドに対応します。

例示すると、 books という テーブルがあったとすれば、対応するモデルは Book クラスであり、 Book クラスは books テーブルは以下のフィールドと同名の isbn , title, publish のようなプロパティを持つことになるでしょう。

ActiveRecord を利用することで Rails では リレーショナルデータベース(表形式データ)をオブジェクトであるかのように操作できるようになります。

また、O/Rマッパを利用することで基本的には 生のSQLを記述する必要がなくなります。 SQL には往々にしてデータベース製品固有の表現が存在しますが、O/R マッパはそれらをラッピングしてくれているので、接続先のデータベースを変更した場合にも、アプリへの影響は最小限に抑えることができます。

(SQLの知識が必要ないわけではないので注意しましょう。)

データベースの設定

ActiveRecord を経由してデータベースに接続するには、config/database.yml 対して、接続設定を定義します。以下は、アプリ作成時にデフォルトで用意されているdatabase.ymlです。

# SQLite version 3.x
#   gem install sqlite3
#
#   Ensure the SQLite 3 gem is defined in your Gemfile
#   gem 'sqlite3'
#
default: &default
  adapter: sqlite3
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000

development:
  <<: *default
  database: db/development.sqlite3

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: db/test.sqlite3

production:
  <<: *default
  database: db/production.sqlite3

yml形式のファイルについては、以下の記事が良さそうだったので、参照してみてください。

Qiita:YAMLとは何か? - いつもRailsの設定ファイルで出てくるやつの正体

Rails は目的に応じて環境を使い分ける仕組みが最初から導入されています。環境は以下の4つが最初は定義されています。

  • development
  • test
  • production

これらを目的に応じて使い分けます。そしてそれぞれで別々のでーたべーすを用意するようになっています。これによって、例えば、開発環境で行った操作が不用意に本番環境に影響をおよぼすような事故を防ぐことができます。デフォルトでは developmentがつかわれるようになっています。

モデルクラスの作成

モデルクラスの作成用のコマンドは以下です。

rails generate model name field:type [...] [options]
# name: モデル名, field:フィールド名, type:データ型
# options: 動作オプション

主要なオプションは以下です。

-- indexes #外部キー列にインデックスを付与するかどうか(デフォルト: true)
-o, -- orm=NAME # 使用するO/Rマッパー(デフォルト: activerecord)
--migration # マイグレーションファイルを生成するか(デフォルト: true)
--timestamps # タイムスタンプ列を生成するか(デフォルト: true)
--t, --test-framework=NAME # 使用するテストフレームワーク(デフォルト: test_unit)
--fixture # フィクスチャを生成するか(デフォルト: true)

今回は、書籍情報を以下のようなカラムに分割して管理するものとし、対応するBookクラスを生成してみようと思います。

  • isbn : isbn コード
  • title: 書籍名
  • price: 価格
  • publish: 出版社
  • published: 刊行日
  • dl: サンプルダウンロードの有無
rails generate model book isbn:string title:string price:integer publish:string published:date dl:boolean

上記のコマンドで生成されたファイルは以下です。

app/models/book.rb
db/migrate/
test/fixtures/books.yml
test/models/book_test.rb

Fileの命名ルールに注目しましょう。以下のようになっています。

  • モデルクラス => 先頭は大文字で単数形 Book
  • モデルクラス(ファイル名) => 先頭は小文字で単数形 book.rb
  • テーブル => 先頭は小文字で複数形 books
  • テストスクリプト => xxxx_test.rb book_test.rb

モデルクラス(のインスタンス)は、それぞれテーブルの各業を表すので単数形に、テーブルはモデルの集合体という意味での複数形になるわけです。

マイグレーションFileによるテーブルの作成

rails generate コマンドを実行しただけでは実際のテーブルは作成されていません。以下でテーブルの作成を行っていきます。

Railsではテーブルの作成や修正に マイグレーション という機能を利用します。マイグレーションとは、一言で言うならば、テーブルレイアウトを作成/変更するための仕組みです。マイグレーションを利用することでテーブル保守の作業を半自動化できるのみならず、途中でレイアウト変更が生じた場合にも簡単に反映ができます。

マイグレーションを実行するためのマイグレーションファイルは rails generate model コマンドを実行した際に、すでに自動生成されているはずです。中身を確認しておきましょう。

パス db/migrate の中身を確認してみてください。

# db/migrate/20180327151318_create_books.rb
class CreateBooks < ActiveRecord::Migration[5.1]
  def change
    create_table :books do |t|
      t.string :isbn
      t.string :title
      t.integer :price
      t.string :publish
      t.date :published
      t.boolean :dl

      t.timestamps
    end
  end
end

上記の用になっていました。 change メソッドの中で呼び出している create_tableメソッドに注目してみましょう。これがbooks テーブルを新規に作成するためのコードです。

直感的に books という 名前のテーブルに対して それぞれの カラムを定義していることが見て取れると思います。こうした列定義が先程の rails generate model コマンドに渡した引数によって自動生成されています。

では、migrateを実行してみます。コマンドは以下です。

❯ rails db:migrate
== 20180327151318 CreateBooks: migrating ======================================
-- create_table(:books)
   -> 0.0008s
== 20180327151318 CreateBooks: migrated (0.0009s) =============================

上記のような結果になりました。このように表示されるとbooks table は正常に作成できています。

コマンドを実行するにあたって、引数の指定などは必要ありません。database.yml や実行すべきマイグレーションファイルなどは、Rails が自動的に判定してくれます。

では、 rails dbconsole コマンドを使って、作成したテーブルを確認してみましょう。

rails dbconsole と入力したあと、 .schema books と入力することで先ほど作成したテーブルの構造を確認することができます。

(今回はrails 標準の SQLite を利用しています。コマンド等を調査する際は参考にしてください。)

❯ rails dbconsole
.SQLite version 3.19.3 2017-06-27 16:48:08
Enter ".help" for usage hints.
sqlite> .schema books
CREATE TABLE IF NOT EXISTS "books" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "isbn" varchar, "title" varchar, "price" integer, "publish" varchar, "published" date, "dl" boolean, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);

上記のような結果となりました。

所感

モデルのインスタンス1つがテーブルの内容の一行に対応しているというのは重要な考え方だと感じています。

今日はちょっと途中までなのですが、体調を整えるべく早めに寝ます。

次回は、テストデータのインサートについてというテーマから再開します。