質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.06%

Railsチュートリアル13章 @feed_itemsがnilになる?

解決済

回答 2

投稿

  • 評価
  • クリップ 2
  • VIEW 1,366

score 17

前提・実現したいこと

ここに質問の内容を詳しく書いてください。
現在、rails チュートリアルを利用して学習中です。
13章の中盤あたり、13.3.3 フィードの原型という節の解説でどうしても引っかかる部分があり投稿しました。

本節ではtwitterのような短い投稿フォーム(micropostフォーム)を静的なhomeページに表示させ、その横に投稿の一覧を表示させる(feed_items)という作業をしています。
その文中で、「投稿内容が空欄(投稿が失敗)の際に、renderで同じhomeページを出力する際にエラーが発生する」という解説があります。

現時点では、新しいマイクロポストの作成は図 13.15で示したように期待どおりに動作しています。ただしささいなことではありますが、マイクロポストの投稿が失敗すると、 Homeページは@feed_itemsインスタンス変数を期待しているため、現状では壊れてしまいます。

この部分が理解できずにいます。ですのでその後に続く

最も簡単な解決方法は、リスト 13.50のように空の配列を渡しておくことです。残念ですが、この場合はページ分割されたフィードを返してもうまく動きません。

という一文も理解でできていません。
ご教授いただきたく投稿しました。よろしくお願いします。
(何か記載する情報が足りない場合はご指摘ください)

発生している問題・エラーメッセージ

