2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

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

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

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

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
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を参考に修正します。

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| %>

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' %>

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

 <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と変えてみたところ、列名でなくてもエラーは起きなくなりました。

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"

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

 <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

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

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を送るアクションを作ります。

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

###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を探す判定の前に移しました。

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

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

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を作ることにします。

app/models/user.rb
  # 試作 chat
  def chat
    Dm.where("sender_id = ?", id)
  end
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
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にも入れます。

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時間です。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?