5
3

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 5 years have passed since last update.

rails db:seedの実行時にはcontrollerは経由されない

Last updated at Posted at 2019-02-23

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チュートリアルの拡張機能として、マイクロポストに『@ユーザー名』を含めると返信できる、というリプライ機能を実装していました。

コードはこちら(不要な部分は省略・一部修正してます)。

MicropostsController.rb
    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

user.rb
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

seeds.rb
# リプライつきポスト
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ではよばれるわけがなかったんです。恥ずかしい。

image.png
(Railsチュートリアルより引用)

5
3
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
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?