質問するログイン新規登録

Q&A

解決済

2回答

1445閲覧

NoMethodError in PostsController#update undefined method `user_id' for nil:class [投稿内容の編集でエラーが出ています]

1234567

総合スコア7

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Ruby on Rails 6

Ruby on Rails 6は、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

0グッド

0クリップ

投稿2020/01/31 05:44

編集2020/02/06 05:46

0

0

現在Ruby on Rauls にて、Twitterのような投稿サービスを作っております。求職用のポートフォリオとしての制作です。

元々progateで勉強をしていたため入力フォームを「form_tag」で理解していました。そして今「form_for」or「form_with」へview/postのフォーム部分を入れ替えているところです。その最中、form_tagでは投稿した内容の編集機能は動いていたのですが、機能しなくなってしまいました。

エラー文は、この通りです
イメージ説明

自分で試したこととしては、
・routesをresourcesにする。(編集できるようになったが、編集した投稿とは別の投稿に変更が反映される等の不具合が起きたため、下記routesの状態に結果戻しました)
・form_forをURLで飛ばす、コントローラ名とアクション名で飛ばす、ルーティングで定義したもので飛ばす
などを試みました。

しかし、一向に解決できず、、

Rails.application.routes.draw do get "posts/index" => "posts#index" get "posts/new" => "posts#new" post "posts/create" => "posts#create" get "posts/:id/edit" => "posts#edit" get "posts/:id" => "posts#show" patch 'posts/:id' => 'posts#update' post "posts/:id/destroy" => "posts#destroy" post "users/create" => "users#create" get "signnup" => "users#new" post "login_guest" => "users#login_guest" get "login" => "users#login_form" post "login" => "users#login" post "logout" => "users#logout" get "users/index" => "users#index" post "users/:id/update" => "users#update" get "users/:id" => "users#show" get "users/:id/edit" => "users#edit" get "home/top" end コード
application.html.erb <!DOCTYPE html> <html> <head> <title>Read & Memo</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto" > <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> </head> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"> <body> <div class="header"> <div class="header-logo"> <%= link_to("「Read & Memo」", "/home/top") %> </div> <ul class="header-menus"> <% if @current_user %> <li> <span class="fa fa-user"></span> <%= link_to(@current_user.name, "/users/#{@current_user.id}") %> </li> <li> <span class="fa fa-pencil"></span> <%= link_to("投稿する", "/posts/new")%> </li> <li> <span class="fa fa-list"></span> <%= link_to("投稿一覧", "/posts/index")%> </li> <li> <span class="fa fa-users"></span> <%= link_to("登録者一覧", "/users/index") %> </li> <li> <span class="fa fa-sign-out"></span> <%= link_to("ログアウトする", "/logout", {method: :post}) %> </li> <% else %> <li> <span class="fa fa-user-secret"></span> <%= link_to("簡単ログイン", "/login_guest", {method: :post})%> </li> <li> <span class="fa fa-user-plus"></span> <%= link_to("新規登録", "/signnup") %> </li> <li> <span class="fa fa-sign-in"></span> <%= link_to("ログインする", "/login") %> </li> <% end %> </ul> </div> <%= yield %> </body> </html>
class PostsController < ApplicationController before_action :authenticate_user before_action :ensure_correct_user, {only: [:edit, :update, :destroy]} def index @post = Post.find_by(id: params[:id]) @posts = Post.page(params[:page]).per(5).order(created_at: :desc) end def show @post = Post.find_by(id: params[:id]) @user = User.find_by(id: @post.user_id) end def new @post = Post.new if @current_user == nil flash[:notice] = "ログインして下さい" redirect_to("/login") end end def create @post = Post.new(post_params) @post.user_id = @current_user.id if @post.save redirect_to("/posts/index") flash[:notice] = "投稿しました" else render("/posts/new") flash[:notice] = "投稿失敗" end end def edit @post = Post.find_by(id: params[:id]) end def update @post = Post.find_by(id: params[:id]) @post.user_id = @current_user.id if @post.update redirect_to("/posts/index") flash[:notice] = "投稿の編集をしました" else redirect_to("/posts/#{@post.id}/edit") end end def destroy @post = Post.find_by(id: params[:id]) @post.destroy redirect_to("/posts/index") flash[:notice] = "削除しました" end def ensure_correct_user @post = Post.find_by(id: params[:id]) if @post.user_id != @current_user.id flash[:notice] = "ユーザーが一致しません" redirect_to("/posts/index") end end private def post_params params.require(:post).permit(:content, :image, :user_id, :title) end end コード
post/new <div class="post-index"> <h1>新規投稿</h1> <% @post.errors.full_messages.each do |message| %> <div class="form-error"> <%= message %> </div> <% end %> <div class="post-new-container"> <%= form_for @post, url: posts_create_path do |f| %> <div class="posts-new-item"> <%= f.label :title, "タイトル(※必須)" %><br> <%= f.text_field :title %><br><br> <%= f.label :content, "感想・まとめ(※必須)" %><br> <%= f.text_area :content %><br><br> <%= f.label :image, "画像" %><br> <%= f.file_field :image %><br><br> <%= f.submit "投稿する" %> </div> <% end %> </div> </div> コード
posts/edit.html.erb <div class="post-index"> <h1>編集投稿</h1> <% @post.errors.full_messages.each do |message| %> <div class="form-error"> <%= message %> </div> <% end %> <div class="post-edit-container"> <%= form_for(@post, url: posts_create_path) do |f| %> <div class="posts-edit-item"> <%= f.label :title, "タイトル" %><br> <%= f.text_field :title %><br><br> <%= f.label :content, "感想・まとめ" %><br> <%= f.text_area :content %><br><br> <%= f.label :image, "画像" %><br> <%= f.file_field :image %><br><br> <%= f.submit "編集する" %> </div> <% end %> </div> </div> コード
posts/_form.html.erb <div class="post-new-container"> <%= form_for @post, url: posts_new_path do |f| %> <div class="posts-new-item"> <%= f.label :title, "タイトル(※必須)" %><br> <%= f.text_field :title %><br><br> <%= f.label :content, "感想・まとめ(※必須)" %><br> <%= f.text_area :content %><br><br> <%= f.label :image, "画像" %><br> <%= f.file_field :image %><br><br> <%= f.submit "投稿する" %> </div> <% end %> </div> コード
models/post.rb class Post < ApplicationRecord mount_uploader :image, ImageUploader validates :title, {presence: true} validates :content, {presence: true} validates :user_id, {presence: true} def user return User.find_by(id: self.user_id) end end コード
ActiveRecord::Schema.define(version: 2020_01_18_054634) do create_table "destroys", force: :cascade do |t| t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end create_table "posts", force: :cascade do |t| t.text "content" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.string "image" t.integer "user_id" t.string "title" t.float "rate" end create_table "sessions_users", force: :cascade do |t| t.string "name" t.string "email" t.string "password" t.string "image" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end create_table "users", force: :cascade do |t| t.string "name" t.string "email" t.string "password" t.string "image" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end end コード

