Rails Tutorialの第14章にある、メッセージ機能を作る件の続きです。
前回までで表示する画面ができました。作成する画面を作ります。
###DMを作成
DMを作成する機能を作ります。micropostを作成するところを参考にします。tutorialのリスト 13.36: を参考にコントローラーにcreateアクションを作ります。
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
<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を参考に修正します。
<%= 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| %>
<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="✓" /><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と変えてみたところ、列名でなくてもエラーは起きなくなりました。
<%= 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と同じ行をコピーします。重複感があるのですが、まとめるのは後廻しにします。その結果、メッセージが正常に表示されました。
###受信者がいないケースのテスト
テストを作ります。
リスト8.9を参考にします。
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を送るアクションを作ります。
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
###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を探す判定の前に移しました。
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のメッセージが表示されるようになりました。
###無効・有効な送信のテスト
無効・有効な送信のテストを作ります。
リスト 13.55を参考にします。
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を作ることにします。
# 試作 chat
def chat
Dm.where("sender_id = ?", id)
end
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
<% 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にも入れます。
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時間です。