データベースを使ったアプリ作成 2 (Ruby on Rails の利用)

はじめに

演習を行う上で必要事項をまとめる.

MVC モデル

Rails では MVC (Model, View, Controller) モデルで開発を行う. データベースを使ったアプリ作成 1 では V と C しか使わなかったが,本ドキュメントでは M も扱う.

モデル:
  データベースなどでデータを管理

コントローラ:
   モデルからデータを読み出して成形してビューに引き渡す
   ビューからデータを受け取って成形してモデルに格納する

ビュー : 
   HTML でデータを表示する
   利用者からの画面入力を受け付け

CRUD

Web アプリには基本となる 4 つの機能があり,その頭文字を取って CRUD と呼ばれている.

  • ページの新規作成(Create)
  • 表示(Read)
  • 更新(Update)
  • 削除(Destroy)

Rails で CRUD 機能を持ったアプリを作る場合は,rails の scaffold という命令を使うのが簡単である. scaffold は「足場」という意味で,開発の足場となるようなコントローラを生成することからその名前がつけられている.

scaffold を用いた CRUD アプリの作成

今回は,本のタイトル,著者名,メモを管理する簡単なアプリを制作する.

アプリの作成

作業ディレクトリに移動する.もし my_web_apps ディレクトリが存在しない場合は mkdir コマンドで作成すること.

$ cd ~/my_web_apps

アプリを作る.以下のように rails new コマンドを行う. 以下の例のように append config/importmap.rb と出れば OK である. 途中で gem をインストールする必要がある場合にはパスワードの入力が求められる.

$ rails new books_app --database=mysql
  ...(略)...
  append  config/importmap.rb

上記のように,--database=mysql を指定してデータベースとして MySQL (mariaDB) を指定した場合には,データベースにアクセスするための情報をファイルに書く必要がある.

$ cd books_app

$ vi config/database.yml

  default: &default
    adapter: mysql2
    encoding: utf8mb4
    pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
    username:                                           <-- 入力
    password:                                           <-- 入力
    socket: /var/run/mysqld/mysqld.sock

次にアプリを作成するために rails g (g = generate) を実行するが, ここでは scaffold を指定する. コントローラ名として book を指定し,その後ろにデータベースのスキーマを指定する. なお,コントローラ名は複数形にしないこと!

$ rails g scaffold book title:string author:string memo:text

モデル (データベース) を作成する.

$ rails db:create
$ rails db:migrate

  == 20230724045213 CreateBooks: migrating ======================================
  -- create_table(:books)
     -> 0.0024s
  == 20230724045213 CreateBooks: migrated (0.0027s) =============================

サーバを起動する.

$ rails s -b 0.0.0.0

サーバを起動したら,ブラウザで http://10.176.0.1XX:3000/books にアクセスしてみよ (XX は自分の VM に合わせること).

以下に示すように,作成したアプリは CRUD の各機能,すなわち, 新規作成,表示,更新,削除を持っていることを確認せよ. 本の表示や編集では,本の ID (自動入力) が付され, /books/1, /books/1/edit のような URL となる.

確認ができたら Ctrl-C でサーバを止める.

モデルの確認

rails から離れて,mysql のコンソールにログインし, 今回のアプリ用のデータベースが作成されているか確認してみる. ユーザ名は hogehoge ではなく,自分のユーザ名とすること.

$ mysql -u hogehoge -p

  Enter password:

  MariaDB [(none)]> show databases;
  +------------------------+
  | Database               |
  +------------------------+
  | books_app_development  |  <-- アプリのデータベース (開発用)
  | books_app_test         |  <-- アプリのデータベース (テスト用)
  | information_schema     |
  | j4db                   |
  | mysql                  |
  | performance_schema     |
  +------------------------+

詳細は省くが,Ruby on Rails では development (開発用), test (テスト用), production (本番用) とレベルの切り分けがなされている. 最初は開発用の development で作業することになるので books_app_development を use する.

MariaDB [(none)]> use books_app_development
   Database changed

books テーブル (コントローラ名の複数形) が自動的に作られていることがわかる. books テーブルにブラウザ経由で入力した情報が書き込まれていることが確認できる.

MariaDB [books_app_development]> show tables;
   +----------------------------------+
   | Tables_in_books_app_development  |
   +----------------------------------+
   | ar_internal_metadata             |
   | books                            |
   | schema_migrations                |
   +----------------------------------+
   3 rows in set (0.001 sec)

MariaDB [books_app_development]> select * from books ;
    +----+--------------+--------------+--------------+----------------------------+----------------------------+
    | id | title        | author       | memo         | created_at                 | updated_at                 |
    +----+--------------+--------------+--------------+----------------------------+----------------------------+
    |  1 | ほげほげ     | すぎやま     | へろへろ     | 2023-07-24 05:44:54.382889 | 2023-07-24 05:44:54.382889 |
    |  2 | あれれ       | すぎやま     | これこれ     | 2023-07-24 05:45:23.967323 | 2023-07-24 05:45:23.967323 |
    +----+--------------+--------------+--------------+----------------------------+----------------------------+
    2 rows in set (0.001 sec)

確認が終わったら MySQL コンソールから抜ける

MariaDB [books_app_development]> quit
  Bye

コントローラの確認

データベースはコントローラで制御されている. scaffold を使って作成したコントローラでは,all メソッドでデータベースの全件検索をしている. また,セキュリティ対策のために「StrongParameter」という形で利用可能なパラメタ (テーブルのカラム) に制限がかかっている.

