Ruby on Rails 入門

はじめに_

Ruby on Railsを味見してみる。

Railsのインストール方法は以下のとおり。

  • rbenvを用いたRuby on Rails環境の構築 on Ubuntu 20.04
  • macOS BigSur上でRuby on Rails

参考情報_

Ruby on Railsは使用するバージョンにより使用できるメソッドが大きく異なる。必ず自分が使用するバージョンの情報を参照すること。

  • Ruby on Rails Guide:公式情報。英語。
    • Ruby on Rails ガイド:有志による上記の日本語訳。ただし、最新バージョンに対応していないことがある。
  • Ruby on Rails API:公式情報。英語。Ruby on Railsで使用できるクラスやメソッドを調べることができる。

バージョンの確認方法_

2021年9月12日現在、以下のバージョンで動作を確認している。

% ruby -v
ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-linux]

% gem --version
3.2.27

% yarn --version
1.22.5

% node -v
v14.17.6

% npm -v
6.14.15

% sqlite3 --version
3.31.1 2020-01-27 19:55:54 3bfa9cc97da10598521b342961df8f5f68c7388fa117345eeb516eaa837balt1

% rails --version
Rails 6.1.4.1

Windows Subsystem for LinuxでLinux環境を整えた場合_

VcxSrv(XLaunch)の起動_

gnome-terminalを利用するので、まず、VcxSrv(XLaunch)を起動する。VcxSrvのインストールで設定したVcxSrvのアイコンをクリックし、VcxSrvを起動する。

gnome-terminalの起動_

gnome-terminalの利用で設定したgnome-terminalを起動する。

Ubuntuターミナルを起動し、ターミナル上で以下を起動する。

% gterm &

作業ディレクトリの準備_

% cd
% mkdir -p IntroRoR
% cd IntroRoR

簡易ブログの作成_

Ruby on Railsガイド:Rails をはじめように従って簡易ブログを作成してみる。

(Windows Subsystem for Linux上のUbuntuの場合)
% rails new blog --skip-spring --skip-listen

(その他の場合)
% rails new blog --skip-bundle

% cd blog
% bundle install --local
% rails webpacker:install

Ruby on Railsの動作確認を行う。以下のコマンドで開発用Webサーバを起動する。

% rails server
=> Booting Puma
=> Rails 6.1.4.1 application starting in development 
=> Run `bin/rails server --help` for more startup options
Puma starting in single mode...
* Puma version: 5.4.0 (ruby 3.0.2-p107) ("Super Flight")
*  Min threads: 5
*  Max threads: 5
*  Environment: development
*          PID: 21029
* Listening on http://127.0.0.1:3000
* Listening on http://[::1]:3000
Use Ctrl-C to stop

Webブラウザで、http://localhost:3000/ にアクセスし、Ruby on Railsのメッセージが出ていたらインストール&設定成功している。なお、以下の画像のRubyやRailsのバージョンは上記のバージョンと異なるが気にしなくてよい。

開発用Webサーバを停止させる。Ctrlキーを押しながら「C」のキーを押す(以下、この操作をCtrl-cと表記する)。

Ruby on Railsの開発環境には http://localhost:3000/で経由でアクセスできる。

Hello Rails_

% rails generate controller Welcome index
      create  app/controllers/welcome_controller.rb
       route  get 'welcome/index'
      invoke  erb
      create    app/views/welcome
      create    app/views/welcome/index.html.erb
      invoke  test_unit
      create    test/controllers/welcome_controller_test.rb
      invoke  helper
      create    app/helpers/welcome_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    scss
      create      app/assets/stylesheets/welcome.scss

エディタ(Ubuntu 20.04だとgeditやVScodeなど、macOSだとテキストエディタなど)にて app/views/welcome/index.html.erb を開く。

編集内容は以下の通り。

<h1>Hello Rails!</h1>
<p>My name is 「自分の名前」</p>

config/routes.rbを編集する。以下の内容に変更する。これでhttp://localhost:3000/にアクセスしたときのページが welcome/index になる。

Rails.application.routes.draw do
  get 'welcome/index'
 
  root 'welcome#index'
end

以下のコマンド実行し、Webブラウザでhttp://localhost:3000/にアクセスし、トップページの表示が切り替わっていることを確認する。

% rails server

確認できたら、開発用WebサーバをCtrl-cで停止する。

