前提・実現したいこと
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
回答1件
あなたの回答
tips
プレビュー