1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

データベースに存在しないIDがURLに入力された場合の処理

Last updated at Posted at 2020-09-01

※いただいたコメントを参考に内容を修正しました。
元の記事は下部にありますが、ページの表示速度の観点から非推奨となります。

本編

データベースに登録されるデータにはプライマリキーの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を入力した時の処理と仮定します。

application_controller.rb
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に設定します。

items_controller.rb
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を入力した時の処理と仮定します。

application_controller.rb
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が実行され、任意のページにリダイレクトを行います。

items_controller.rb
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回回ることになるので、詳細ページなどの表示がかなり重くなってしまいます。

他にいいやり方があるはずなので、見つかり次第更新します┏○
コメント等でも教えていただけると幸いです!

1
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?