Rails Tutorial 拡張機能のメッセージ機能を作ってみた(その3)：DMの作成

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

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

###DMを作成
DMを作成する機能を作ります。micropostを作成するところを参考にします。tutorialのリスト 13.36: を参考にコントローラーにcreateアクションを作ります。


```ruby:app/controllers/dms_controller.rb
class DmsController < ApplicationController
  before_action :logged_in_user, only: [:index, :create, :destroy]
  
  def index
      @user = current_user
      @dms = @user.sent_dms.paginate(page: params[:page])
  end

  def create
    @dm = current_user.sent_dms.build(dm_params)
    if @dm.save
      flash[:success] = "DM sent!"
    else
      render 'dms/index'
    end
  end

  private
    def dm_params
      params.require(:dm).permit(:content,:receiver)
    end
end
```

```ruby:app/views/dms/index.html.erb
<section class="micropost_form">
  <%= form_for(@dm) do |f| %>
    <%= render 'shared/error_messages', object: f.object %>
    <div class="field">
      <%= f.label :receiver_name %>
      <%= f.text_field :receiver_name, class: 'form-control' %>
      
      <%= f.text_area :content, placeholder: "new DM..." %>
    </div>
    <%= f.submit "Send", class: "btn btn-primary" %>
  <% end %>   
</section>
```

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

```
 Showing /home/ubuntu/environment/sample_app/app/views/dms/index.html.erb where line #13 raised:
First argument in form cannot contain nil or be empty
```

@dmがnilなのでエラーになったと考えました。リスト 13.40を参考に修正します。

```ruby:app/controllers/dms_controller.rb
  <%= form_for(@dm) do |f| %>

  def index
      @user = current_user
      @dms = @user.sent_dms.paginate(page: params[:page])
      @dm = current_user.sent_dms.build if logged_in?
```

別のエラーになりました。

```
ActionView::Template::Error (undefined method `receiver_name' for #<Dm:0x00007ff01420e4a8>
Did you mean?  receiver_id
```

formについて理解が足りないようで、読み直します。
7.2.1でform_forの引数はActive Recordのオブジェクトを取り込むとあります。
一方で、8.1.2ではform_forにActive RecordにはないSessionを扱っています。
リスト8.7で検索をしているところを参考に使えそうです。
form_forの引数にハッシュを使っていることが分かりました。
form_forを修正します。
修正前： <%= form_for(@dm) do |f| %>
修正後： <%= form_for(:dm) do |f| %>  

```ruby:app/views/dms/index.html.erb
<section class="micropost_form">
  <%= form_for(:dm, url:new_dm_path) do |f| %>