また、routesにてupdate部分を

patch 'posts/:id/update' => 'posts#update'

のように変えると、下記のエラーが表示されます(その後、patch 'posts/:id' => 'posts#update'に戻してます)。

イメージ説明

ちなみにform_tagに戻すと投稿の編集も普通に機能するため、余計に理解できない状態にあります。むしろform_tagのままでよければ全て解決なんですが、Rails 5.1 以降は推奨されていないようなので、現場に出たことがない自分としてはそれでいいのか判断しかねています。

また、posts_create_pathに向けてフォームの送信してる事に関しては、エラー画面下部に表示されてる内容から送信先HTTP Verbの「PATCH /posts/:id/update(.:format)」がposts_create_pathのなかに入れ子(?)のようになっていたので、そこに従って書いていました。
そのように記載する事に関しては深く考えがあったわけではありませんでした。

色々とおかしな書き方をしてるとは思うのですが、現状として解決したいのは投稿内容の編集をform_forかform_withで機能させることです。

自分では解決しかねております。ご教授いただけると幸いです。どうぞ、よろしくお願いします。

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

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

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

Mugheart

2020/02/05 02:47 編集

解答にコメントした後に気づいたんですが、edit画面表示した時にレンダリングされてるHTMLってどのファイルですか? 見た感じposts/edit.html.erbだと思ったんですが、このフォームはposts_create_pathに向けて送信してるので今回のエラー表示にはなり得ないです。 エラー見た感じだとposts/_form.html.erbのフォームで送信されてますよね? どこでこれを表示していますか?
1234567

2020/02/05 04:47