undefined method `any? for nil:NilClass


エラー個所は
<% if @feed_items.any? %>で、解説の

Homeページは@feed_itemsインスタンス変数を期待しているため、現状では壊れてしまいます。

という言葉と一致していると思われます。
undefined method any? for nil:NilClassというエラーが出ているということは、@feed_itemsがnilになっているということだと思うのですが、なぜnilになってしまうのかが知りたいです。
renderでhomeページを表示

該当のソースコード

home.html.erb (homeページのソース)

<% if logged_in? %>
  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        <%= render 'shared/user_info' %>
      </section>
      <section class="micropost_form">
        <%= render 'shared/micropost_form' %>
      </section>
    </aside>
    <div class="col-md-8">
      <h3>Micropost Feed</h3>
      <%= render 'shared/feed' %>
    </div>
  </div>
<% else %>
  .
  .
  .
<% end %>

<%= render 'shared/feed' %>のパーシャル

<% if @feed_items.any? %>
  <ol class="microposts">
    <%= render @feed_items %>
  </ol>
  <%= will_paginate @feed_items %>
<% end %>

homeコントローラー(@feed_itemsの定義があります)

  def home
    if logged_in?
      @micropost  = current_user.microposts.build
      @feed_items = current_user.feed.paginate(page: params[:page])
    end

上記コード、feedメソッド部分(User model)

  def feed
    Micropost.where("user_id = ?", id)
  end

micropostを作成する際のcreateアクションの定義

class MicropostsController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]

  def create
    @micropost = current_user.microposts.build(micropost_params)
    if @micropost.save
      flash[:success] = "Micropost created!"
      redirect_to root_url
    else #投稿が失敗
      @feed_items = [] #これを挿入しないとエラーが出る
      render 'static_pages/home' 
    end

試したこと

何回かキーワードになりそうな単語を使い調べてみたのですが(render rails 変数,, etc)わかりませんでした。
ものすごく基本的なことを質問してしまっていそうで怖いのですが、ご回答いただければ幸いです。

補足情報(FW/ツールのバージョンなど)

ここにより詳細な情報を記載してください。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+3

下記の1行を足さないとエラーになる原因を知りたいという理解で大丈夫でしょうか?

      @feed_items = [] #これを挿入しないとエラーが出る

まず、投稿に失敗した際は@micropost.savefalseになり、else内に行くことになります。
その後render(static_pages/home)で、つまりhome.html.erbが表示されます。

このときhome.html.erbに渡される変数は、def createからrender 'static_pages/home'の間で@の付いた変数に入れられたものです。

この場合だと、
@micropost = current_user.microposts.build(micropost_params)
と、
@feed_items = []
になります。

  def create
    @micropost = current_user.microposts.build(micropost_params)
    if @micropost.save
      flash[:success] = "Micropost created!"
      redirect_to root_url
    else #投稿が失敗
      @feed_items = [] #これを挿入しないとエラーが出る
      render 'static_pages/home' 
    end

では@feed_items = []がないとどうなるかというと、home.html.erbの描画時に@feed_itemsの中に何も入っていないということになります。
つまり@feed_itemsにはnilが入ります。

nilany?という関数を持たないため、shared/feed@feed_items.any?でエラーになるわけです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/06/15 16:37

    お返事ありがとうございます!
    > 下記の1行を足さないとエラーになる原因を知りたいという理解で大丈夫でしょうか?
    そうです、@feed_itemsがnilだからエラーが起こる事までは把握できたのですが、なぜnilになるかがわかりませんでした。
    ですが、お二人から頂いた解説で、「どこを理解してなかった」が理解できました。
    どうやらrenderの動作がまだ把握できていなかったようです。
    renderでページを描写する際の変数の読み込みの理解がまだ足りていないようでした。

    ご回答いただいた後にチュートリアルには記載されてなかったですが、自分で少しコードをいじって色々と調べてみました。誰かの参考になるかもしれないので記載します。

    ---------------------------------------------
    チュートリアルのサンプルでは、投稿が失敗した場合はエラー文を表紙させる代わりに、@feed_itemsに空の配列を入れてるので過去の投稿一覧が消えてしまっていました。
    出来れば、1,エラー文を表示させつつ 2,過去の投稿も表示させたい と思いコードを修正しました。

    def createの投稿失敗時の動作
    else #投稿が失敗
    @feed_items = [] #これを挿入しないとエラーが出る
    render 'static_pages/home' 
    end

    else #投稿が失敗
    redirect_to root_path (homeページ) 
    end

    と変更した場合(renderではなくredirectを使って再描写)は、発生したエラーも含めてページが更新された状態になるので、過去の投稿は表示されましたがエラー内容が表示されませんでした。

    次に
    else #投稿が失敗
    @feed_items = current_user.feed.paginate(page: params[:page])
    render 'static_pages/home' 
    end

    と、失敗時に@feed_itemsにhomeコントローラーと同じ内容を入れておくことで、renderした後、エラーを表示しつつ過去の自分の投稿も表示させることができました。
    こちらの方がより実際のSNSっぽいかなぁと思います。
    ---------------------------------------------


    お二人からご回答を頂けて本当に助かりました、ありがとうございます。精進します!

    キャンセル

+3

any? は配列のようなオブジェクトに使えるmethodなので nil には使えません。

@feed_items = current_user.feed.paginate(page: params[:page]) で current_user が feed を持っていれば any? が使えるようになりますが、そもそも feed が存在せず、 @feed_items に nil が入ってしまうと nil.any? となってしまうのでエラーになってしまいます!

undefined method any? for nil:NilClassというエラーが出ているということは、@feed_itemsがnilになっているということだと思うのですが、なぜnilになってしまうのかが知りたいです。

@feed_items が nil になる原因は、 current_user が feed を一つも保持していないときがあるからです!

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/06/15 00:40

    t_kusakabe74 さん

    ご回答ありがとうございます。
    .anyやエラーの原因の解説ありがとうございます!理由は理解できたのですが、追加で新しい疑問点が一つできてしまったので追記しました。

    ご回答で
    > @feed_items が nil になる原因は、 current_user が feed を一つも保持していないときがあるからです!

    ということですが、今回エラーが起きたcurrent_userには過去にもmicropostに投稿がありました。
    つまりhomeページで投稿を失敗する前までは
    current_userがfeedを保持している状態でした。(過去の投稿が表示されていました)
    その後投稿に失敗した際にfeedを一つも保持していない(=nilになった)状態になったという理解でいいと思うのですが、これの原因は一旦なんなのでしょうか?

    いきなりnilになっているので
    Micropost.where("user_id = ?", id)のidが取得できていない?など色々考えていたのですが。。。ご回答いただけたら幸いです。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.06%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る