```

同じエラーです。

```
ActionView::Template::Error (undefined method `receiver_name' for #<Dm:0x00007f962c6ab110>
      <%= f.text_field :receiver_name, class: 'form-control' %>
```

メソッドがないというので、モデルにある列名ならよいのかと考え、試しに変えてみます。
変更前:receiver_name
変更後:receiver_id

```
      <%= f.text_field :receiver_id, class: 'form-control' %>
```

画面が表示されました。生成されたページのソースを見てみます。

```html
 <form action="/dms/new" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="&#x2713;" /><input type="hidden" name="authenticity_token" value="rSSWCwPnDGlcWrtuhvyk4BZdbOzImu6+XGx9ZVGKqJcUdwsYZdqCxsBOVAPsSHYj0L6plKtBiqAigicYUtCyYA==" />
    <div class="field">
      <label for="dm_receiver_name">Receiver name</label>
      <input class="form-control" type="text" name="dm[receiver_id]" id="dm_receiver_id" />
      
      <textarea placeholder="new DM..." name="dm[content]" id="dm_content">
```

form_forの引数がActiveRecordと関連していて、列名かどうかをチェックする動きをしています。
試しにdmをdm1と変えてみたところ、列名でなくてもエラーは起きなくなりました。

```ruby:app/views/dms/index.html.erb
  <%= form_for(:dm1, url:new_dm_path) do |f| %>
    <%#= render 'shared/error_messages', object: f.object %>
    <div class="field">
      <%= f.label :receiver_name %>
      <%= f.text_field :receiver_name, class: 'form-control' %>     
      <%= f.text_area :content, placeholder: "new DM..." %>
    </div>
    <%= f.submit "Send", class: "btn btn-primary" %>
  <% end %>
```

###試しにPOSTを画面で実行
receiver_nameをキーとしてユーザーを見つけ、DMをデータベースに追加します。
リスト12.5を参考にします。
Sendボタンをクリックします。

エラーが起きました。

```
No route matches [POST] "/dms/new"
```

ページのソースを調べると

```html
 <form action="/dms/new"
```

となっています。
urlに指定するのはPOSTのpathだと分かりましたので、修正します。

```
  <%= form_for(:dm1, url:dms_path) do |f| %>
```

別のエラーが起きました。

```
Param is missing or the value is empty: dm
      params.require(:dm).permit(:content,:receiver)
app/controllers/dms_controller.rb:28:in `dm_params'
```

パラメーターを調べます。

```
{"utf8"=>"✓", "authenticity_token"=>"QnULF0hViVk9ty/LggtG1fcwzTYNvfp8HMJ6nvq2Z/H9qG/1nSzPgR1+97W7osFg250OdLAO1q8vplnjjIbvNQ==", "dm1"=>{"receiver_name"=>"gege", "content"=>"agege"}, "commit"=>"Send"}
```

dmをdm1に変更する必要がありそうなので、修正します。
変更前:params.require(:dm).permit(:content,:receiver)
変更後:params.require(:dm1).permit(:content,:receiver)

別のエラーが起きました。

```
'nil' is not an ActiveModel-compatible object. It must implement :to_partial_path.
 <%= render @dms %>  
```

@dmsがnilとは？、@dmsを設定していないのでエラーになったと考えます。

createでリストを表示するところに、indexと同じ行をコピーします。重複感があるのですが、まとめるのは後廻しにします。その結果、メッセージが正常に表示されました。

![dm6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/639093/06de4c3c-278e-ae2b-5789-7f66e5e11524.png)



###受信者がいないケースのテスト
テストを作ります。
リスト8.9を参考にします。

```ruby:test/integration/dms_test.rb
  test "send dm with invalid receiver" do
    log_in_as(@user)
    get dms_path
    post dms_path, params: { dm1: {receiver_name: "" }}
    assert_template 'dms/index'
    assert_not flash.empty?
  end
```
リスト13.36を参考に、DMを送るアクションを作ります。

```ruby:app/controllers/dms_controller.rb
 def create
    @user = current_user
    @dms = @user.sent_dms.paginate(page: params[:page])
    # receiverを探す
    @receiver = User.find_by(name: params[:dm1][:receiver_name])
    if @receiver
      @dm = @user.sent_dms.build 
      @dm.receiver_id = @receiver.id
      @dm.content = params[:dm1][:content]
      @dm.save
      flash[:success] = "DM sent!"
      redirect_to dms_path
    else
      flash.now[:danger] = 'Receiver not found'
      render 'index'
    end
 ```   

DMを送ってみます。無事表示されました。
![dm7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/639093/c705fe96-c465-db4a-8403-2fdc0a69b88e.png)

###contentがブランクのテスト
contentをブランクにしてDMを送ってみます。
画面にはエラーが表示されません。コンソールのログを調べます。

```
   (0.1ms)  rollback transaction
Redirected to https://7eca7b943b584a2296afeb1d7ceb9db2.vfs.cloud9.us-east-2.amazonaws.com/dms
Completed 302 Found in 10ms (ActiveRecord: 0.7ms)
```

```
NoMethodError in Dms#index
undefined method `errors' for nil:NilClass
<% if object.errors.any? %>
```

8章と12章をもう一度読みます。
receiverがnot foundのときを再テストしてみます。
エラーが表示されました。

@dmをbuildしていないので、nilになっているのではと考え、buildしている行をreceiverを探す判定の前に移しました。

```ruby:app/controllers/dms_controller.rb
 def create
    @user = current_user
    @dms = @user.sent_dms.paginate(page: params[:page])
    @dm = @user.sent_dms.build 
    # receiverを探す
    @receiver = User.find_by(name: params[:dm1][:receiver_name])
    if @receiver
      @dm.receiver_id = @receiver.id
      @dm.content = params[:dm1][:content]
      if @dm.save
        flash[:success] = "DM sent!"
        redirect_to dms_path
      else
        render 'index'
      end
    else
      flash.now[:danger] = 'Receiver not found'
      render 'index'
    end
 end
```  

not foundのメッセージが表示されるようになりました。
![dm8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/639093/c0d1e5da-e5eb-1a1b-aa5f-2072fe03dd23.png)

###無効・有効な送信のテスト
無効・有効な送信のテストを作ります。
リスト 13.55を参考にします。

```ruby:test/integration/dms_test.rb
  test "DM interface" do
    log_in_as(@user)
    get dms_path
    # 無効な送信
    assert_no_difference 'Dm.count' do
      post dms_path, params: { dm1: {receiver_name: @receiver.name,
                                     content: "" } }
    assert_select 'div#error_explanation'
    end
    # 有効な送信
    content = "Dm test content1"
    assert_difference 'Dm.count', 1 do
      post dms_path, params: { dm1: {receiver_name: @receiver.name,
                                     content: content } }
    end
    assert_redirected_to dms_path
    follow_redirect!
    assert_match content, response.body
  end
```

###自分が受信者のDMを表示
自分が受信者のDMも表示するように変更します。
「13.3.3 フィードの原型」を読みます。

Micropostとfeedの関係のように、DMに対してchatとchat_itemsを作ることにします。

```ruby:app/models/user.rb
  # 試作 chat
  def chat
    Dm.where("sender_id = ?", id)
  end
```


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

```ruby:app/views/dms/index.html.erb
<% if @user.sent_dms.any? %>
    <h3>DMs (<%= @user.sent_dms.count %>)</h3>
    <ol class= "microposts">
      <%= render @chat_items %>  
      <%#= render @dms %>  
    </ol>
    <%= will_paginate @chat_items %>
    <%#= will_paginate @dms %>
<% end %>  
```

receiverをブランクにして送信してみたところエラーになりました。

```
undefined method `any?' for nil:NilClass
<% if @chat_items.any? %>
```

chat_itemsを作る行をindexと同じようにcreateにも入れます。

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

  def create
    @user = current_user
    @dm = @user.sent_dms.build 
    @chat_items = @user.chat.paginate(page: params[:page])
    # receiverを探す
    @receiver = User.find_by(name: params[:dm1][:receiver_name])
    if @receiver
      @dm.receiver_id = @receiver.id
      @dm.content = params[:dm1][:content]
      if @dm.save
        flash[:success] = "DM sent!"
        redirect_to dms_path
      else
        render 'index'
      end
    else
      flash.now[:danger] = 'Receiver not found'
      render 'index'
    end
``` 

テストしてすべてGreenです。

##所要時間

11/15から11/22までの6.5時間です。