コメント欄含め、アドバイスありがとうございます。 自分の認識としても、posts/edit.html.erbで表示しているつもりでした。 また、posts_create_pathに向けて送信してる事に関しては、エラー画面下部に表示されてる内容から送信先HTTP Verbの「PATCH /posts/:id/update(.:format)」がposts_create_pathのなかに入れ子(?)のようになっていたので、そこに従って書いていました。 そのように記載する事に関しては深く考えがあったわけではありませんでした。 また、自分でも色々探ってみたのですが、routesにてupdate部分を 「 patch 'posts/:id/update' => 'posts#update' 」 としてみたところ、エラー文が 「 Routing Error No route matches [PATCH] "/posts/create" Rails.root: /Users/sakamotoryou/portfolio.folder/jiro_navi.app.new Application Trace | Framework Trace | Full Trace Routes Routes match in priority from top to bottom 」 のように変わりました。 またform_forからform_tagにいったん戻して試したところ、updateも機能しました。これは何か関係があるのでしょうか。またform_tagとしてこのままにしておくのはやはり好ましくないのでしょうか?一応念のためform_withも試しましたが、結果はform_forと同じで機能しませんでした、、
Mugheart

2020/02/05 06:24

全く把握できないのでその辺りも踏まえて整理した上で質問自体を修正していただけると助かります。
1234567

2020/02/06 05:36

何点か変更を加え、質問自体を修正いたしました。お時間ある時に目を通していただけると幸いです。
guest

回答2

0

ベストアンサー

一応全て指摘します。


erb

1<%# posts/edit.html.erb %> 2 3<%= form_for(@post, url: posts_create_path) do |f| %>

編集画面のフォームなのに送信先はposts_create_pathで本当にいいんですか?
posts_create_pathで実際にどんなURLが生成されるか把握していますか?
posts_create_pathが正しいとしても引数が必要なのではないですか?
実際に生成されるURLを意識しながら名前付きURLを使用してください。
もしわかっていないのならデバッグしたりroutesコマンド使ったりして確かめた上で使ってください。


erb

1<%# posts/_form.html.erb %>

このファイルはどこで使われているのでしょう?
関係のないファイルを提示されても困るのできちんと精査してください。
一応指摘しておくとこれもフォームの送信先(名前付き)がおかしいです。


rb

1# controllers/posts_controller.rb 2 3def ensure_correct_user 4 @post = Post.find_by(id: params[:id]) 5 if @post.user_id != @current_user.id 6 flash[:notice] = "ユーザーが一致しません" 7 redirect_to("/posts/index") 8 end 9end

問題のエラー箇所ですが、おそらく
@post = Post.find_by(id: params[:id])の戻り値がnilなのでしょう。
原因はparams[:id]nilのためレコードが見つからないためでしょう。
メソッド内でデバッグすれば確かめられるはずです。
params[:id]nilになる原因は上で述べた通りです。

投稿2020/02/06 06:46

編集2020/02/06 06:55
Mugheart

総合スコア2352

1234567

2020/02/08 06:23

routesの見直しと、フォームの送信先をなしにしてみたところ、無事解決いたしました!色々と理解が足りないこともわかったので、引き続き勉強していこうと思います。ご丁寧に色々と教えてくださり、本当にありがとうございました。
guest

0

Ruby ど定番のエラーですね……

undefined method 'XXX' for nil:class

が出た場合、メソッドの親クラス(この場合でいうと @post)が nil なのです。

つまり本当の問題は、この行の一つ前、

Ruby

1@post = Post.find_by(id: params[:id])

で、@post が該当無しのため nil になっているのです。
従って理由としては、「このアクションにパラメータ idが渡ってきていない」可能性が高い。

そこに気をつけて見直してみましょう。

投稿2020/02/03 00:54

tacsheaven

総合スコア13707

1234567

2020/02/03 06:49

アドバイスありがとうございます。 posts_contrillerにて、 def create @post = Post.new(  title = params[:title] content = params[:content] image = params[:image] user_id = current_user.id ) @post.user_id = @current_user.id のように投稿時にnewメソッドの部分で、user_idの値として「@current_user.id」を入れる書き方で試しましたが、解決しませんでした。 これはそもそもensure_correct_userアクションではない他の方法で考える方が良いのでしょうか、、?一応ensure_correct_userアクションを消してみたりもしたんですが、この辺も特に解決には繋がらずでした、、 もしお分かりでしたら、ご教授いただけると幸いです。
tacsheaven

2020/02/03 07:57

全然違います。 @post が何を表すはずのものか、もう一度よく考えてください。update のアクションで更新するのは「何」ですか?
1234567

2020/02/04 04:15

