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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Ruby on Rails 5

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

Ruby

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

Q&A

解決済

1回答

951閲覧

Railsアプリ上で他人としてログインするとエラーになる(Couldn't find Post with 'id' =)

退会済みユーザー

退会済みユーザー

総合スコア0

Ruby on Rails 5

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

Ruby

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

0グッド

2クリップ

投稿2019/08/01 13:52

編集2019/08/02 02:10

前提・実現したいこと

Railsで作成したマイクロポストアプリにRedisを用いてランキング機能を実装しました。無事に記事タイトルとPV数の表示を完了することができたのですが、次に他人としてログインして他の投稿が編集削除出来ないか確認しようとしたところエラーが発生してしまいました。

ちなみに現在のユーザーは二人作成していて、Redisでのランキング機能を実装した時は、ユーザーIDが1の方でログインをしていました。

Railsは一ヶ月間勉強しており、**Redisに関してはほぼ素人です。Redisに関する下記のコードは理解が乏しいかと思います。**どうしてもアプリを完成させたいので、助言をいただけたら幸いです。

追記:Idが存在していないポストをコンソール上で作成したら一旦、動くようにはなったのですがそれでも投稿を削除しようとすると同じエラーが発生してしまいます。
**私の予測ですが、どうやら削除した瞬間にランキングに表示すべきポストが入った変数がおかしくなっている気がします。**そのままランキング表示から削除したポストは外したいのですが、それもどうやったらいいのか見当がつきません…。引き続き自分でのリサーチは続けます。

追記:案の定、データを全て削除するとランキングの一番目にあったポストのIdが見つからないと言う風にエラーが表示されました。原因はわかったのですが解決方法がいまだに掴めていません。

追記:考えた結果、削除を実行した際にモデルのデータからは情報が削除されて、一方でRedisからはデータが削除されていないためエラーになることが分かりました。そこで下記のコードをpostsコントローラのdestroyアクションに追加したのですが、再びエラーが発生しています。

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

destroyアクション等にコードを追加するまでは下のエラーが発生していました。

Couldn't find Post with 'id'=7

そしてランキングデータを取得するアクションの下の行を
@ranking_posts = ids.map{| id |Post.find(id)}
次のように書き換えました。
@ranking_posts = ids.map{| id |Post.find_by(id: id)}

また、Redisのソート済セット型のデータから日付をキー、ポストのIdをメンバーを指定してスコアを削除するようにpostsコントローラーのdestroyアクションに下の行を追加しました(@postはビフォーアクションで取得できています。)
REDIS.zrem "posts/daily/#{Date.today.to_s}", @post.id

そしてその結果発生しているエラーが下です。

undefined method `user_id' for nil:NilClass

エラーの該当箇所は間違ったユーザーに編集削除を許可するためのアクションです。

def ensure_correct_user @post = Post.find_by(id: params[:id]) if @post.user_id != current_user.id flash[:notice] = "Not yours" redirect_to "/" end end

該当のソースコード

下はHomeコントローラーです。

Ruby

1class HomeController < ApplicationController 2 before_action :set_ranking_data, only:[:top] 3 4 def top 5 @posts = Post.all.order(created_at: :DESC) 6 end 7 8 def set_ranking_data 9 #5件のランキングデータを取得 10 ids = REDIS.zrevrangebyscore "posts/daily/#{Date.today.to_s}", "+inf", 0, limit: [0, 5] 11 @ranking_posts = ids.map{| id |Post.find_by(id: id)} 12 end 13end

下はPostsコントローラーです。

RUBY

1class PostsController < ApplicationController 2 before_action :set_post, only: [:show, :edit, :update, :destroy] 3 before_action :authenticate_user!, only:[:edit, :update, :destroy] 4 before_action :ensure_correct_user, only:[:edit, :update, :destroy] 5 before_action :set_ranking_data, only:[:show, :edit] 6 7 # GET /posts 8 # GET /posts.json 9 def index 10 @posts = Post.all.order(created_at: :DESC) 11 end 12 13 # GET /posts/1 14 # GET /posts/1.json 15 def show 16 @post = Post.find_by(id: params[:id]) 17 REDIS.zincrby "posts/daily/#{Date.today.to_s}", 1 ,@post.id 18 end 19 20 def set_ranking_data 21 #5件のランキングデータを取得 22 ids = REDIS.zrevrangebyscore "posts/daily/#{Date.today.to_s}", "+inf", 0, limit: [0, 5] 23 @ranking_posts = ids.map{| id |Post.find_by(id: id)} 24 end 25 26 # GET /posts/new 27 def new 28 @post = Post.new 29 end 30 31 # GET /posts/1/edit 32 def edit 33 @post = Post.find_by(id: params[:id]) 34 end 35 36 # POST /posts 37 # POST /posts.json 38 def create 39 @post = Post.new(post_params) 40 @post.user_id = current_user.id 41 respond_to do |format| 42 if @post.save 43 format.html { redirect_to @post, notice: 'Post was successfully created.' } 44 format.json { render :show, status: :created, location: @post } 45 else 46 format.html { render :new } 47 format.json { render json: @post.errors, status: :unprocessable_entity } 48 end 49 end 50 end 51 52 # PATCH/PUT /posts/1 53 # PATCH/PUT /posts/1.json 54 def update 55 respond_to do |format| 56 if @post.update(post_params) 57 format.html { redirect_to @post, notice: 'Post was successfully updated.' } 58 format.json { render :show, status: :ok, location: @post } 59 else 60 format.html { render :edit } 61 format.json { render json: @post.errors, status: :unprocessable_entity } 62 end 63 end 64 end 65 66 # DELETE /posts/1 67 # DELETE /posts/1.json 68 def destroy 69 @post.destroy 70 respond_to do |format| 71 format.html { redirect_to "/", notice: 'Post was successfully destroyed.' } 72 format.json { head :no_content } 73 end 74 75# この部分でRedisのデータからポストを削除したいと思っています 76 REDIS.zrem "posts/daily/#{Date.today.to_s}", @post.id 77 end 78 79 private 80 # Use callbacks to share common setup or constraints between actions. 81 def set_post 82 @post = Post.find_by(id: params[:id]) 83 end 84 85 # Never trust parameters from the scary internet, only allow the white list through. 86 def post_params 87 params.require(:post).permit(:title, :content) 88 end 89 90 def ensure_correct_user 91 @post = Post.find_by(id: params[:id]) 92 if @post.user_id != current_user.id 93 flash[:notice] = "Not yours" 94 redirect_to "/" 95 end 96 end 97end 98

ちなみにランキングを表示しようとしているHTMLは下のように記述しています。

HTML

1<% @ranking_posts.each do |post| %> 2 <li><%= link_to post.title, "/posts/#{post.id}" %><span>(<%= "#{REDIS.zscore("posts/daily/#{Date.today.to_s}", post.id).to_i}" %>PV)</span></a></li> 3 <% end %>

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

ruby 2.5.3
rails 5.2.3

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

winterboum

2019/08/02 01:36

これだけの情報では助言難しいです。 そのエラーはどのfileのどこで起きたのか エラーが起きたaction(method)はどのviewのどこから呼ばれたのか そのviewを書きだしたのはどのcontrollerか を開示して下さい。 コメント欄ではなく質問本文にお願いしまs
chelsy7110

2019/08/02 01:39

Redisはわかりませんが、論理削除できるようにparanoia等のgemを使っていて、Redisはそんなもの関係なくランキングを取得しているように思えるのですが、論理削除のgemを使っていますか?
退会済みユーザー

退会済みユーザー

2019/08/02 02:11

ご返信ありがとうございます。 質問の方を編集してなるべく多くの情報を追加しました。非常に長い質問文になってしまい恐縮ですが、目を通していただけたら幸いです。
guest

回答1

0

ベストアンサー

findではidが見つからない時エラーになりますがfind_byでは要素がない場合にnilが返ります。

@ranking_posts = ids.map{| id |Post.find_by(id: id)}

これで単純なエラーの回避はできますが、ランキングに削除済みPostが残ります。

ちゃんとやるならPostを削除するとき一緒に
Redisのランキングのメンバーから当該Postを削除する必要があります。

REDIS.srem "posts/daily/#{Date.today.to_s}", post_id

投稿2019/08/02 01:30

hellomartha

総合スコア329

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2019/08/02 02:17

ご回答ありがとうございます! おっしゃるように@ranking_postsの方はそのまま @ranking_posts = ids.map{| id |Post.find_by(id: id)} に書き換えました。 そして、REDISからの削除についてはソート済セット型を使っていること、またメンバーとして@post変数のIdを使っていることを考慮してpostsコントローラのdestroyアクションに以下のように記述しました。 ``` REDIS.zrem "posts/daily/#{Date.today.to_s}", @post.id ``` しかし、次はfind_byによってnilクラスが返されたことによって undefined method `user_id' for nil:NilClass といったエラーが返されるようになりました。 destroyアクションの時点でポストは削除できているように感じるので非常に困惑しています。 引き続き自分で考えますが、何かアドバイスを頂ければ幸いです。
hellomartha

2019/08/02 02:31

都度Redis削除していればnilデータがランキングに含まれることはないはずですが・・・ 現状のREDIS内のランキングデータを確認して destroyアクション変更前に削除したデータが残っていないか確認してください。 もしPostコントローラー以外でPost削除しているような部分があれば Postモデルのbefore_destroyとかでRedis削除してください。
退会済みユーザー

退会済みユーザー

2019/08/02 02:35

素早いご回答ありがとうございます! 参考にさせていただきます。 さきほど、destroyアクションの REDIS.zrem "posts/daily/#{Date.today.to_s}", @post.id という記述を   @post.destroy respond_to do |format| format.html { redirect_to "/", notice: 'Post was successfully destroyed.' } format.json { head :no_content } end の上に移動させたところ期待通りに動作するようになりました。 REDISに関する記述は@post変数に関するアクションより上に記載しないといけないルールなどは存在するのでしょうか? おっしゃる通りで、このコードがきちんと動いてなかったためにランキング表示の方でエラーが発生していたのだと思います…。 hellomarthaさんの回答が無ければ絶対にここまでたどり着けていませんでした。本当にありがとうございます!
hellomartha

2019/08/02 02:55

>REDISに関する記述は@post変数に関するアクションより上に記載しないといけないルールなどは存在するのでしょうか? このようなルールは存在しないはずです。 なぜ記述位置が関係するのかは不可解ですが動作したならよかったです
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問