Rails Tutorial 拡張機能のメッセージ機能を作ってみた(その2)：表示する画面を作成

Rails Tutorialの第14章にある、メッセージ機能を作る件の続きです。

前回まででモデルができました。表示する画面を作ります。

###DMを表示するViewの仕様を設計
DMを表示する方法を作ります。
tutorialの13.2　「マイクロポストを表示する」を読みます。

MicropostのようにUserの画面に合わせて表示するのではなく、独立したページで表示することにします。Twitterと同様です。

モックアップを作ります。送信者が複数いるので、送信者が表示されているモックアップとして、図 14.5を参考にします。

----------------------------------
DM(3)
画像1　　Thomas Hobbes  Lorem ipsum
             sent 1 day ago.
画像2　　Sasha Smith  Also poor,nasty,
             sent 2 days ago.
画像3　　John Calvin  Excepteur sint
             sent 3 days ago.

Previous 1  2  3  next

----------------------------------
図 DMページのモックアップ 



###DMを表示するViewを作成
コントローラとビューを作成するために、コントローラを生成します。

```
ubuntu:~/environment/sample_app (create-dm) $ rails generate controller Dms
```

ビューを作ります。リスト13.22と13.24を参考にします。

```ruby:app/views/dms/show.html.erb
<% provide(:title, @user.name)%>
<div class="row">
  <div class="col-md-8">
    <% if @user.send_dms.any? %>
      <h3>DMs (<%= @user.sent_dms.count %>)</h3>
      <ol class="dms">
        <li id="dm-<%= dm.id %>">
          <%= link_to gravatar_for(dm.sender, size: 50), dm.sender %>
          <span class="user"><%= link_to dm.sender.name, dm.sender%></span>
          <span class="content"><%= dm.content %></span>
          <span class="timestamp">
            Sent <%= time_ago_in_words(dm.created_at) %> ago.
          </span>
        </li>
      </ol>
    <% end %>  
  </div>
</div>
```

###DMを表示するコントローラーを作成
新しいDMのページを表示するためのコントローラーを作ります。
tutorialの「12.1.1 PasswordResetsコントローラ」を読みます。

```ruby:config/routes.rb
Rails.application.routes.draw do
  root 'static_pages#home'
  get  '/help',    to: 'static_pages#help'
  get  '/about',   to: 'static_pages#about'
  get  '/contact', to: 'static_pages#contact'
  get  '/signup',  to: 'users#new'
  post '/signup',  to: 'users#create'
  get  '/login',   to: 'sessions#new'
  post '/login',   to: 'sessions#create'
  delete '/logout', to: 'sessions#destroy'
  resources :users do
    member do
      get :following, :followers
    end
  end
    resources :account_activations, only: [:edit]
  resources :password_resets,     only: [:new, :create, :edit, :update]
  resources :microposts,          only: [:create, :destroy]
  resources :relationships,       only: [:create, :destroy]
  resources :dms,                 only: [:new, :create, :index, :destroy]
end
```

|HTTPリクエスト|URL |Action| 名前付きルート |
|:-:|:-:|:-:|:-:|
|GET   | /dms/new|new  |new_dm_path |
|POST|/dms   |create 	| dms_path |
|GET |/dms|index | dms_path|
|DELETE|dms/<id>     |destroy   | dm_path  |

RESTfulルーティング 


tutorialの「10.3.1 ユーザーの一覧ページ」を読みます。
リスト「 10.40: ユーザー一覧ページへのリンクを更新する 」にリンクを追加しているところがありました。同様に追加します。

```ruby:views/layouts/_header.html.erb
                <ul class="dropdown-menu">
                  <li><%= link_to "Profile", current_user %></li>
                  <li><%= link_to "Settings", edit_user_path(current_user) %></li>
                  <li><%= link_to "DM", dms_path %></li>
```

画面を表示してリンクがメニューに追加されたことを確かめます。

![dm1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/639093/8a253492-ef7e-6bad-21a1-626c62a41c29.png)

ログインしていなかったらRedirectするテストは後で作ることにします。


コントローラーはindexなのに、viewはshowなことに気が付きました。ファイルをリネームします。
show.html.erb -> index.html.erb

```ruby:app/controllers/dms_controller.rb
class DmsController < ApplicationController
  def index
  end
end
```
###画面を試しに表示
rails serverで画面で表示してみます。
エラーになりました。メッセージは
undefined method `name' for nil:NilClass
で、エラーが起きた場所は
<% provide(:title, @user.name)%>
です。@userがnilなのだと考えます。
@userにどこでログインしたユーザーを設定するのか、userのshow画面を参考に見て同様に変更します。

```ruby:app/controllers/dms_controller.rb
class DmsController < ApplicationController
  def index
      @user = current_user
  end
end
```

画面で表示してみます。またエラーになりました。

```
undefined local variable or method `dm' for #<#<Class:0x00005575f57bf2a8>:0x00005575f57deb58>
```