Ruby on Railsのシンプルな構造の練習_

Rails をはじめよう: 5 MVCを理解するの内容を実行するとRuby on Railsの基本的な構造が理解できる。

5章を実行してみる。

scaffoldを用いた補足説明_

以下に5章の流れに沿った上で事前にscaffold機能を使って該当するファイルを自動生成した場合の補足説明を載せる。なお、scaffold機能では6.3.3 バリデーションとエラーメッセージの表示 は記載されない。自分で付け加えること。

scaffoldの実行_

いちいち、コードを打つのが大変なので scaffoldという機能で上記で作成・編集すべきファイルを作成してしまうことにする。

% cd ~/IntroRoR/blog
% rails generate scaffold article title:string text:text
      invoke  active_record
   ~中略~
      invoke  assets
      invoke    scss
      create      app/assets/stylesheets/articles.scss
      invoke  scss
      create    app/assets/stylesheets/scaffolds.scss

% rails db:migrate (注意:下の数字は作成した時刻によって変わる。この例通りでなくてよい)
== 20210113163652 CreateArticles: migrating ===================================
-- create_table(:articles)
   -> 0.0037s
== 20210113163652 CreateArticles: migrated (0.0039s) ==========================

config/routes.rbの確認。(resources :articlesという記述が追加されている)。

%  more config/routes.rb
Rails.application.routes.draw do
  resources :articles
  get 'welcome/index'
  root 'welcome#index'
end

ルーティング(アクセスしたURLおよびHTTPメソッドと呼び出されるコントローラ名およびアクションとの対応)の確認。

% rails routes
                                  Prefix Verb   URI Pattern                                                                                       Controller#Action
                                articles GET    /articles(.:format)                                                                               articles#index
                                         POST   /articles(.:format)                                                                               articles#create
