Ruby on Rails のチュートリアルをやってみました



今まで Java / Python / Javascript を使って開発することが多かったのですが、Ruby on Rails を使っているとの話を耳にすることが多いので、試しにチュートリアルをやってみました。



*Ruby on Rails とは

Rubyで書かれたWebアプリケーションフレームワークです。
少ないコード量で簡単にプログラミングできるように設計されています。MVCアーキテクチャを採用しているため、開発効率をより高めることができます。
その他に特徴としては、オブジェクト指向でクラスの継承もできる、SQLを使わなくてもデータベースの操作ができる、テスト自動化の仕組みが元から入っている、日本語のドキュメントが多いといった特徴があります。ブログサイトやECサイトなどで使われることが多いのですが、機械学習には向いていないようです。


*参考



*環境

  • MacOS
  • Ruby 2.3.3
  • rbenv 1.1.2
  • Rails 5.2.3


*環境構築

MacにはRubyが元から入っているので、複数のRubyのバージョンを環境で切り替えれるよう、rbenvを使います。
# Rubyのバージョン確認
$ ruby -v
ruby 2.3.3p222 (2016-11-21 revision 56859) [universal.x86_64-darwin17]

# rbenvのインストール
$ brew update
$ brew install rbenv ruby-build

# rbenvを使えるよう設定
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile
$ rbenv -v
rbenv 1.1.2

rbenv を使って指定バージョンをインストールします。
# 指定バージョンをインストール
$ rbenv install 2.3.3

# バージョン確認(まだローカルのまま)
$ rbenv versions
* system
2.3.3

# バージョン切り替え
$ rbenv rehash
$ rbenv global 2.3.3

# バージョン確認(指定バージョン)
$ rbenv versions
system
* 2.3.3

$ ruby -v
ruby 2.3.3p222 (2016-11-21 revision 56859) [x86_64-darwin17]

フレームワークとして使う Rails をインストールします。
$ gem install rails

$ rails --version
Rails 5.2.3


*アプリケーション作成

ターミナルでrails new {アプリ名}を実行すると、指定した名前のアプリケーションが作成されます。(作成された時点で Git も入っています)
$ rails new blog

