本当は怖くないRuby on Rails入門 Part.3

データベースの中身を表示しよう

今回はModel(モデル)を使ってDBの中身を表示してみましょう。

まずはrails db:createでDBを作成します。

$ rails db:create
Created database 'db/development.sqlite3'
Created database 'db/test.sqlite3'

railsはさまざまなDBに対応していますが、デフォルトではsqlite3が使われます。sqlite3はDBはファイルとしてできます。

上記でdevelopment用のDBとtest用のDBが作成されました。

開発環境

railsでは基本的に3種類の実行モードがあります。

  • production(本番)
  • development(開発)
  • test(テスト)

この実行モードごとにDBが分かれています。

productionモードは速度のために無駄な処理が少なくなっています。

developmentモードはrailsサーバーの再起動無しにファイルが読み込めたりといった開発中に便利なモードです。

testモードは自動テスト実行時のモードで後々出てきます。

テーブル作成

ModelはDBのTableと一対一の関係になります。DBにFruits(フルーツ)テーブルを作って、Fruitモデルも作りましょう。

ss

Modelの名前は単数形なのにDBテーブルは複数形なんですね。

railsは名前に非常にうるさいフレームワークです。DBテーブルは1レコードにつき1fruitを表現しています。テーブルは複数レコードを持つので複数形のfruitsになります。

ss

単数形・複数形を変換する機能もあります。

railsの中身を手軽に試せるコマンドrails consoleで試してみましょう。

$ rails console
>> "fruit".pluralize
=> "fruits"
>> "fruits".singularize
=> "fruit"

pluralizeは文字列を複数形にするメソッドで、singularizeはその逆です。

細かいですね。英語の先生みたい。

rails generateコマンドを使ってfruitsテーブルを作成しましょう。string型のnameカラムとinteger型のpriceカラムを持ちます。

$ rails generate model fruit name:string price:integer
      invoke  active_record
      create    db/migrate/20180206061109_create_fruits.rb
      create    app/models/fruit.rb
      invoke    test_unit
      create      test/models/fruit_test.rb
      create      test/fixtures/fruits.yml

rails generateはさまざまなコードを生成してくれる便利なコマンドです。手で書くより早いのでどんどん活用しましょう。

  • db/migrate/20180206061109_create_fruits.rb
  • app/models/fruit.rb

が生成されたことに注目してください。(test/以下については今は気にしないでください)

ファイルが勝手にできるとびっくりしますね。

Migration(マイグレーション)

生成されたファイルを順に見ていきましょう。

db/migrate/20180206061109_create_fruits.rbはMigration(マイグレーション)ファイルと呼ばれるものです。

db/migrate/20180206061109_create_fruits.rb:

class CreateFruits < ActiveRecord::Migration[5.1]
  def change
    create_table :fruits do |t|
      t.string :name
      t.integer :price

      t.timestamps
    end
  end
end

普通DBテーブルを作成するにはSQLでCREATE文を使います。しかしrailsではテーブル作成や削除、カラム追加の操作はこのMigrationの仕組みを使って行います。

苦労してSQLのCREATE文を覚えたのに!CREATE文じゃ駄目なんですか?

もちろんMigrationならではの利点があります。

まずはCREATE文はPostgreSQL, MySQLなどのDBによって書き方に微妙な違いがあります。その違いをMigrationは吸収してくれます。

また、Migrationで書いておくと、一度実行したテーブル構造の変更を戻すことができます。

ええ!!そんなことできるんですか?

実際の仕組みは単純で、上記のcreate_tableの場合は戻す場合にMigrationがCREATEとは逆のSQLを発行しています。

CREATE TABLEだったら戻す場合はDROP TABLE。カラム追加を戻す場合はカラム削除のSQLを発行してくれます。

へぇ〜すごい。やっぱりMigrationを使ったほうがいいですね。

もちろん中のデータが戻るわけではないので完全にDBがもとに戻るわけではないですが、これによってカラム追加や不要になったカラム削除を気軽にできます。

ファイルの中身を見ると大体わかると思いますが、nameというstring型のカラムとpriceというinteger型のカラムを持つfruitsテーブルを作成しています。

Timestamp

t.timestampsというのは下記の省略記法になります。

t.datetime :created_at
t.datetime :updated_at

created_atというのはrails(Active Record)の便利機能で、モデルのデータを新規作成(DBのレコードを追加)した時、created_atというカラムがあったら、そこに作成した時の日付と時間を入れておいてくれます。

また、updated_atというカラムがあると、レコードの更新時にその時の日付と時間を入れておいてくれます。

実際にWebアプリを作った人ならわかりますが、DBテーブルには大抵こんな感じのカラムを付けることが多いので自動で付いてくれるととてもうれしいのです。

Active Record、空気の読めるヤツ・・・。

Migrationの実行と確認

それでは実際にMigrationを実行してみましょう。

$ rails db:migrate
== 20180206061109 CreateFruits: migrating =====================================
-- create_table(:fruits)
   -> 0.0008s
== 20180206061109 CreateFruits: migrated (0.0008s) ============================

下記でログを見てみると実際に発行されたSQLが確認できます。

$ cat log/development.log
(省略)
   (0.4ms)  CREATE TABLE "fruits" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar, "price" integer, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL)
(省略)

あ、ほんとだ。CREATE TABLE "fruits"って書いてある。

sqlite3に付属のコマンドで調べてみてもしっかりテーブルが作成されています。

$ sqlite3 db/development.sqlite3
> .schema fruits
CREATE TABLE "fruits" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar, "price" integer, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);

idというカラムが勝手にできてる気がするんですが?

