Getting Started with Rails でざっくりと遊んでみた話

# 概要
Windows 10 の端末を使って，Ruby on Rails Guides にある [Getting Started with Rails](https://guides.rubyonrails.org/getting_started.html) の3.2節から8.1節まで[^1]を，細かい説明は飛ばしてざっくりやってみた．内容は，Rails を用いて簡単なブログを作成する，というものである．
[^1]: 一部飛ばしている節もある．

# 詳細
以下，Getting Started with Rails の3.1節（Rails のインストール）までは終わっているものとして進める．また，行頭が `>` で始まるコードはコマンドプロンプトでの操作を表す．
## アプリケーションの作成
`rails new` コマンドでアプリケーションの型を作成．

```
>rails new DenkiuoBlog
```
終わったらできたフォルダ `DenkiuoBlog` 内に移動する．

```
>cd DenkiuoBlog
```
後のために，コマンドプロンプトをもう一つ開き，フォルダ `DenkiuoBlog` に移動して

```
>rails s
```
を実行しておく．こうすることで実際に作成したものをブラウザ上で閲覧できるようになる．
## コントローラーの作成
コントローラー `articles` を作成．

```
>rails g controller articles
```
ルートの設定．`DenkiuoBlog\config\routes.rb` を次のように書き換える．

```ruby:routes.rb
Rails.application.routes.draw do
  resources :articles
end
```
## アクションの作成
コントローラー `DenkiuoBlog\app\controllers\articles_controller.rb` に `new` アクションを追加．これは後々新しい記事を投稿するものとして機能する．

```ruby:articles_controller.rb
class ArticlesController < ApplicationController
    def new
    end
end
```
`new` アクションに対応するページ `DenkiuoBlog\app\views\articles\new.html.erb` を作成．ひとまず見出しだけつけておく．

```erb:new.html.erb
<h1>新しく投稿する</h1>
```
この状態で http://localhost:3000/articles/new にアクセスすると
>新しく投稿する

とだけ書かれたページが出現する．
次に，このページで記事を書けるようにフォームを作成．そのためには `new.html.erb` を次のように書き換える．

```erb:new.html.erb
<h1>新しく投稿する</h1>

<%= form_with scope: :article, local: true do |form| %>
    <p>
      <%= form.label :タイトル %><br>
      <%= form.text_field :title %>
    </p>
   
    <p>
      <%= form.label :本文 %><br>
      <%= form.text_area :text %>
    </p>
   
    <p>
      <%= form.submit "投稿" %>
    </p>
<% end %>
```
再び http://localhost:3000/articles/new にアクセスして

- タイトル入力フォーム
- 本文入力フォーム
- 投稿ボタン

が追加されていることを確認．
フォームの投稿を実現するためには，投稿ボタンを押したとき別のページに遷移しなければならない．これは `new.html.erb` 内の `form_with` の `url` オプションにより実現される．そこで，該当箇所 `<%= form_with scope: :article, local: true do |form| %>` に `url` オプションを記述する．

```erb:new.html.erb
<h1>新しく投稿する</h1>

<%= form_with scope: :article, url: articles_path, local: true do |form| %>
    <p>
...
```
## モデルとデータベースの作成
投稿ボタンを押したときの他の挙動（例えば，入力したデータの送信など）は `create` アクションで行われる．ここからはデータのやり取りを実現するための準備をする．
記事投稿のモデルを作成する．

```
>rails g model article title:string text:text
```
上記でできたモデルを基にデータベースを作成する．

```
>rails db:migrate
```
## ブログの各ページの成形
`articles_controller.rb` に `create` アクションを書き加える．さらに `show` アクションも加えておく．

```ruby:articles_controller.rb
class ArticlesController < ApplicationController
    def new
    end

    def create
        @article = Article.new(article_params)

        @article.save
        redirect_to @article
    end

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

    private
        def article_params
            params.require(:article).permit(:title, :text)
        end
end
```
`show` アクションに対応する `DenkiuoBlog\app\views\articles\show.html.erb` を作成．

```erb:show.html.erb
<p>
  <strong>タイトル:</strong>
  <%= @article.title %>
</p>
 
<p>
  <strong>本文:</strong>
  <%= @article.text %>
</p>
```
Index ページを作成する．`articles_controller.rb` に `index` アクションを追加．

```ruby:articles_controller.rb
class ArticlesController < ApplicationController
    def index
        @articles = Article.all
    end
...
```
さらに `DenkiuoBlog\app\views\articles\index.html.erb` を作成．

```erb:index.html.erb
<h1>投稿記事一覧</h1>
 
<table>
  <tr>
    <th>タイトル</th>
    <th>本文</th>
    <th></th>
  </tr>
 
  <% @articles.each do |article| %>
    <tr>
      <td><%= article.title %></td>
      <td><%= article.text %></td>
      <td><%= link_to '詳細', article_path(article) %></td>
    </tr>
  <% end %>
</table>
```
`index.html.erb` `show.html.erb` `new.html.erb` にリンクを張る．

```erb:index.html.erb
<h1>投稿記事一覧</h1>
<%= link_to '記事作成', new_article_path %>
...
```
```erb:show.html.erb
... 
<p>
  <strong>本文:</strong>
  <%= @article.text %>
</p>

<%= link_to '戻る', articles_path %>
```
```erb:new.html.erb
...
<% end %>

<%= link_to '戻る', articles_path %>
```
以上でブログの大枠は完成．試しに記事の投稿をしてみよう．
http://localhost:3000/articles にアクセスする．これは `index.html.erb` に対応するページである．新規投稿というリンクをクリックすると `new.html.erb` に対応するページに移動する．タイトルと本文を適当に入れて，投稿ボタンを押す．すると，いま投稿した記事のタイトルと本文が表示されるページに移動する．これが `show.html.erb` に対応するページである．戻るボタンで最初のページの戻ると，記事一覧にいま投稿したものが追加されている．

次からは，ブログに必要と思われる様々な機能を追加していく．

## 投稿内容制限の実装
現段階では，タイトルや本文が空欄でも投稿できてしまう．そこで，投稿内容に条件を設ける．`DenkiuoBlog\app\models\article.rb` を次のように編集する．

```ruby:article.rb
class Article < ApplicationRecord
    validates :title, presence: true, length: { minimum: 5 }
    validates :text, presence: true
end
```
これでタイトルも本文も空欄にはできなくなり，さらにタイトルは5文字以上でなければならない制約がつけられた．次に，これらの制約により投稿ができなかった際にエラーを表示できるようにしよう．まず，`articles_controller.rb` の `new` アクションと `create` アクションを次のように書き換える．

```ruby:articles_controller.rb
...
    def new
        @article = Article.new
    end

    def create
        @article = Article.new(article_params)

        if @article.save
			redirect_to @article
		else
			render 'new'
		end
    end
...
```
そして，`new.html.erb` にエラーを表示するためのコードを追加する．

```erb:new.html.erb
...
<%= form_with scope: :article, url: articles_path, local: true do |form| %>
    <% if @article.errors.any? %>
        <div id="error_explanation">
            <h2>投稿内容に<%= @article.errors.count %>個のエラーがあります:</h2>
            <ul>
                <% @article.errors.full_messages.each do |msg| %>
                <li><%= msg %></li>
                <% end %>
            </ul>
        </div>
    <% end %>
...
```
これでエラー表示（ただしメッセージは英語）が実装された．

## 記事編集機能の実装
`articles_controller.rb` に `edit` アクションを追加．

```ruby:articles_controller.rb
...
    def new
        @article = Article.new
    end

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

    def create
        @article = Article.new(article_params)
...
```
`edit` アクションに対応する `DenkiuoBlog\app\views\articles\edit.html.erb` を作成．

```erb:edit.html.erb
<h1>投稿を編集する</h1>

<%= form_with(model: @article, local: true) do |form| %>
	<% if @article.errors.any? %>
		<div id="error_explanation">
		<h2>編集された内容に<%= @article.errors.count %>つのエラーがあります:</h2>
			<ul>
				<% @article.errors.full_messages.each do |msg| %>
				<li><%= msg %></li>
				<% end %>
			</ul>
		</div>
	<% end %>

    <p>
      <%= form.label :タイトル %><br>
      <%= form.text_field :title %>
    </p>

    <p>
      <%= form.label :本文 %><br>
      <%= form.text_area :text %>
    </p>

    <p>
      <%= form.submit "更新" %>
    </p>
<% end %>

<%= link_to '戻る', articles_path %>
```
再び `articles_controller.rb` に戻り `update` アクションを追加．

```ruby:articles_controller.rb
...
    def update
        @article = Article.find(params[:id])

        if @article.update(article_params)
            redirect_to @article
        else
            render 'edit'
        end
    end
...
```
`index.html.erb` を次のように変更．

```erb:index.html.erb
...
<td><%= link_to '詳細', article_path(article) %></td>
<td><%= link_to '編集', edit_article_path(article) %></td>
...
```
`show.html.erb` を次のように変更．

```erb:show.html.erb
...
</p>

<%= link_to '編集', edit_article_path(@article) %> |
<%= link_to '戻る', articles_path %>
```
これで編集ボタンが追加された．

## 記事削除機能の実装
`articles_controller.rb` に `destroy` アクションを追加．

```ruby:articles_controller.rb
...
    def show
        @article = Article.find(params[:id])
    end
	
    def destroy
        @article = Article.find(params[:id])
        @article.destroy

        redirect_to articles_path
    end
...
```
Index ページに記事削除のためのリンクを追加．

```erb:index.html.erb
...
<td><%= link_to '詳細', article_path(article) %></td>
<td><%= link_to '編集', edit_article_path(article) %></td>
<td><%= link_to '削除', article_path(article), method: :delete, data: { confirm: '削除してもよろしいですか？' } %></td>
...
```
http://localhost:3000/articles を見てみると，削除ボタンが追加されているはずである．

## コメント機能の実装
コメント投稿のモデルを作成する．

```
>rails g model comment commenter:string body:text article:references
```
上記を基にデータベースを作成する．

```
>rails db:migrate
```
`DenkiuoBlog\app\models\article.rb` に `has_many :comments` を追加する．

```ruby:article.rb
class Article < ApplicationRecord
    has_many :comments
    validates :title, presence: true, length: { minimum: 5 }
    validates :text, presence: true
end
```
`DenkiuoBlog\config\routes.rb` を次のように書き換える．

```ruby:routes.rb
Rails.application.routes.draw do
    resources :articles do
        resources :comments
    end
end
```
コメント投稿のコントローラーを作成する．

```
>rails g controller comments
```
`DenkiuoBlog\app\views\articles\show.html.erb` を次のように書き換える．

```erb:show.html.erb
...
<h2>コメントを追加する:</h2>
<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %>
  <p>
    <%= form.label :コメント者 %><br>
    <%= form.text_field :commenter %>
  </p>
  <p>
    <%= form.label :コメント %><br>
    <%= form.text_area :body %>
  </p>
  <p>
    <%= form.submit '追加' %>
  </p>
<% end %>

<%= link_to '編集', edit_article_path(@article) %> |
<%= link_to '戻る', articles_path %>
```
`DenkiuoBlog\app\controllers\comments_controller.rb` を次のように編集する．

```ruby: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
```
再び `show.html.erb` に戻り，コメントを表示・削除できるように書き換える．

```erb:show.html.erb
...
<p>
  <strong>本文:</strong>
  <%= @article.text %>
</p>

<h2>コメント一覧</h2>
<% @article.comments.each do |comment| %>
  <p>
    <strong>コメント者:</strong>
    <%= comment.commenter %>
  </p>
 
  <p>
    <strong>コメント:</strong>
    <%= comment.body %>
  </p>

  <p>
    <%= link_to 'コメントを削除', [comment.article, comment], method: :delete, data: { confirm: '削除してもよろしいですか？' } %>
  </p>
<% end %>

<h2>コメントを追加する:</h2>
...
```
コメント削除機能を完成させるため，`comments_controller.rb` に `destroy` アクションを追加する．

```ruby:comments_controller.rb
...
    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
...
```
投稿が削除されたときはそれに付いたコメントも同時に削除されねばならない．そのために，`DenkiuoBlog\app\models\article.rb` に `dependent: :destroy` を追加する．

```ruby:article.rb
class Article < ApplicationRecord
    has_many :comments, dependent: :destroy
    validates :title, presence: true, length: { minimum: 5 }
    validates :text, presence: true
end
```
これでコメント機能は完成だが，コメント者やコメント欄が空欄でも追加できてしまうので，`DenkiuoBlog\app\models\comment.rb` を次のように書き換える[^2]．これで空欄の場合はコメントが追加できなくなる．

```ruby:comment.rb
class Comment < ApplicationRecord
    belongs_to :article
    validates :commenter, presence: true
    validates :body, presence: true
end
```
[^2]: この書き換えや実装は Getting Started with Rails には載っていない．
また，コメントが追加できなかったときのエラー表示は次のように実装できる[^2]．`DenkiuoBlog\app\views\comments\error.html.erb` を以下の内容で作成する．

```erb:error.html.erb
<% if @comment.errors.any? %>
    <div id="error_explanation">
        <h2>コメントの内容に<%= @comment.errors.count %>つのエラーがあります:</h2>
            <ul>
                <% @comment.errors.full_messages.each do |msg| %>
                    <li><%= msg %></li>
                <% end %>
            </ul>
    </div>
<% end %>

<%= link_to '戻る', article_path(@article) %>
```
そして，`DenkiuoBlog\app\controllers\comments_controller.rb` の `create` アクションを次のように書き換える．

```ruby:comments_controller.rb
class CommentsController < ApplicationController
    def create
        @article = Article.find(params[:article_id])
        @comment = @article.comments.create(comment_params)
        
        if @comment.save
            redirect_to article_path(@article)
        else
            render 'error'
        end
    end
...
```

# 参考
- [Getting Started with Rails — Ruby on Rails Guides](https://guides.rubyonrails.org/getting_started.html)