Ruby on Rails Tutorial の第二版11.4.1にある、「サンプルアプリケーションの機能を拡張する」を実践しました。今回は返信機能の拡張です。最新の第三版ではなく第二版なのは、自分の会社がRspecを用いているからです。
仕様
あくまでも演習ということで、 あまり現実的とは言えませんが、 以下のような仕様としました。
- マイクロポスト入力中に@記号に続けてidとユーザー名を組み合わせたユニークなフレーズを入力すると、 そのマイクロポストは返信となる。例えば、idが1でユーザー名が"Example User"なら、@1-Example-Userとする(スペースはハイフンで置換)。
- 返信扱いのマイクロポストは、 マイクロポストの送信者(自分)と受信者(@1-Example-Userのように指定したユーザー)のフィードにのみ表示される。
- 返信先として指定できるのは、 一つのマイクロポストにつき一人のみ(@1-Example-Userのようなフレーズを複数入力しても、一つ目のユーザーにしか返信されない)。
実装
Micropostsへのカラムの追加
各マイクロポストに、 返信先のユーザーを指定するためのカラムが必要なので、追加する。"rails g migration AddInReplyToToMicropost"を実行。マイグレーションスクリプトを以下のように変更し、 "rake db:migrate"。
class AddInReplyToToMicroposts < ActiveRecord::Migration
def change
add_column :microposts, :in_reply_to, :string, default: ""
end
end
カラム名は"in_reply_to"、defaultは""とした。
in_reply_toカラムへの保存
Micropostモデルにbefore_saveを設定し、 Micropostのcontentから返信先ユーザーを表すフレーズを抽出し、 in_reply_toカラムに入力する。
class Micropost < ActiveRecord::Base
・
・
before_save { in_reply_to = reply_user.to_s }
・
・
・
def reply_user
if user_unique_name = content.match(/(@[^\s]+)\s.*/)
user_unique_name[1]
end
end
end
scopeの追加
任意のマイクロポストの"in_reply_to"カラムに返信先ユーザーを表すフレーズ(@1-Example-Userの形式)が入っている場合、 そのマイクロポストは返信先ユーザーおよび送信者(自分)からしか見えないようにする必要がある。例えば、@1-Example-Userというフレーズがcontentに入っていたら、 そのマイクロポストはid: 1、 name: "Example User"の属性を持つユーザーに対する返信となり、 自分と返信先ユーザーのフィードにのみ表示される。この機能を実現するため、 Micropostモデルに以下のようなscopeを追加する。
class Micropost < ActiveRecord::Base
・
・
scope :including_replies, ->(user){where("in_reply_to = ? OR in_reply_to = ? OR user_id = ?", "", "@#{user.id}\-#{user.name.sub(/\s/,'-')}", user.id)}
・
・
end
scopeによるフィードの絞り込み
上で実装したscopeをstatic_pages_controllerで使用する。それに伴って、 User.feedメソッドも以下のように変更。
class StaticPagesController < ApplicationController
def home
if signed_in?
@micropost = current_user.microposts.build
# 元のコード
# @feed_items = current_user.feed.paginate(page: params[:page])
# 変更後のコード
@feed_items = current_user.feed(current_user).paginate(page: params[:page])
end
end
・
・
・
・
end
class User < ActiveRecord::Base
・
・
・
・
・
def feed(user)
Micropost.including_replies(user).from_users_followed_by(self)
end
・
・
・
end
これで、簡易的な返信機能が実装できました。
おまけ
以下、 RSpecでの簡単なテストです。
describe "Static pages" do
subject { page }
describe "Home page" do
・
・
・
describe 'reply micropost' do
# 送信者としてのユーザーを作成
let(:user){FactoryGirl.create(:user)}
# 返信先のユーザーを作成
let(:reply_user){FactoryGirl.create(:user)}
# 第三者のユーザーを作成
let(:other_user){FactoryGirl.create(:user)}
before do
# 送信者から、返信先ユーザーに向けたマイクロポストを投稿
FactoryGirl.create(:micropost, user: user, content: "@#{reply_user.id}\-#{reply_user.name.strip.sub(/\s/,'-')}\nhello world")
reply_user.follow!(user)
end
#送信者からはマイクロポストがフィードに表示される
describe 'at user' do
before do
sign_in user
visit root_path
end
it { should have_content("hello world") }
end
#送信先ユーザーにもマイクロポストがフィードに表示される
describe 'at reply_user' do
before do
sign_in reply_user
visit root_path
end
it { should have_content("hello world") }
end
#第三者ユーザーにはマイクロポストがフィードに表示されない
describe 'at other_user' do
before do
sign_in other_user
visit root_path
end
it { should_not have_content("hello world") }
end
end
・
・
・