よく気づきましたね。

Active Recordでは指定しない場合、idというカラムがプライマリキーとして作成されます。

プリイマリキー・・・習ったような聞いたことあるような・・・

データベースのカリキュラムで習ったはずですよ。テーブルの中でそのレコードを一意に識別するための重複する値を持たないカラムです。

データの追加

sqlite3を使ったついでにSQL文でfruitsをいくつか追加しましょう。

えー、INSERT文でしたっけ・・・書けるかなー。

$ sqlite3 db/development.sqlite3
> INSERT INTO "fruits" (name, price, created_at, updated_at) VALUES ("Apple", 100, "2018-01-01 00:00:00", "2018-01-01 00:00:00");
> INSERT INTO "fruits" (name, price, created_at, updated_at) VALUES ("Orange", 180, "2018-01-01 00:00:00", "2018-01-01 00:00:00");
> INSERT INTO "fruits" (name, price, created_at, updated_at) VALUES ("Banana", 80, "2018-01-01 00:00:00", "2018-01-01 00:00:00");

なんとかINSERTできましたよ。

> SELECT * FROM "fruits";
        id = 1
      name = Apple
     price = 100
created_at = 2018-01-01 00:00:00
updated_at = 2018-01-01 00:00:00

        id = 2
      name = Orange
     price = 180
created_at = 2018-01-01 00:00:00
updated_at = 2018-01-01 00:00:00

        id = 3
      name = Banana
     price = 80
created_at = 2018-01-01 00:00:00
updated_at = 2018-01-01 00:00:00

ちゃんとデータも入っています。

フルーツの一覧表示

表示するデータが整ったのでページに表示してみましょう。Part.2で出てきた図を思い出してください。

mvc2

Routesは毎回同じファイルなので省略するとして、今度は先程のrails generateで生成されているのでModelが出てきます。

先程の図はこうなります。

mvc3

Routes, Model, Controller, Viewを書きます。(Modelは既にあると思います)

config/routes.rb:

Rails.application.routes.draw do
  get "home", to: "home#index"
  get "fruits", to: "fruits#index"
end

app/models/fruit.rb:

class Fruit < ApplicationRecord
end

app/controllers/fruits_controller.rb:

class FruitsController < ApplicationController
  def index
    @fruits = Fruit.all
  end
end

app/views/fruits/index.html.erb:

<h1>フルーツ一覧</h1>

<table>
  <tr>
    <th>名前</th>
    <th>値段</th>
  </tr>
<% @fruits.each do |fruit| %>
  <tr>
    <td><%= fruit.name %></td>
    <td><%= fruit.price %></td>
  </tr>
<% end %>
</table>

http://localhost:3000/fruitsにアクセスすると下記のように表示されるはずです。

ss

今回はControllerのindexメソッドに何か増えてます。Viewもちょっと難しく見えます。

順を追って説明しますね。

@fruits = Fruit.all

Controllerのこの部分はモデルであるFruitクラスを使ってDBからデータをとっています。

Modelがrails generateで自動生成されていることを思い出してください。中身を見てみましょう。

app/models/fruit.rb:

class Fruit < ApplicationRecord
end

シンプル〜

Modelはテーブル名(の単数形)のクラスがApplicationRecordを継承して存在しているだけでさまざまな機能が使えます。

ここで使っているallメソッドは単純に全部を持ってくるメソッドです。

SQLで言えばSELECT * FROM "fruits"をしているといえばわかりやすいでしょうか。

allメソッドはどこから来たんですか?Fruitクラスは空っぽなのに。

FruitApplicationRecordを継承していますよね?ApplicationRecordapp/models/application_record.rbにあります。

そしてさらに親クラスはActiveRecord::Baseになっています。Active Recordのクラスがallwhereなどのメソッドを持っていて、それを継承してModelを作るので自分でメソッドを書かなくても最初から便利なメソッドが使えるというわけです。

機能満載のクラスを継承すると便利なんですね。

次にViewを見てみましょう。

app/views/fruits/index.html.erb:

<h1>フルーツ一覧</h1>

<table>
  <tr>
    <th>名前</th>
    <th>値段</th>
  </tr>
<% @fruits.each do |fruit| %>
  <tr>
    <td><%= fruit.name %></td>
    <td><%= fruit.price %></td>
  </tr>
<% end %>
</table>

@fruitsにfruitsテーブルの全レコードが入っているというのはなんとなくわかりますが、Fruitクラスには1つもメソッドがなかったのにnamepriceメソッドが使えているのが不思議です。

これもActive Recordが持っているメソッドなんですか?

でもnamepriceはさっき自分で決めた名前だから知らないはずですよね?

鋭いですね。それが不思議に見えるのは正しいです。Rubyの勉強をちゃんとやった証拠です。

それこそが正にrubyとActive Recordの大きなマジックです。

Active RecordがなんとDBのカラム情報を見に行って、同じ名前のメソッドを勝手に定義しているのです!

ナ、ナンダッテー!!!

カラム名と同じメソッドは全て使えると考えて大丈夫です。

rubyファイルのどこにもnameメソッドやpriceメソッドは書いてないのに使えちゃうなんてほんと不思議ですねー。

今回はMigrationの使い方を中心にDBの情報を表示する方法を学びました。

これまでの内容を復習しておいてくださいね。特にSQLやRubyの構文が怪しい人は見直しておきましょう。

今回のPartでテーブル内容を全部出すだけだったらできるような気がしてきました!

次もがんばります!

前提となる技術とカリキュラム

リンク先はFjord Boot Camp内のカリキュラムになっています。

comments powered by Disqus