Railsチュートリアルの拡張機能を実装していて、思い通りの挙動にならない!と戸惑ったので書いておきます。
実際はただの理解不足による勘違いだったのですが、もし同じところでつまづいているひとがいたら参考になるかもしれません。
##rails db:seedの実行時にはモデルのみを経由する
rails db:seed
コマンドで初期データを生成するとき、このデータはModelをみて生成され、controllerは経由されない。
通常、MVCでのデータの流れは
①フォームなどからユーザーによって入力される(view)
②routeを経由して適切なconrollerとアクションが呼びだされる
③controller内でデータを操作する
④対応するmodelがDBとのデータのやりとりを行い(保存・取り出し)、controllerに返す
⑤controllerからviewにデータをわたし、描画する
というふうになると思います。
はじめに挙げたrails db:seed
(一般的にはrails db:migrate:reset
とセットで使う)コマンドは、seeds.rbをもとに初期データを生成・DBに保存します。
個人的には③と④をしてくれるんだな〜(データの操作と保存はひとつながりのセットというイメージ)と思っていたのですが、実際はそうではなく、④のモデルのみを経由してDBにデータを保存、という動きをします。
つまり、DBに保存するまえにコントローラーでデータを操作する実装をしていた場合、seedで生成した初期データにはその操作は適用されないので注意が必要です。
##Railsチュートリアルにおける具体例
コードを交えつつ、実際に直面した問題を説明します。
Railsチュートリアルの拡張機能として、マイクロポストに『@ユーザー名』を含めると返信できる、というリプライ機能を実装していました。
コードはこちら(不要な部分は省略・一部修正してます)。
def create
@micropost = current_user.microposts.build(micropost_params)
set_in_reply_to
if @micropost.save
flash[:success] = "Micropost created!"
redirect_to root_url
else
@feed_items = []
render 'static_pages/home'
end
end
private
#@userがあればその値をin_reply_toカラムにセット
def set_in_reply_to
#リプライはひとりにしかつけれらない仕様
reply_user = @micropost[:content].slice(/(?<=@)[^\s]+/)
@micropost[:in_reply_to] = reply_user unless reply_user.nil?
end
def feed
following_ids = "SELECT followed_id FROM relationships WHERE follower_id = :user_id"
Micropost.where("user_id IN (#{following_ids}) OR user_id = #{id} OR in_reply_to = #{user_name}")
end
さっきのMVCの流れでいうと、
①投稿フォームに@userをふくめたデータが入力される(view)
②MicropostsControllerのcreateアクションがよばれる
③createアクション内で、入力されたデータから@userを抜きだして特定のカラムにセットする(set_in_reply_toメソッド)
④modelを通じてDBに保存。そのDBから表示するべきポストをとり出し、conrollerに返す(feedメソッド)
⑤該当ユーザーのフィードに、@userを含んだポストが表示される
という感じです。
実際に画面を動かしてこれを確認するには、
1.まずログインして@userをふくめた投稿をする
2.一旦ログアウト
3.@userで指定したユーザーで再度ログイン
4.フィードにそのポストがあるか確認
という4ステップが必要になります。普通にめんどくさい。
それならseedで初期データとして@userを含んだ投稿を生成しておいて、そのユーザーでログインすれば一発じゃん(!?)。
ということで、seeds.rbを修正して、rails db:migrate:reset
からのrails db:seed
。
# リプライつきポスト
second_user = User.second
second_user.microposts.create!(content: "this should be displayed on @First_User 's feed")
end
あとは@First_Userでログインすれば、フィードにこのポストがあるはず!!→ない。
変だなとおもいつつ調査した結果は以下のとおり。
・ポスト自体はちゃんと存在している(投稿したユーザーページに出てくる)
・railsコンソールで確認すると、コントローラーでセットしたはずのin_reply_toカラムがnilになっている
・まったくおなじ内容をウェブから投稿すると、ちゃんと@First_Userのフィードの反映される(=controllerおよびフィードの取得は正しく動いている)
ここまできてやっと、rails db:seed
ではcontrollerは呼びだされないのか...?という仮説をもち、メンターさんに質問したところ、ビンゴ。ググっても出てこず、疑問を解消するのにかなり時間を食ってしまいました。
ちなみに、railsコンソールからseeds.rbに書いたコードを実行すると、DBに新しいデータは保存されますがin_reply_toカラムはnilになります。
よく考えると、user.microposts.create
はmicropostsモデルに直接create
を投げているので、MicropostsControllerのcreate
アクションとは別物なんですよね...
というか、そもそもコントローラーってブラウザからのリクエストを処理するところなので、そりゃ直接データを突っ込むrails db:seed
ではよばれるわけがなかったんです。恥ずかしい。
(Railsチュートリアルより引用)