~中略~
                      rails_disk_service GET    /rails/active_storage/disk/:encoded_key/*filename(.:format)                                       active_storage/disk#show
               update_rails_disk_service PUT    /rails/active_storage/disk/:encoded_token(.:format)                                               active_storage/disk#update
                    rails_direct_uploads POST   /rails/active_storage/direct_uploads(.:format)                                                    active_storage/direct_uploads#create

動作確認_

以下のコマンドで開発用Webサーバを起動する。その後、http://localhost:3000/articles にアクセスし、適当に入力したり、リンク先をたどったりしてみる。

% rails server

一通り確認が終わったら、Ctrl-cで開発用Webサーバを停止する。次は、自動生成されたソースコードのどの部分が、先ほど閲覧したWebページに関連しているのかを見ていく。

データ入力(new, create)_

記事(article)に関する処理を担当するコントローラArticlesControllerの確認。

% more app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  before_action :set_article, only: [:show, :edit, :update, :destroy]

  # GET /articles
  # GET /articles.json
  def index
    @articles = Article.all
  end
 ~後略~

Webブラウザでhttp://localhost:3000/articles/newにアクセスすると、articles_controller.rbで定義されているメソッド(アクション) newが実行される。当該部分は以下のとおり。

  def new
    @article = Article.new
  end

newメソッドで定義されている処理が終わると自動的に app/views/articles/new.html.erb で定義されている処理が呼び出され、Webブラウザに表示される。new.html.erbの中身は以下のとおり。

% more app/views/articles/new.html.erb 
<h1>New Article</h1>

<%= render 'form', article: @article %>

<%= link_to 'Back', articles_path %>

2行目の記述「<%= render 'form', article: @article %>」はパーシャルという機能をつかって、同じ内容の入力フォームが存在する edit.html.erbとソースコードを共有している。入力フォームを実現しているソースコードはapp/views/articles/_form.html.erbである。_form.html.erbの中身は以下のとおり。

(moreコマンドでページ送りはスペースキー、終了はqキー)
% more app/views/articles/_form.html.erb 
<%= form_with(model: article) do |form| %>
  <% if article.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(article.errors.count, "error") %> prohibited this articl
e from being saved:</h2>

      <ul>
        <% article.errors.each do |error| %>
          <li><%= error.full_message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :title %>
    <%= form.text_field :title %>
  </div>

  <div class="field">
    <%= form.label :text %>
    <%= form.text_area :text %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

app/views/articles/new.html.erb で入力されたデータは ArticlesControllerで定義されているcreateメソッド(createアクション。以後ArticlesController#createで表記)で処理される。当該部分は以下のとおり。

  # POST /articles or /articles.json
  def create
    @article = Article.new(article_params)

    respond_to do |format|
      if @article.save
        format.html { redirect_to @article, notice: "Article was successfully created." }
        format.json { render :show, status: :created, location: @article }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @article.errors, status: :unprocessable_entity }
      end
    end
  end

Ruby on Railsでは入力フォームから受け取ったデータをストロングパラメータという仕組みを使ってチェックしている(想定している項目のデータだけが送られているどうかをチェックする。ただし、送られてきたデータの内容のチェックは別)。

上記のcreateメソッド中の「article_params」というのはメソッド名であり、以下のようにArticlesController#article_paramsとして定義されている。当該部分は以下のとおり。

    # Only allow a list of trusted parameters through.
    def article_params
      params.require(:article).permit(:title, :text)
    end

データの表示(show)_

無事にデータベースに入力データが保存されると以下の記述によってArticlesController#show の処理にリダイレクトされる。URLとしては http://localhost:3000/articles/ID番号 となる。上述のcreateアクションのうち、リダイレクトを行っている部分は以下のとおり。

format.html { redirect_to @article, notice: "Article was successfully created." }

ArticlesController#showは以下のように定義されている。

  # GET /articles/1 or /articles/1.json
  def show
  end

何も定義がされていないがフィルタという機能により、showメソッドが呼び出される前に以下の処理が行われている。以下はRailsの特殊なメソッド before_actionである。この意味は show, edit, update, destroyメソッドが呼び出される前にset_articleメソッドを実行せよというものになっている。

before_action :set_article, only: %i[ show edit update destroy ]

set_articleメソッドはArticlesControllerで定義されている。

    # Use callbacks to share common setup or constraints between actions.
    def set_article
      @article = Article.find(params[:id])
    end

これはarticlesテーブルからIDに対応するレコードを取り出し、@articleに代入するという処理である。

ArticlesController#showの処理がおわると、自動的に app/views/articles/show.html.erb に記載されている処理が実行され、Webブラウザで表示される。

% more app/views/articles/show.html.erb 
<p id="notice"><%= notice %></p>

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>

検証 (バリデーション) の追加_

6.3.3 バリデーションとエラーメッセージの表示 をする。

app/models/article.rbを編集し、以下の内容にする。

class Article < ApplicationRecord
  validates :title, presence: true
  validates :body, presence: true, length: { minimum: 10 }
end

これはApplicationRecord#validate というメソッドを呼び出している。このメソッドはデータベースの保存時にメソッドで定義されている条件をデータが満たしているかをチェックし、満たしているならば保存、そうでないならば保存を中止しエラーを返す。

今回の例ではtitle項目が空でないこと、body項目が空でないこと、かつ、文字列の長さが最低10文字であるというのが条件となっている。

データの一覧表示(index)_

上記ページでBackのリンクをクリックすると http://localhost:3000/articles というURLに飛ぶ。このURLにアクセスすると ArticlesController#indexが実行される。

  def index
    @articles = Article.all
  end

ArticlesController#indexの処理が終わると、app/views/articles/index.html.erbの処理が実行されWebブラウザで表示される。

% more app/views/articles/index.html.erb 
<p id="notice"><%= notice %></p>

<h1>Articles</h1>

<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>Text</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @articles.each do |article| %>
      <tr>
        <td><%= article.title %></td>
        <td><%= article.text %></td>
        <td><%= link_to 'Show', article %></td>
        <td><%= link_to 'Edit', edit_article_path(article) %></td>
        <td><%= link_to 'Destroy', article, method: :delete, data: { confirm: 'A
re you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Article', new_article_path %>

上記index.html.erbで生成されるページのリンク「Edit」をクリックすると http://loclahost:3000/記事のID番号/edit というURLに飛ぶ。このURLにアクセスすると ArticlesController#edit が実行される。先ほどのArticlesController#showメソッドと同様にArticlesController#set_articleメソッドがeditメソッド実行前に実行されている。

  def edit
  end

ArticlesController#editの処理が終了すると自動的にapp/views/articles/edit.html.erbの処理が実行されWebブラウザで表示される。

% more app/views/articles/edit.html.erb 
<h1>Editing Article</h1>

<%= render 'form', article: @article %>

<%= link_to 'Show', @article %> |
<%= link_to 'Back', articles_path %>

データの更新(edit, update)_

edit.html.erbでの入力フォームはindex.html.erbと共通なので、共通の入力フォーム処理を_form.html.erbに記載し、「<%= render 'form', article: @article %>」で呼び出している。

edit.html.erbで入力されたデータはArticlesController#updateへ送られて処理される。

  # PATCH/PUT /articles/1 or /articles/1.json
  def update
    respond_to do |format|
      if @article.update(article_params)
        format.html { redirect_to @article, notice: "Article was successfully updated." }
        format.json { render :show, status: :ok, location: @article }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @article.errors, status: :unprocessable_entity }
      end
    end
  end

ArticlesController#updateで使われている「article_params」はArticlesController#createで使われているのと同様にストロングパラメータという機能に対応するためのメソッド。

ここでは更新が成功すると http://localhost:3000/articles/ID番号 にリダイレクトする。このURLにアクセスすると ArticlesController#showが実行される。

データの削除(destroy)_

http://localhost:3000/articles にアクセスし、そこにあるリンク「Destroy」をクリックする。確認メッセージの後に、 ArticlesController#destroyが実行される。ArticlesController#destroyの前にArticlesController#set_article が実行され、削除対象のarticleオブジェクトが取得されている(@article)。この処理により、データベースから該当のレコードが削除される。

  # DELETE /articles/1 or /articles/1.json
  def destroy
    @article.destroy
    respond_to do |format|
      format.html { redirect_to articles_url, notice: "Article was successfully destroyed." }
      format.json { head :no_content }
    end
  end

削除終了後は http://localhost:3000/articles へリダイレクトされる。このURLにアクセスすると ArticlesController#index が実行される。

ここまでのまとめ_

上記の7つのメソッド new, create, show, index, edit, update, destroy があるデータをコントローラを通して作成(Create)、読み込み(Read)、更新(Update)、削除(Delete)するときに使う基本メソッドである。Ruby on RailsではURL+HTTPメソッドと上の7つの基本メソッドが自動的に結びついている。

また、newメソッドに対応する new.html.erb、showメソッドに対応する show.html.erb、indexメソッドに対応するindex.html.erb、editメソッドに対応する edit.html.erbがそれぞれのメソッド終了後に自動的に呼び出される。

コントローラがモデルで定義されている処理によってデータベースからデータを引き出し、ビューに引き渡す処理をしたうえでビューに引き渡すというのがRuby on RailsのModel-View-Controller(MVC)モデルである。

ArticlesControllerを経由してArticleModel(データベースのarticlesテーブル)を操作する際のURLとコントローラの対応は以下のとおりである。

URL対応するコントローラのアクション対応するviewsのファイル操作の意味
http://localhost:3000/articlesindexindex.html.erbarticleの一覧表示
http://localhost:3000/article/:id (GET)showshow.html.erbarticleの表示
http://localhost:3000/article/newnew, createnew.html.erb新規作成
http://localhost:3000/article/edit/:idedit, updateedit.html.erb更新
http://localhost:3000/article/:id (DELETE)destroy--削除

開発用Webサーバを起動し、http://localhost:3000/articles をWebブラウザで開き、URLと開発用Webサーバのログを見比べながら、上の表の対応関係を確かめてほしい。

% rails server

複数のテーブルの連携_

以下はarticleに関連するファイルをscaffoldで作成していても実行できる。

練習:コメントの追加_

7 モデルを追加するを実行する。

練習:パーシャルの練習_

8 リファクタリングを実行する。

練習:コメントの削除_

9 コメントを削除するを実行する。

おわりに_

関係データベースシステムの重要な応用例がWebアプリケーションである。Ruby on Railsは関係データベースを利用する際にSQLをソースコード内に記述しなくても、Rubyプログラム上から簡単にアクセスできる仕組みオブジェクト-リレーショナルマッピング(ORマッピング)を採用している。

参考

しかし、良いWebアプリケーションを作成するためには良いデータベースの設計が必要であるし、ORマッピングを用いるとしてもSQLを理解した上で、効率の良いソースコードを書かなくてはならない。

この講義を通して学んだ内容は、このようなWebアプリケーション開発などの応用事例の基礎となる事柄である。

戻る_