ensure_correct_userアクションで内での@postの役割ということでしょうか?ensure_correct_userアクション内の@postは、edit、update、destroyに権限がないユーザーがurlを直接入力で介入されることを防ぐため、そのpostしたユーザーが@post.user_id != @current_user.idでないことを確認するため、その投稿idからユーザーを探して定義するための変数と解釈しています。 また、updateアクションにて更新するのは、title、content、imageです。 トンチンカンなことをしてるのかもしれないのですが、updateアクション内を 「 def update @post = Post.find_by(params[:id]) @post.title = params[:title] @post.content = params[:content] @post.image = params[:image] @post.user_id = @current_user.id if @post.update redirect_to("/posts/index") flash[:notice] = "投稿の編集をしました" else redirect_to("/posts/#{@post.id}/edit") end end 」 のように変更して試したりもしました。 すると、エラー文が変わりまして、 「 Routing Error No route matches [PATCH] "/posts/create" Rails.root: /Users/sakamotoryou/portfolio.folder/jiro_navi.app.new Application Trace | Framework Trace | Full Trace Routes Routes match in priority from top to bottom 」 と、なりました、、 これでもやはり@postのnilが原因で起きてるエラーなのでしょうか?
tacsheaven

2020/02/04 04:53

@post は「投稿」を示すもの、というのは理解しているのですよね。では @post = Post.find_by(params[:id]) なのですから、どの「投稿」かはどうやって渡ってきていることを期待しているのか、考えてみてください。 そしてその「投稿」を示すためのものが存在していなかったから、エラーになるのですよ。 update が呼ばれるとき、どの画面からどんなデータが渡ってきているのか見直しましょう。
1234567

2020/02/04 05:47 編集

ずいぶん長いこと見直しをしたのですが、正直お手上げ状態です、、 知識不足なのは重々承知しておりますが、具体的な修正点のアドバイスをいただけたら非常に幸いです、、
tacsheaven

2020/02/04 07:21

見直しに次ぐ見直しで訳が分からなくなったのだったら、やったことを全部元に戻して、仕切り直しましょう。 最初は編集がちゃんと動いていた、ということであれば、そこまで戻してやり直すのです。 分からないまま変にいろいろ弄くると、余計に分からなくなるだけです。あるタイミングで動かなくなったのだったら、その前との間での差異のどこかに、動かなくなる要因があるのですから、その絞り込みをしなくてはいけません。
1234567

2020/02/05 02:10

それをいうならば、form_forではなく、元のform_tagの状態がまさに機能してる状態です。 任意のmodelに基づいたformを作るときにはform_forで使い分けた方がいいとのことで修正したのですが、この場合form_tagに戻してしまっても問題ないのでしょうか? ちなみにこのポートフォリオは、求職で使うために取り組んでるモノです。現場に出たことがない未経験のため、その辺の勝手がよくわかりません。ご意見聞かせていただけたらと思います
tacsheaven

2020/02/05 02:20

Rails 5.1 以降は form_tag も form_for も統一した、form_with の使用が推奨されてますよ? ちょっと古い記述を参考にでもされましたか?
tacsheaven

2020/02/05 02:27

そして form_for の解説(それこそブログとかいろいろありますが)を見れば、form_for で url: パラメータを付けるのは特殊事例であることが分かるはずです。なぜそれを付けたのです?
Mugheart

2020/02/05 02:40 編集

> form_for で url: パラメータを付けるのは特殊事例であることが分かるはずです。なぜそれを付けたのです? routes.rbでpost "posts/create" => "posts#create"としているからでしょう。 resources を使うべきという話もあるでしょうが、意味もわからず resources を使われるよりよっぽどマシなのでそこは今回指摘するべきでは無いでしょうね。 今考えるべきはedit画面のフォームの送信先はそのURLで本当に合っているのか? 名前付きURLはその記述で本当に正しいのか、引数が必要なのでは無いですか? というあたりだと思います。
tacsheaven

2020/02/05 02:46

本来 Rails であれば、「規約に従った action, model, view の名付け方をしていれば、ルーティングは勝手に正しく行われる」ので、本当はそこから見直さないといけないような気はしてるんですけどね。 ただ質問者の理解度がそこまでたどり着いていないのではないかと。
Mugheart

2020/02/05 02:51 編集

MVCの処理の流れを理解する上ではいいアプローチかなと思います。 もちろんきちんとアプリケーションを作成する上ではRailsの「設定より規約」には従う必要がありますが。 こちらにコメントした後で気づいて追記依頼の方にもコメントしたんですが 今回のエラーは提示されてるファイル以外のところが起因な気がしますね。 提示されてるファイルにもツッコミどころは満載なんですが....。
tacsheaven

2020/02/05 02:56

それもあって、一度「元に戻してからやり直す」方がいいとは思うんですよ。
Mugheart

2020/02/05 02:59

確かに、確実に動くところまで戻した方がわかりやすいかもしれませんね。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.29%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問