エラーが起きた場所は

```
  <li id="dm-<%= dm.id %>">
```

です。
コントローラーで@dmsにデータを入れる必要があると考えます。
micropostをhome画面に表示するところを参考に見てみます。

コントローラーで

```
      @micropost  = current_user.microposts.build
      @feed_items = current_user.feed.paginate(page: params[:page])
```

と@feed_itemsにデータを入れています。

ビューでは

```ruby:
  <ol class="microposts">
    <%= render @feed_items%>
  </ol>
```

と@feed_itemsをrenderで一覧表示しています。参考にして変更します。

```ruby:app/controllers/dms_controller.rb
  def index
      @user = current_user
      @dms = @user.sent_dms
  end
```

Micropostではfeedとfeed_itemsをうまく使っている13章を参考にするとよさそうです。読み返すなかで

```
render @user
```

が何を意味しているのかがあやふやだったので、さかのぼって読み返します。
コントローラーに

```ruby:app/controllers/dms_controller.rb
  def index
      @user = current_user
      @dms = @user.sent_dms.paginate(page: params[:page])
  end
```

とすればよいと分かり、その場合viewの　

```
<span class="user"><%= link_to dm.sender.name, dm.sender%></span>
```

に何を書けばいいのか考えます。

```ruby:app/views/dms/index.html.erb
<% provide(:title, @user.name) %>
<h1>DM</h1>

<% if @user.sent_dms.any? %>
    <h3>DMs (<%= @user.sent_dms.count %>)</h3>
    <ol class= "microposts">
      <%= render @dms %>  
    </ol>
    <%= will_paginate @dms %>
<% end %>  
```

```ruby:app/views/dms/_dm.html.erb
<li id="dm-<%= dm.id %>">
  <%= link_to gravatar_for(dm.sender, size: 50), dm.sender %>
  <span class="user"><%= link_to dm.sender.name, dm.sender%></span>
  <span class="content"><%= dm.content %></span>
  <span class="timestamp">
    Sent <%= time_ago_in_words(dm.created_at) %> ago.
  </span>
</li>
```

試しにrails serverで画面を表示してみます。データが少ないため1ページしかありません。
pagnateがされているか確認するために、データを増やします。
contentのテストデータ生成に、Faker::Hipster.sentenceを使ってみます。

```ruby:db/seeds.rb
# DM
users = User.order(:created_at).take(6)
receiver = users.second
50.times do
  content = Faker::Hipster.sentence
  users.each {|user| user.sent_dms.create!(content: content,
                                           receiver_id: receiver.id) }
end
```

画面を表示してみます。

![dm3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/639093/9e69ec24-c79f-dc6e-6fa7-522a19d86ba9.png)


receiverが出ていないことに気が付きましたので、senderから変更します。

```ruby:app/views/dms/_dm.html.erb
<li id="dm-<%= dm.id %>">
  <%= link_to gravatar_for(dm.receiver, size: 50), dm.receiver %>
  <span class="user"><%= link_to dm.receiver.name, dm.receiver%></span>
```

![dm4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/639093/35ace002-3339-bdec-2a3b-61c68afa2ac2.png)

###DM表示のテスト作成

DMを表示する画面のテストを作ります。
tutorialの「13.2.3　プロフィール画面のマイクロポストをテストする」を参考にします。

```ruby:test/fixtures/dms.yml
...
<% 30.times do |n| %>
dm_<%= n %>：
  content: <%= Faker::Hipster.sentence %>
  created_at* <%= 42.days.ago %>
  sender: michael
  receiver: archer
<% end %>
```


```ruby:test/integration/dms_test.rb
class DmsTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end
  
  test "dm display" do
    log_in_as(@user)
    get dms_path
    assert_template 'dms/index'
    assert_select 'title', full_title(@user.name)
    assert_match @user.sent_dms.count.to_s, response.body
    assert_select 'div.pagination'
    @user.sent_dms.paginate(page: 1).each do |dm| 
      assert_match CGI.escapeHTML(dm.content), response.body
    end
  end
```

リスト 13.28を参考に、「'」などの記号が特殊文字で出力されていたので、エスケープする方法をネットで調べて修正しました。
https://rakuda3desu.net/rakudas-rails-tutorial14-3/


###コントローラーのアクセス制御のテスト作成
controllerのテストを作ります。
tutorialの「13.3.1 マイクロポストのアクセス制御」を読みます。

```ruby:test/controllers/dms_controller_test.rb
  test "should redirect index when not logged in" do
    get dms_path
    assert_redirected_to login_url
  end  
```
REDです。コントローラーにindexアクションに対するアクセス制限を追加します。

```ruby:app/controllers/dms_controller.rb
class DmsController < ApplicationController
  before_action :logged_in_user, only: [:index]
```

テストがGREENになりました。

##所要時間
11/7から11/14までの7.0時間です。