※いただいたコメントを参考に内容を修正しました。
元の記事は下部にありますが、ページの表示速度の観点から非推奨となります。
本編
データベースに登録されるデータにはプライマリキーのIDが付けられますが、そのデータを削除した場合はそのIDも存在しなくなります。
商品の詳細ページや会員の詳細ページのURLには:id
があり、この数値を変更することでその数値のIDに該当する詳細ページが表示されますが、もし、存在しないIDが入力された場合には
ActiveRecord::RecordNotFound
Couldn't find モデル名 with 'id'=入力された数値
というエラーが発生します。
今回行う処理は「存在するIDなら表示し、存在しなければリダイレクトを行う」という物です。
環境
Ruby 2.5.7
Rails 5.2.4
処理の手順
今回はapplication_controllerにアクションを定義し、リダイレクトさせたい各コントローラーでそのアクションをbefor_actionとして設定する方法で進めていきます。
状況としては商品の詳細ページでURLに商品IDを入力した時の処理と仮定します。
class ApplicationController < ActionController::Base
def exist_item?
unless Item.find_by(:id params[:id])
redirect_to root_path
end
end
unless文を使い、条件式がfalse
の場合に中の処理を行います。
今回の場合だと、find_byメソッドを使ってItemのIDの中から入力された数値(params[:id]
)を探してヒットするかどうかを条件にし、ヒットしなかった場合(=false
)は中の処理であるredirect_to root_path
が実行されると言うものです。
次に商品のコントローラー内に先ほどapplication_controller.rbで定義したexist?
をbefore_action
に設定します。
class ItemsController < ApplicationController
# URLに:idを含むアクションを指定
before_action :exist_item?, only[:show, :edit, :update, :destroy]
...
def show
...
end
...
end
この時にオプションのonly:
で指定するアクションは、表示を伴う:show
や:edit
だけでもいいのですが、:update
や:destroy
も一応追加しておきます。
(なくてもいいですが、フォームで無理やりパラメータを変更して送信された場合に冒頭のエラーが表示されます。)
これで各アクションの前に、入力されたitemのIDが存在するかどうかを検証するアクション(exist_item?
)が実行されます。
まとめ
修正前の弱点であった表示速度が大幅に改善される見込みです。
今後もいただいたコメントは随時検証して行きたいと思います!
最後まで読んでいただきありがとうございました!
なお修正前の記事は以下になりますが、非推奨です。
処理の手順(※非推奨)
今回はapplication_controllerにアクションを定義し、リダイレクトさせたい各コントローラーでそのアクションをbefor_actionとして設定する方法で進めていきます。
状況としては商品の詳細ページでURLに商品IDを入力した時の処理と仮定します。
class ApplicationController < ActionController::Base
def exist?
Item.all.each do |item|
if item.id == params[:id].to_i
return
end
end
redirect_to root_path
end
end
まず、each文を使って商品のテーブルから全ての商品情報を順番に取得します。
eachの中では1巡ごとにそのitemのid
が入力された数値と同じかどうかを検証します。
(.to_i
としているのは、入力された数値は文字列型として認識されているのでitem.id
と同じ整数型に揃える必要があるからです。
item.id = 0
params[:id] = "0"
if item.id == params[:id] => false
)
each文の中でitemのIDと入力された数値(params[:id]
)が一致した場合、if
の中の処理、return
を行います。
return
が行われると、それいこうのプログラムはスキップし、アクションを終了させるので、redirect_to root_path
は実行されません。
逆に、each文が終了し、その中で入力された数値とitemのIDが一度も一致しなかった場合は、その後に続くredirect_to root_path
が実行され、任意のページにリダイレクトを行います。
class ItemsController < ApplicationController
# URLに:idを含むアクションを指定
before_action :exist?, only[:show, :edit, :update, :destroy]
...
def show
...
end
...
end
次に商品のコントローラー内に先ほどapplication_controller.rbで定義したexist?
をbefore_action
に設定します。
この時にオプションのonly:
で指定するアクションは、表示を伴う:show
や:edit
だけでもいいのですが、:update
や:destroy
も一応追加しておきます。
(なくてもいいですが、フォームで無理やりパラメータを変更して送信された場合に冒頭のエラーが表示されます。)
これで各アクションの前に、入力されたitemのIDが存在するかどうかを検証するアクション(exist?
)が実行されます。
弱点
この方法はレコードの数(今回で言うところの商品数)が多くなればなるほど処理に時間がかかるようになります。
商品が1,000個あればeach文はMAX1,000回回ることになるので、詳細ページなどの表示がかなり重くなってしまいます。
他にいいやり方があるはずなので、見つかり次第更新します┏○
コメント等でも教えていただけると幸いです!