$ cd blog/
/blog (master #)$

作成されたディレクトリは下記構成になっています。
blog
├── Gemfile
├── Gemfile.lock
├── README.md
├── Rakefile    ---- コマンドラインから実行するタスクの定義
├── app    ---- コントローラー、モデル、ビューなど開発で中心に使う
│   ├── assets
│   ├── channels
│   ├── controllers
│   ├── helpers
│   ├── jobs
│   ├── mailers
│   ├── models
│   └── views
├── bin    ---- 起動やデプロイ用のスクリプトファイル
│   ├── bundle
│   ├── rails
│   ├── rake
│   ├── setup
│   ├── spring
│   ├── update
│   └── yarn
├── config    ---- DBやルーティングなどの設定ファイル
│   ├── application.rb
│   ├── boot.rb
│   ├── cable.yml
│   ├── credentials.yml.enc
│   ├── database.yml
│   ├── environment.rb
│   ├── environments
│   ├── initializers
│   ├── locales
│   ├── master.key
│   ├── puma.rb
│   ├── routes.rb
│   ├── spring.rb
│   └── storage.yml
├── config.ru    ---- 起動時に必要になるRack設定ファイル
├── db    ---- データベーススキーマとマイグレーションファイル
│   └── seeds.rb
├── lib    ---- 拡張モジュール
│   ├── assets
│   └── tasks
├── log    ---- ログファイル
├── package.json    ---- npm依存関係の指定
├── public    ---- 静的ファイルやコンパイル済みアセットなど
│   ├── 404.html
│   ├── 422.html
│   ├── 500.html
│   ├── apple-touch-icon-precomposed.png
│   ├── apple-touch-icon.png
│   ├── favicon.ico
│   └── robots.txt
├── storage
├── test
│   ├── application_system_test_case.rb
│   ├── controllers
│   ├── fixtures
│   ├── helpers
│   ├── integration
│   ├── mailers
│   ├── models
│   ├── system
│   └── test_helper.rb
├── tmp    ---- キャッシュや一時ファイル
│   ├── cache
│   └── storage
└── vendor    ---- サードパーティーによって書かれたコード


*サーバーの起動

下記コマンドを実行するとサーバーが起動します。
$ rails server

下記にアクセスすると、サンプル画面が表示されます。
http://localhost:3000/














*Hello画面を作成

ジェネレータを使ってコントローラーを作成します。
コマンドはrails generate controller {コントローラー名} {アクション}として使います。Rails ではコントローラーで定義するメソッド名をアクションと呼びます。
$ rails generate controller Welcome index

コマンドを実行後、{コントローラー名}_controller.rbのファイルが生成されます。
<app/controllers/welcome_controller.rb>
class WelcomeController < ApplicationController
  def index
  end
end

views ディレクトリにはコントローラー名のディレクトリ配下に、{アクション名}.html.erbのファイルが生成されます。
<app/views/welcome/index.html.erb>
<h1>Welcome#index</h1>
<p>Find me in app/views/welcome/index.html.erb</p>

このファイルを下記に修正し、Hello, Rails!が画面に表示されるようにします。
<h1>Hello, Rails!</h1>

ルーティングを修正します。
rootを指定することで、welcomeコントローラーのindexアクションを実行するようにします。
<config/routes.rb>
Rails.application.routes.draw do
  get 'welcome/index'
  # ---- ↓追加 ----
  root 'welcome#index'
end

起動すると「Hello, Rails!」が表示されます。
$ rails server











*ブログアプリケーションの実装

ひとまとまりの単位をリソースと呼び、この単位ごとに CRUD の処理を実行します。ルーティングの設定にリソースを追加する必要があるので、今回は「記事」のリソースを追加しました。
<config/routes.rb>
Rails.application.routes.draw do
  get 'welcome/index'

  # ---- ↓追加 ----
  resources :articles

  root 'welcome#index'
end

Rails では標準的なルーティングが自動で定義されており、下記コマンドを実行して確認することができます。
$ rails routes

                   Prefix Verb   URI Pattern                                                                              Controller#Action
            welcome_index GET    /welcome/index(.:format)                                                                 welcome#index
                 articles GET    /articles(.:format)                                                                      articles#index
                          POST   /articles(.:format)                                                                      articles#create
              new_article GET    /articles/new(.:format)                                                                  articles#new
             edit_article GET    /articles/:id/edit(.:format)                                                             articles#edit
                  article GET    /articles/:id(.:format)                                                                  articles#show
                          PATCH  /articles/:id(.:format)                                                                  articles#update
                          PUT    /articles/:id(.:format)                                                                  articles#update
                          DELETE /articles/:id(.:format)                                                                  articles#destroy
                     root GET    /                                                                                        welcome#index
       rails_service_blob GET    /rails/active_storage/blobs/:signed_id/*filename(.:format)                               active_storage/blobs#show
rails_blob_representation GET    /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show
       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

先ほどのHelloのときと同様に、ジェネレータを使ってArticlesのコントローラーを作成します。
コントローラー名は複数形、頭文字を大文字にするという規則があります。
$ rails generate controller Articles

アクションを指定しなかったので、生成されたコントローラーには何も定義されていない状態になりますが、このままだとエラーになるのでnewアクションを追加します。
<app/controllers/articles_controller.rb>
class ArticlesController < ApplicationController
  def new
  end
end

このままだとviewファイルがなくてエラーになるので、new.html.erbを新規作成します。
コントローラー作成時にアクションを指定すると自動生成されるので、本来であればrails generate controller Articles newとしてアクションを指定したほうが良さそうです。
<app/views/articles/new.html.erb>
<h1>New  Article</h1>

ブラウザに下記が表示されます。











*入力フォームの作成

記事を投稿するためのフォームを作成します。
先ほど作成したviewファイルに下記を追加します。
<% ~ %>または<%= ~ %>の中に記述されたコードは Ruby のコードとして実行されます。
<%= ~ %>は実行された結果を評価し、文字列として出力します。いっぽう<% ~ %>は、結果を出力しない計算や繰り返し処理を記述するときに使います。
  • form_with ヘルパー
    Viewファイルでform_withというヘルパーを使うことで、フォームを簡単に作成することができます。オプションを指定することができ、今回はscope:url:local:を指定しています。scope:にはルーティングで名前空間を指定したときのプレフィックスを、url:にはフォームに入力されたデータを送信するURLを、local:にはリモートフォームを使わない設定を指定します。
  • path ヘルパー
    rails routeコマンドを実行すると表示されるプレフィックス名_path と定義することで、URL用のパスを生成してくれます。articles_pathとすると、/articlesを返します。
<app/views/articles/new.html.erb>
<h1>New Article</h1>
<%= form_with scope: :article, url: articles_path, local: true do |form| %>
  <p>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
  </p>
  <p>
    <%= form.label :text %><br>
    <%= form.text_area :text %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>  

POSTリクエストとしてルーティングされるので、コントローラーにcreateアクションを作成する必要があります。現時点では試しにパラメーターが正常に渡っているかブラウザに表示して確認をします。inspectを使うとPOSTの値を確認することができます。
  • render
    plain:オプションを指定することで、平文をマークアップせずにブラウザに送信することができます。
<app/controllers/articles_controller.rb>
  #  ---- ↓追加 ----
  def create
    render plain: params[:article].inspect
  end

ブラウザにPOSTの値が表示されます。
下記になっていれば正常に渡されています。
<ActionController::Parameters {"title"=>"test", "text"=>"aaa"} permitted: false>


*モデルの作成

ジェネレータを使ってモデルを作成します。
rails generate model {モデル名} {カラム名:型}として実行します。モデル名は単数形で頭文字を大文字するという命名規則があります。
$ rails generate model Article title:string text:text

下記ファイルが生成されます。
<app/models/article.rb>
class Article < ApplicationRecord
end

<db/migrate/20190731111353_create_articles.rb>
class CreateArticles < ActiveRecord::Migration[5.2]
  def change
    create_table :articles do |t|
      t.string :title
      t.text :text

      t.timestamps
    end
  end
end

マイグレーションを実行します。
Articlesテーブルがデータベース上に生成されます。
$ rails db:migrate

== 20190731111353 CreateArticles: migrating ===================================
-- create_table(:articles)
   -> 0.0023s
== 20190731111353 CreateArticles: migrated (0.0023s) ==========================


*投稿データの保存

コントローラーにデータを保存するためのcreateアクションと、保存されたデータを表示するためのshowアクションを追加します。
  • @
    @articleはインスタンス変数です。コントローラーのインスタンス変数はViewファイルから参照することができます。
  • find()
    指定したモデルのインスタンスを返します。@article.titleとすることで、データの値を使うことができます。指定したIDがわかっている場合はfind()を使いますが、特定カラムの値から取得したい場合はfind_by()を使います。
  • new()
    属性を渡すとモデルオブジェクトを生成することができます。作成したインスタンスでsaveを実行するとデータを保存することができます。
  • params
    URLから送られてきた値やフォームの入力値を受け渡しするためのメソッドです。params[:パラメータ名]として使います。
<app/controllers/articles_controller.rb>
class ArticlesController < ApplicationController
  # ---- ↓追加 ----
  def show
    @article = Article.find(params[:id])
  end
  
  def new
  end
  
  # ---- ↓追加 ----
  def create
    @article = Article.new(article_params)
    @article.save
    redirect_to @article
  end

  # ---- ↓追加 ----
  private
    def article_params
      params.require(:article).permit(:title, :text)
    end
end

表示用のViewファイルを新規作成します。
コントローラーのインスタンス変数からタイトルとテキストを表示しています。
<app/views/articles/show.html.erb>
<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>


*記事一覧を表示

既に定義されているルーティングを使い、GETするためにindexアクションを追加します。
allを使うことでモデルの中身全てを取得することができます。
<app/controllers/articles_controller.rb>
class ArticlesController < ApplicationController
  # ---- ↓追加 ----
  def index
    @articles = Article.all
  end
  def show
    @article = Article.find(params[:id])
  end
  def new
  end
  def create
    @article = Article.new(article_params)

    @article.save
    redirect_to @article
  end

  private
    def article_params
      params.require(:article).permit(:title, :text)
    end
end

コントローラーに対応するViewファイルを作成します。
@articlesのインスタンス変数を使って表示します。
<app/views/articles/index.html.erb>
<h1>Listing articles</h1>
<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
    <th colspan="3"></th>
  </tr>
  <% @articles.each do |article| %>
    <tr>
      <td><%= article.title %></td>
      <td><%= article.text %></td>
      <td><%= link_to 'Show', article_path(article) %></td>
    </tr>
  <% end %>
</table>










*リンクの追加

最初の Hello のページからブログ一覧画面へ遷移するためのリンクを作成します。
  • link_to
    Viewファイルでリンクを表示するためのヘルパーメソッドです。link_to 'リンク表示の文字列', リンク先のURLとして使います。
<app/views/welcome/index.html.erb>
<h1>Hello, Rails!</h1>
# ---- ↓追加 ----
<%= link_to 'My Blog', controller: 'articles' %>
http://localhost:3000








一覧画面から投稿新規作成画面に遷移するリンクを作成します。ここでも_pathヘルパーを使っています。
  • path ヘルパー
    rails routeコマンドを実行すると表示されるプレフィックス名_path と定義することで、URL用のパスを生成してくれます。new_article_pathとすると、/articles/newを返します。
<app/views/articles/index.html.erb>
<h1>Listing articles</h1>
# ---- ↓追加 ----
<%= link_to 'New article', new_article_path %>
<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
    <th colspan="3"></th>
  </tr>
  <% @articles.each do |article| %>
    <tr>
      <td><%= article.title %></td>
      <td><%= article.text %></td>
      <td><%= link_to 'Show', article_path(article) %></td>
    </tr>
  <% end %>
</table>









投稿作成画面から一覧画面に戻るリンクを作成します。
<app/views/articles/new.html.erb>
<h1>New Article</h1>
<%= form_with scope: :article, url: articles_path, local: true do |form| %>
  <p>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
  </p>
  <p>
    <%= form.label :text %><br>
    <%= form.text_area :text %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>
# ---- ↓追加 ----
<%= link_to 'Back', articles_path %>















詳細表示画面から一覧画面に戻るリンクを作成します。
<app/views/articles/show.html.erb>
<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>
# ---- ↓追加 ----
<%= link_to 'Back', articles_path %>



*バリデーションの追加

モデルファイルにvalidatesを設定するとバリデーションを追加することができます。presence: trueとすることで、値が空でないことを確認しています。
<app/models/article.rb>
class Article < ApplicationRecord
  validates :title, presence: true, length: { minimum: 5 }
end

バリデーションエラーになるとsaveをしたときにfalseを返すようになるので、コントローラーのcreateアクションに条件を追加します。バリデーションエラー時はrender 'new'で Viewファイルのnewテンプレートを呼び出し、新規作成画面を再表示します。保存に成功した場合は、一覧画面に遷移します。
  • redirect_to
    インスタンス変数を指定すると、インスタンス変数名のshowアクションを呼び出します。
<app/controllers/articles_controller.rb>
class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
  end

  # ---- ↓追加 ----
  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article
    else
      render 'new'
    end
  end

  private
    def article_params
      params.require(:article).permit(:title, :text)
    end
end

バリデーションエラーが発生した場合は、原因がわかるように画面に内容を表示するようにします。
  • errors.any?
    @article.errors.any?とすることでエラーが発生しているかの判定をすることができます。
  • pluralize
    数値を受け取って、それに応じて単数形/複数形に対応した名詞に変換します。
<app/views/articles/new.html.erb>
<h1>New Article</h1>
<%= form_with scope: :article, url: articles_path, local: true do |form| %>
  # ---- ↓修正 start ----
  <% if @article.errors.any? %>
  <div id="error_explanation">
    <h2><%= pluralize(@article.errors.count, "error") %>
      prohibited this article from being saved:
    </h2>
    <ul>
    <% @article.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
  <% end %>
  # ---- ↑修正 end ----
  <p>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
  </p>
  <p>
    <%= form.label :text %><br>
    <%= form.text_area :text %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>
<%= link_to 'Back', articles_path %>













*更新機能の追加

更新するためにはeditアクションを追加します。
更新後のフォームを送信するためにはupdateアクションを追加する必要があります。
<app/controllers/articles_controller.rb>
class ArticlesController < ApplicationController
  ...

  # ---- ↓追加 ----
  def edit
    @article = Article.find(params[:id])
  end

  def update
    @article = Article.find(params[:id])
    if @article.update(article_params)
      redirect_to @article
    else
      render 'edit'
    end
  end

  private
    def article_params
      params.require(:article).permit(:title, :text)
    end
end

editの View ファイルは新規作成の入力フォームと同じなので、共通化します。View ファイルに_form.html.erbを新規作成し、入力フォームの部分を移植します。
<app/views/articles/_form.html.erb>
<%= form_with(model: @article, local: true) do |form| %>
  <% if @article.errors.any? %>
  <div id="error_explanation">
    <h2><%= pluralize(@article.errors.count, "error") %>
      prohibited this article from being saved:
    </h2>
    <ul>
    <% @article.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
  <% end %>
  <p>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
  </p>
  <p>
    <%= form.label :text %><br>
    <%= form.text_area :text %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>

new.html.erbを下記に修正します。
renderで表示するビューを指定します。
<app/views/articles/new.html.erb>
<h1>New Article</h1>

<%= render 'form' %>

<%= link_to 'Back', articles_path %>

edit.html.erbを新規作成します。
<app/views/articles/edit.html.erb>
<h1>Edit article</h1>

<%= render 'form' %>

<%= link_to 'Back', articles_path %>















一覧画面に、記事を更新するための編集画面へのリンクを追加します。
<app/views/articles/index.html.erb>
<h1>Listing articles</h1>
<%= link_to 'New article', new_article_path %>
<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
    <th colspan="2"></th>
  </tr>
  <% @articles.each do |article| %>
    <tr>
      <td><%= article.title %></td>
      <td><%= article.text %></td>
      <td><%= link_to 'Show', article_path(article) %></td>
      # ---- ↓追加 ----
      <td><%= link_to 'Edit', edit_article_path(article) %></td>
    </tr>
  <% end %>
</table>











詳細表示画面にも、編集画面へのリンクを追加します。
edit_article_path(@article)とすることで、View からコントローラーに値を渡すことができます。
<app/views/articles/show.html.erb>
<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>
<%= link_to 'Back', articles_path %>
<%= link_to 'Edit', edit_article_path(@article) %>












*削除機能の追加

削除するためにはコントローラーにdestroyアクションを追加します。削除後は一覧画面を再表示します。
  • destroy
    インスタンス変数に対してdestroyをすると、データを1件削除することができます。関連データも削除します。
<app/controllers/articles_controller.rb>
class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article
    else
      render 'new'
    end
  end

  def edit
    @article = Article.find(params[:id])
  end

  def update
    @article = Article.find(params[:id])
    if @article.update(article_params)
      redirect_to @article
    else
      render 'edit'
    end
  end

  # ---- ↓追加 ----
  def destroy
    @article = Article.find(params[:id])
    @article.destroy

    redirect_to articles_path
  end

  private
    def article_params
      params.require(:article).permit(:title, :text)
    end
end

一覧画面に削除するためのリンクを追加します。
data-confirmを使って本当に削除して良いかの確認をするようにしています。
  • data-confirm
    data: {confirm: "{メッセージ}"}を指定することで、指定したメッセージをブラウザのダイアログで表示させることができます。
<app/views/articles/index.html.erb>
<h1>Listing articles</h1>
<%= link_to 'New article', new_article_path %>
<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
    <th colspan="3"></th>
  </tr>
  <% @articles.each do |article| %>
    <tr>
      <td><%= article.title %></td>
      <td><%= article.text %></td>
      <td><%= link_to 'Show', article_path(article) %></td>
      <td><%= link_to 'Edit', edit_article_path(article) %></td>
      # ---- ↓追加 ----
      <td><%= link_to 'Destroy', article_path(article), 
        method: :delete, data: { confirm: 'Are you sure?' } %></td>
    </tr>
  <% end %>
</table>















*コメント機能の追加

記事に対してコメントを追加できるようにします。
記事のモデル作成時と同様に、ジェネレータを使ってコメントモデルを作成します。記事モデルと関連付ける必要があるため、referencesで外部キーを指定しています。
$ rails generate model Comment commenter:string body:text article:references

Running via Spring preloader in process 58630
      invoke  active_record
      create    db/migrate/20190803104946_create_comments.rb
      create    app/models/comment.rb
      invoke    test_unit
      create      test/models/comment_test.rb
      create      test/fixtures/comments.yml

マイグレーションを実行します。
$ rails db:migrate

== 20190803104946 CreateComments: migrating ===================================
-- create_table(:comments)
   -> 0.0065s
== 20190803104946 CreateComments: migrated (0.0068s) ==========================

記事モデルにもコメントモデルとの関連付けを指定するため、既存のarticle.rbhas_many :commentsを追加します。
<app/models/article.rb>
class Article < ApplicationRecord
  has_many :comments
  validates :title, presence: true, length: { minimum: 5 }
end

articlesのネストされたリソースとして、commentsのルーティングを追加します。
do ... endはブロック処理です。
<config/routes.rb>
Rails.application.routes.draw do
  get 'welcome/index'

  resources :articles do
    resources :comments
  end
  
  root 'welcome#index'
end

ジェネレータを使ってCommentsのコントローラーを生成します。
$ rails generate controller Comments

Running via Spring preloader in process 58861
      create  app/controllers/comments_controller.rb
      invoke  erb
      create    app/views/comments
      invoke  test_unit
      create    test/controllers/comments_controller_test.rb
      invoke  helper
      create    app/helpers/comments_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/comments.coffee
      invoke    scss
      create      app/assets/stylesheets/comments.scss

詳細表示画面に、コメントの入力フォームを追加します。
  • build
    親モデルに対する外部参照キーを自動でセットすることができます。
<app/views/articles/show.html.erb>
<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

# ---- ↓追加 start ----
<h2>Comments</h2>
<% @article.comments.each do |comment| %>
  <p>
    <strong>Commenter:</strong>
    <%= comment.commenter %>
  </p>
  <p>
    <strong>Comment:</strong>
    <%= comment.body %>
  </p>
<% end %>

<h2>Add a comment:</h2>
<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %>
  <p>
    <%= form.label :commenter %><br>
    <%= form.text_field :commenter %>
  </p>
  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>
# ---- ↑追加 end ----

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

コントローラーにコメントを登録するためのcreateアクションを追加します。
<app/controllers/comments_controller.rb>
class CommentsController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end
  
  private
    def comment_params
      params.require(:comment).permit(:commenter, :body)
    end
end

コメント表示部分を共通化します。
View ファイルに_comment.html.erbを新規作成します。
ファイル名と同じcommentをローカル変数として使うことができます。
<app/views/comments/_comment.html.erb>
<p>
  <strong>Commenter:</strong>
  <%= comment.commenter %>
</p>
<p>
  <strong>Comment:</strong>
  <%= comment.body %>
</p>

詳細表示画面でコメントを表示させるように修正します。
render@article.commentsを指定することで、_comment.html.erbに値を渡すことができます。
<app/views/articles/show.html.erb>
<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

<h2>Comments</h2>
# ---- ↓修正 ----
<%= render @article.comments %>

<h2>Add a comment:</h2>
<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %>
  <p>
    <%= form.label :commenter %><br>
    <%= form.text_field :commenter %>
  </p>
  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>

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

コメントの入力フォーム部分も共通化します。
_form.html.erbを新規作成します。
<app/views/comments/_form.html.erb>
<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %>
  <p>
    <%= form.label :commenter %><br>
    <%= form.text_field :commenter %>
  </p>
  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>

詳細表示画面で、_form.html.erbを呼び出すよう修正します。
<app/views/articles/show.html.erb>
<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

<h2>Comments</h2>
<%= render @article.comments %>

<h2>Add a comment:</h2>
# ---- ↓修正 ----
<%= render "comments/form" %>

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
















*コメント削除機能の追加

コメント表示部分に削除するためのリンクを追加します。
<app/views/comments/_comment.html.erb>
<p>
  <strong>Commenter:</strong>
  <%= comment.commenter %>
</p>
<p>
  <strong>Comment:</strong>
  <%= comment.body %>
</p>

# ---- ↓追加 ----
<p>
  <%= link_to 'Destroy Comment', [comment.article, comment], 
    method: :delete, data: { confirm: 'Are you sure?' } %>
</p>

コントローラーにdestroyアクションを追加します。
<app/controllers/comments_controller.rb>
class CommentsController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end

  # ---- ↓追加 ----
  def destroy
    @article = Article.find(params[:article_id])
    @comment = @article.comments.find(params[:id])
    @comment.destroy
    redirect_to article_path(@article)
  end
  
  private
    def comment_params
      params.require(:comment).permit(:commenter, :body)
    end
end

関連データを削除する必要があるため、article.rbdependent: :destroyを追加します。
<app/models/article.rb>
class Article < ApplicationRecord
  has_many :comments, dependent: :destroy
  validates :title, presence: true, length: { minimum: 5 }
end

























*Basic認証の追加

http_basic_authenticate_withを使うことで、簡単にHTTP認証を実装することができます。exceptを指定することで、認証しないアクションを指定することができます。
コントローラーに追加し、表示以外は認証するようにします。
<app/controllers/articles_controller.rb>
class ArticlesController < ApplicationController

  # ---- ↓追加 ----
  http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]
  
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article
    else
      render 'new'
    end
  end

  def edit
    @article = Article.find(params[:id])
  end

  def update
    @article = Article.find(params[:id])
    if @article.update(article_params)
      redirect_to @article
    else
      render 'edit'
    end
  end

  def destroy
    @article = Article.find(params[:id])
    @article.destroy

    redirect_to articles_path
  end

  private
    def article_params
      params.require(:article).permit(:title, :text)
    end
end

コメントは削除のときのみに認証をつけます。
<app/controllers/comments_controller.rb>
class CommentsController < ApplicationController

  # ---- ↓追加 ----
  http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy

  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end

  def destroy
    @article = Article.find(params[:article_id])
    @comment = @article.comments.find(params[:id])
    @comment.destroy
    redirect_to article_path(@article)
  end
  
  private
    def comment_params
      params.require(:comment).permit(:commenter, :body)
    end
end











*所感

Java や Python を経験した私にとって、Rails はこう書けばこう動くといったお作法が非常に多くて理解するのが大変でした。チュートリアルだけだとわからない部分が多く、記法などを調べながらやることで理解できました。
覚えれば速く開発できそうですが、きちんと理解しないと影響範囲がわかりづらく修正漏れが発生しそうです。初心者向きとの噂も聞いたことがありますが、きちんと理解して使わないと危ない言語だと思いました。
Rails は黒魔術感がすごいとは聞いていましたが、実際に書いてみて納得しました。

Previous
Next Post »

人気の投稿