基礎Ruby on Rails Chapter10 モデル間の関連付け／ブログ機能の追加（コントローラ／ビュー編）

前 [基礎Ruby on Rails Chapter10 モデル間の関連付け／ブログ機能の追加（モデル編）](https://qiita.com/tseno/items/a353ff0b9d5c707e8cae)
次 [基礎Ruby on Rails Chapter11 ストロング・パラメータ／エラーページのカスタマイズ](https://qiita.com/tseno/items/70d39f7c0e8e1c779b5e)

# 会員ブログ機能の実装

## ネストされたリソース

### ネストされたリソースのルーティング

- Taroさんのブログ記事のようなページを作成するには、**ネストされたリソース**を設定する。
- MemberとEntryのように、一対多で関連付けられているモデルをコントローラで扱うときは便利。
- entriesは、membersの入れ子になっている。

```rb
resources :members do
  resources :entries
end
```

|アクション|パス|HTTPメソッド|パスを返すメソッド|パスを表す配列|
|:--|:--|:--|:--|:--|
|index|/members/123/entries|GET|member_entries_path(@member)|[@member,:entries]|
|show|/members/123/entries/123|GET|member_entry_path(@member,@entry)|[@member,@entry]|
|new|/members/123/entries/new|GET|new_member_entries_path(@member)|[:new,@member,:entry]|
|edit|/members/123/entries/456/edit|GET|edit_member_entries_path(@member,@entry)|[:edit,@member,@entry]|
|create|/members/123/entries|POST|member_entries_path(@member)|[@member,:entries]|
|update|/members/123/entries/456|PATCH|member_entry_path(@member,@entry)|[@member,@entry]|
|destroy|/members/123/entries/456|DELETE|member_entry_path(@member,@entry)|[@member,@entry]|


- 会員@memberの記事一覧（index）と、編集（edit）のリンクは次のように作れる。

```erb
<%= link_to "記事一覧",[@member,:entries] %>

<%= link_to "記事の編集",[:edit,@member,@entry] %>
```

### ルーティングの設定

- 会員ごとの記事一覧と全体の一覧ページを作るので、`resources :entries, only: [:index]`、`resources :entries`を追加する。
- `/members/123/entries`と`/entries`が使えるようになる。

```rb:config/routes.rb（一部）
  resources :members do
    get "search", on: :collection
    resources :entries, only: [:index]
  end
#（省略）
  resources :entries
```

## ブログ記事の一覧と表示

### EntriesControllerの作成

- `bin/rails g`コマンドで、EntriesControllerを作成する。4つのアクションのテンプレートも作成する。

```sh
$ bin/rails g controller entries index show new edit
      create  app/controllers/entries_controller.rb
      invoke  erb
      create    app/views/entries
      create    app/views/entries/index.html.erb
      create    app/views/entries/show.html.erb
      create    app/views/entries/new.html.erb
      create    app/views/entries/edit.html.erb
```

- indexとshowのコントローラを実装する。

```rb:app/controllers/entries_controller.rb（一部）
  def index
    if params[:member_id]
      # 会員IDがある場合は、その会員のブログ記事一覧
      @member = Member.find(params[:member_id])
      @entries = @member.entries
    else
      # 会員IDが無い場合は、全員のブログ記事一覧
      @entries = Entry.all
    end

    # ログイン中の会員が見られる記事に絞る。ページネーションも追加。
    @entries = @entries.readable_for(current_member).order(posted_at: :desc).page(params[:page]).per(3)

  end

  def show
    # ログイン中の会員が見られる記事のみ。idはブログ記事の主キー
    @entry = Entry.readable_for(current_member).find(params[:id])
  end
```

### 記事の一覧表示

- 一覧表示のビューを作成する。
- member指定かどうかで、タイトルを変える。
- 新規作成（new）、もっと読む（show）を追加する。

```erb:app/views/entries/index.html.erb
<% @page_title = @member ? @member.name + "さんのブログ" : "会員のブログ" %>
<h1><%= @page_title %></h1>

<% if current_member %>
  <div class="toolbar"><%= link_to "ブログ記事の作成", :new_entry %></div>
<% end %>

<% if @entries.present? %>
  <% @entries.each do |entry| %>
    <h2><%= entry.title %></h2>
    <p><%= truncate(entry.body, length: 80) %><%= link_to "もっと読む", entry %></p>
    <%= render "footer", entry: entry %>
  <% end %>
  <%= paginate @entries %>
<% else %>
    <p>記事がありません。</p>
<% end %>

```

- フッター用の部分テンプレートを作成する。
- 編集（edit）、削除（deatroy）を追加する。

```erb:app/views/entries/_footer.html.erb
<ul class="entry-footer">
  <% if current_member %>
    <li><%= Entry.status_text(entry.status) %></li>
    <% if current_member == entry.author %>
      <%= menu_link_to "編集", [:edit, entry] %>
      <%= menu_link_to "削除", entry, method: :delete, data: { confirm: "本当に削除しますか？" } %>
    <% end %>
  <% end %>
  <li>
    by<%= link_to entry.author.name, [entry.author, :entries] %>
  </li>
  <li>
    <%= entry.posted_at.strftime("%Y/%m/%d %H:%M") %>
  </li>
</ul>
```

- ヘルパーメソッド`menu_link_to`を修正する。

```rb
  # 第3引数は省略可
  def menu_link_to(text, path, options={})
    content_tag :li do
      
      # methodが設定されているか、現在と同じリンクではない場合、リンクを有効にする。
      condition = options[:method] || !current_page?(path)
      link_to_if(condition, text, path, options) do
        
        content_tag(:span, text)
      end
    end
  end
```

- ブログ一覧へのリンクを追加する。

```erb:app/views/shared/_header.html.erb（一部）
    <%= menu_link_to "ブログ", :entries %>
```
- 未ログインのブログ一覧

![image.png](https://qiita-image-store.s3.amazonaws.com/0/88686/b4a7dafd-ae39-ae09-5b00-97a76fa81c66.png)

- `Hana`をクリックすると、Hanaさんのブログ一覧が見られる
 
![image.png](https://qiita-image-store.s3.amazonaws.com/0/88686/569b2581-d628-dc5b-8330-93f494ede808.png)

- ログイン後のブログ一覧

![image.png](https://qiita-image-store.s3.amazonaws.com/0/88686/25719412-e6f5-f573-8fed-d7bd9d9defa6.png)

### 記事の詳細表示

```rb:app/views/entries/show.html.erb
<% @page_title = @entry.title + " - " + @entry.author.name + "さんのブログ" %>
<h1><%= @entry.author.name %>さんのブログ</h1>

<h2><%= @entry.title %></h2>

<%= simple_format(@entry.body) %>

<%= render "footer", entry: @entry %>
```

- ブログの詳細画面が見られた。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/88686/2b9a49a2-3fef-10cd-ded8-4c308b74d28e.png)

### サイドバーの修正

- サイドバーにブログ一覧を最新5件表示する。

```erb:app/views/shared/_sidebar.html.erb（一部）
<h2>会員のブログ</h2>
<%
  # 現在ログインしている人がいればその人の記事と会員のみと公開記事、それ以外は公開記事を取得。
  entries = Entry.readable_for(current_member).order(posted_at: :desc).limit(5)
%>

<ul>
  <% entries.each do |entry| %>
    <li>
      <%= link_to entry.title, entry %>
      by<%= link_to entry.author.name, [entry.author, :entries] %>
    </li>
  <% end %>
</ul>
```

- サイドバーにブログ一覧が表示された。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/88686/4e9f5dc4-ba8c-7e94-0d7a-83782a4df031.png)

## 記事の作成、更新、削除

### newアクションとeditアクション

- 新規は、posted_atに現在日付を初期値として渡して、画面上に入れる。
- 更新画面は、ログインメンバーのentriesを取得してfindしている。idの記事が見つからなければエラー。

```rb:app/controllers/entries_controller.rb（一部）
  def new
    @entry = Entry.new(posted_at: Time.current)
  end

  def edit
    # current_memberからentriesを取得している。一対多の関係があるため。
    @entry = current_member.entries.find(params[:id])
  end
```

### 記事の編集フォーム

- newとedit、そして入力の部分テンプレートを作成します。

```erb:app/views/entries/index.html.erb
<% @page_title = "ブログ記事の新規作成" %>
<h1><%= @entry.title %></h1>

<%= form_for @entry do |form| %>
  <%= render "form", form: form %>
  <div><%= form.submit %></div>
<% end %>

```

```erb:app/views/entries/edit.html.erb
<% @page_title = "ブログ記事の編集" %>
<h1><%= @entry.title %></h1>

<div class="toolbar"><%= link_to "記事の表示に戻る", @entry %></div>

<%= form_for @entry do |form| %>
  <%= render "form", form: form %>
  <div><%= form.submit %></div>
<% end %>

```

```erb:app/views/entries/_form.html.erb
<%= render "shared/errors" %>

<table class="attr">
  <tr>
    <th width="80"><%= form.label :title %></th>
    <td><%= form.text_field :title, size: 50 %></td>
  </tr>
  <tr>
    <th><%= form.label :body %></th>
    <td><%= form.text_area :body, rows: 10, cols: 45 %></td>
  </tr>
  <tr>
    <th><%= form.label :posted_at, for: "entry_posted_at_li" %></th>
    <td><%= form.datetime_select :posted_at, start_year: 2000, end_year: Time.current.year + 1, use_month_numbers: true %></td>
  </tr>
  <tr>
    <th><%= form.label :status %></th>
    <td><%= form.select :status, Entry.status_options %></td>
  </tr>
</table>
```

### 記事の追加と更新、削除

- 記事の新規保存、更新保存、削除を作成する。

```rb:
  def create
    @entry = Entry.new(params[:entry])
    @entry.author = current_member
    if @entry.save
      redirect_to @entry, notice: "記事を作成しました。"
    else
      render "new"
    end
  end

  def update
    @entry = current_member.entries.find(params[:id])
    @entry.assign_attributes(params[:entry])
    if @entry.save
      redirect_to @entry, notice: "記事を更新しました。"
    else
      render "edit"
    end
  end

  def destroy
    @entry = current_member.entries.find(params[:id])
    @entry.destroy
    redirect_to @entry, notice: "記事を削除しました。"
  end
```

- 自由にブログを新規作成、編集、削除ができるようになった。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/88686/9ae48053-1450-746d-6524-5e7493747f2f.png)

- 一対多の関連付けで、ブログを作成することができた。

参考
[改訂4版 基礎 Ruby on Rails (IMPRESS KISO SERIES)](https://www.amazon.co.jp/%E6%94%B9%E8%A8%824%E7%89%88-Ruby-Rails-IMPRESS-KISO/dp/429500460X/ref=tmm_pap_swatch_0?_encoding=UTF8&qid=1536666358&sr=1-1)