$ less app/controllers/books_controller.rb

  class BooksController < ApplicationController
    before_action :set_book, only: %i[ show edit update destroy ]

    # GET /books or /books.json
    def index
       @books = Book.all      <-- データベースから全件検索し (ActiveRecord),
                                  @book インスタンス変数へ代入し,ビューへ渡す
    end                           

    ...(中略)...

    # GET /books/new
    def new
       @book = Book.new       <-- 空の Book モデルオブジェクトを作り,
                                  @book インスタンス変数へ代入し,ビューへ渡す
    end

    ...(中略)...

    # POST /books or /books.json
    def create
       @book = Book.new(book_params)   <-- パラメタ(属性,値)を元に Book モデルオブジェクトを作成

       respond_to do |format|
       if @book.save                   <-- データベースを保存.保存に成功したら以下の 2 行を実行
           format.html { redirect_to book_url(@book), notice: "Book was successfully created." }
           format.json { render :show, status: :created, location: @book }
       else                            <-- データベースの保存に失敗した場合
           format.html { render :new, status: :unprocessable_entity }
           format.json { render json: @book.errors, status: :unprocessable_entity }
       end
     end

    ...(中略)...

    # POST /books or /books.json
    def update
        respond_to do |format|
            if @book.update(book_params)     <-- 入力データを用いてデータベースを更新

    ...(中略)...

      def book_params
         params.require(:book).permit(:title, :author, :memo)    <-- セキュリティ対策
      end                                                             StrongParameters(require, permit メソッド)を
                                                                      利用して params に制限をかける      
    ...(以下略)...                                                    book の title, author, memo カラムのみ取得可能

上記で使われている Book クラスは,app/models/book.rb で定義されている. 継承関係を紐解いていくと,結局のところ Book クラスは ActiveRecord クラスを 継承していることがわかる. ActiveRecord の基本的な操作については 以前のドキュメントを参照されたい. テーブルの全件参照を Book.all といったように all メソッドで行うことができる. 条件付き検索をする場合は Book.where(...) という形で書くことができる.

$ cat app/models/book.rb
  class Book < ApplicationRecord            <-- Book クラスは ApplicationRecord を継承
  end

$ cat app/models/application_record.rb
  class ApplicationRecord < ActiveRecord::Base             <-- ApplicationRecord は ActiveRecord を継承
    primary_abstract_class
  end

ビューの確認

トップページのビューを確認すると,データベースを使ったアプリ作成 1 では見られなかった命令 (render, link_to) が使われているのが分かる.

$ less app/views/books/index.html.erb

  <p style="color: green"><%= notice %></p>
  <h1>Books</h1>
  <div id="books">
    <% @books.each do |book| %>
       <%= render book %>                <---- render 命令
       <p>
         <%= link_to "Show this book", book %>  <-- link_to 命令
       </p>
     <% end %>
  </div>
  <%= link_to "New book", new_book_path %>      <-- link_to 命令

link_to の方はソースコードとブラウザ表示画面を見比べれば意味が分かると思うが, render の方は説明が必要であろう. render は別の共通して使うビューファイルを埋め込むために用いる. 上記は "render book" と書かれているので, _book.html.erb が読み込まれる (先頭にアンダーバーが付加されていることに注意). このファイルの中身を見てみると,本のタイトル (book.title),著者 (book.autho),メモ (book.memo) が表示されるようになっていることがわかる.なお,メソッド名はテーブルのカラム名と一致している.

$ app/views/books/_book.html.erb

 <div id="<%= dom_id book %>">
   <p>
       <strong>Title:</strong>
       <%= book.title %>
   </p>
   <p>
       <strong>Author:</strong>
       <%= book.author %>
   </p>
   <p>
       <strong>Memo:</strong>
       <%= book.memo %>
   </p>
 </div>

課題

scaffold で作った既存テーブルへカラムを追加する. 既にある books テーブルに出版社 (string 型,カラム名 company) を加えてみよ.

  • モデルの更新

    • books テーブルに string 型の company を加える.
    $ rails g migration AddCompanyToBooks company:string
    
       書式: rails g migration Addカラム名Toテーブル名 カラム名:型名  (カラム名とテーブル名の先頭は大文字)
    • データベースのテーブルを更新する.
    $ rails db:migrate
    • mysql のコンソールで,books テーブルのスキーマを確認する.
    $ mysql -u ユーザ名 -p
    
      ...(コマンド 略)...
    
      MariaDB [books_app_development]> desc books;
      +------------+--------------+------+-----+---------+----------------+
      | Field      | Type         | Null | Key | Default | Extra          |
      +------------+--------------+------+-----+---------+----------------+
      | id         | bigint(20)   | NO   | PRI | NULL    | auto_increment |
      | title      | varchar(255) | YES  |     | NULL    |                |
      | author     | varchar(255) | YES  |     | NULL    |                |
      | memo       | text         | YES  |     | NULL    |                |
      | created_at | datetime(6)  | NO   |     | NULL    |                |
      | updated_at | datetime(6)  | NO   |     | NULL    |                |
      | company    | varchar(255) | YES  |     | NULL    |                |
      +------------+--------------+------+-----+---------+----------------+
      7 rows in set (0.002 sec)
  • ビューの修正
    • 入力・表示・修正の画面で Company も扱われるようにすること
  • controller を修正.
    • StrongParameters に company を追加する
  • [発展]ビューの見た目をカッコよくすること.
  • [発展] 書籍一覧ページで (http://10.176.0.1XX:3000),書籍の全データを表示するのではなく,タイトルのみ表示するようにする.
  • 提出:修正したファイルと,以下の画面のスナップショットを提出すること.スナップショットには必ず URL バーを含めること.
    • http://10.176.0.1XX:3000/books
    • http://10.176.0.1XX:3000/books/YY
    • http://10.176.0.1XX:3000/books/YY/edit