3
3

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 5 years have passed since last update.

Rails sessionで特定の要素だけ削除

Last updated at Posted at 2018-08-23

前回のおさらい

Rails session情報削除ボタンの作り方 より、session情報全てを削除するコードは実装出来ました。
しかし、個別の要素を削除したいとなると不便なので前回のコードの記述を書き改めて実装したいと思います。
現在の状態

cart.controller.rb
def destory
  reset_session
  redirect_to cart_index_path
end

全ての失敗例

cart.controller.rb
def destory
  @cart = reset_session #全て消えてしまうので意味がない
end
cart.controller.rb
def destory
 @cart.destroy #Undefined method destroy for Array<***>
end
cart.controller.rb
def destory
  session.delete(@cart) #missin id require idとなる
end
cart.controller.rb
  before_action :set_cart, only: [:destroy]

  def destroy
    @cart.destroy
    redirect_to cart_index_path
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_cart
      @cart = session[:cart].map { |item_id| Item.find(item_id) }
    end

全てにおいて

No method error. Undefined method 'destroy' for Array

という結果が帰ってくる。
カート機能を実装した他の例におけるdef destroyコードをチェックしても@hoge.destroyの記述だけで上手くいっているので、根本的な何かが違うのだろうとprivateメソッドを追記してもアウト。

削除ボタンのリンクは正しいか?

<%= button_to "Clear Cart", cart_path(session[:cart]), method: :delete %>

もともとの記述では,cart_path(session[:cart])と記述した後に、controllerにてreset_sessionと記述することでカートの中身を全て削除出来ていた。
しかし、今回の場合ではindex.html.erbにてカートの配列を並べた際に削除ボタンを隣に作成したかったので

<% @cart.each do |item| %>
<%= button_to "Clear Cart", cart_path(item), method: :delete %><br>
<% end %><br>

cart_path(item)と記述を変更していた。
改めてrailsが吐いたエラーを確認すると

Parameters:
{"_method"=>"delete", "authenticity_token"=>**, "id"=>"8"}

"id=>"8"が送られてきている。これは商品にdbが振ったIDでしかなく、もしやこの記述がおかしいのではないだろうか?

linkのpath変更

<%= link_to 'Destroy', item, method: :delete, data: { confirm: 'Are you sure?' } 

商品を作成する際のitem.controllerに対応するindex.html.erbにおける記述を参考に

<%= button_to "Clear Cart", item, method: :delete %>

itemに変更してみた。というのもループで回す際の変数が|item|と同じなのでこれでいけるだろうという甘い誤算。

Couldn't find Item with 'id'=16
@cart = session[:cart].map { |item_id| Item.find(item_id) }

結果はsessionではなく大元の商品自体を削除してしまう結果となる。それもそのはず、指定したitem|item|の変数などではなく、routesitem deleteパスに飛ばすものだったからだ。

そしてドツボにハマる

存在しないitem_idをひたすら参照するようになってしまったので、なんとか元の状態に戻そうと
reset_session,session[:cart]=nil,rails db:migrate:resetを実行。
参照しているデータベース自体をリセットし、なおかつsession情報を削除すれば元に戻るだろうと予測。

ActiveRecord::RecordNotFound in CartController#index
Couldn't find Item with 'id'=16

しかし結果は変わらない。
ブラウザに出現したterminalでデバッグを試してみる

>> puts session[:cart]
=> nil
>> puts Item.all
=> nil

sessionもデータベースも空なのになぜitem_idの情報だけは頑なに保持しているのか・・?

cart_controller.rb
def index
    if session[:cart] != nil
      session[:cart].sort!
      @cart = session[:cart].map { |item_id| Item.find(item_id) }
    end
end

controllerにてsession[:cart] != nilとsessionに中身がある時のコードを中身がないにも関わらず参照し始めるということは、元々このコードで動いていたことはひとまずおいて、条件分岐を正しく設定出来ていないのかもしれない。

else
      @cart = nil
end

を追記して、@cartは確認した際もnilであったが改めてnilとし、indexページに飛べるように設定。

ActiveRecord::RecordNotFound in CartController#index
Couldn't find Item with 'id'=16

しかし結果は変わらない。
奇怪至極に思えてならないのが

Request
Parameters:
None

エラーが出た際のrailsページに表記されているこの三行である。
何もパラメーターに入っていないのなら、何もリクエストしていないのなら
session[:cart] != nilのコードを実行し始めるのはありえない筈。
こうなってしまってはもはや打つ手が無い。

一時解決と思った・・

全てはlinkのpathを変更してしまったことで、ドツボにハマってしまったので、だったらいっそ振り出しに戻ってしまおうということで

git checkout -- 任意のファイル

全ての変更内容を取り消して初期状態に巻き戻し!
これで、打つ状況がなくなった詰み状況から離脱。
改めてlinkのpathに代入する、適切なコードを検討
いざrails sで元の状態に戻ったか確認。

ActiveRecord::RecordNotFound in CartController#index
Couldn't find Item with 'id'=16

呪いに取り憑かれたのか・・ファイルを全て初期化した上でこのエラーが出現するということはデータベースでもない、コードでもない、セッションでもない。
となると、Cookieがitem_idをひたすら保持し続けているのだろうか。

解決

Chromeブラウザで特定のキャッシュやCookieだけを削除する方法を参考に
Chrome => Developer tool => Application => Clear Storage => Clear Cite dataの手順でキャッシュを削除
・・・するとようやくエラーが消えた!!!!万歳!!!

改めてlinkのpath記述をどう変更すれば良いのか

<%= button_to "Clear Cart", cart_path(item), method: :delete%><br>

cart@cart,cart_pathではmissing keys require idとエラーが出たので引数に@cartをループで回している際の変数を入れることに。
ループで回しているわけなので、それぞれ対応した一つの要素を認識するはず・・

cart_controller.rb
def destroy
    reset_session
    # @cart = session[:cart].map { |item_id| Item.find(item_id) }
    @cart.destroy
    # @cart =Item.find(@item_id)
    # @cart.destroy
    redirect_to cart_index_path
  end

最初の@cart = ・・・で始まる文はundefined method "map"が帰ってきた為、@cart.destroyのみに。そして結果が

undefined method `destroy' for nil:NilClass

うーむ。なぜdestroyメソッドをcart_controllerでは使えないのだろう。

link pathの指定は誤っていない説

エラーを引き起こした際にrails web consoleで適当に@cartなどと打ってやると正しくパラメーターを取得していたのでpathに記述ミスがあるわけではないと判断。
ではメソッドの指定が異なるのだろうと

<%= button_to "Clear Product", cart_path(item), method: :patch, 
data: { confirm: 'Are you sure?' } %><br>

destroyメソッドではなくupdateメソッドを用いることに。

そして再び詰む


def update
    # 配列の中から特定の要素を削除
    # session.delete([:item_id]) ダメ
    # @cart.update ダメ
    # session[:cart].delete([:id]) ダメ
    # @cart.delete_at(params[:item_id]) ダメ
    @cart = session[:cart].map { |item_id| Item.find(item_id) }
    x = params[:id].to_i
    y = x - 1
    @cart.delete_at(y)
    redirect_to cart_index_path
    # session[:cart] 中身を確認
    # =>["1", "1", "3", "3"]
    #@cart 中身を確認
    # => [#<Item id: 1, name: "1", description: "1", price: 1, created_at: "2018-08-23 09:53:01", updated_at: "2018-08-23 09:53:01", image: "masamune.jpg">, #<Item id: 1, name: "1", description: "1", price: 1, created_at: "2018-08-23 09:53:01", updated_at: "2018-08-23 09:53:01", image: "masamune.jpg">, #<Item id: 3, name: "2", description: "2", price: 2, created_at: "2018-08-23 09:54:19", updated_at: "2018-08-23 09:54:19", image: "transcendence.jpg">, #<Item id: 3, name: "2", description: "2", price: 2, created_at: "2018-08-23 09:54:19", updated_at: "2018-08-23 09:54:19", image: "transcendence.jpg">]
  end

destroyメソッドがundefined method 'destroy' for Arrayを返した様に、updateを用いても結果は変わらなかった。ググった結果、nil for nilClassであるならば参照先の@cartを定義していないからだという記述も見かけたが、定義してこの結果が帰ってくるのでは話にならない。
scaffoldコマンドで作成したMVCならupdateが使えてここで使えない理由が全く検討もつかない。

rails web consoleで上手く動いたdelete_atメソッドを用いて
配列が受け取った値のparams[:id]より削除を実装してみたが、redirect_toで更新しても削除ボタンで消したはずの項目が残っている。
delete,shift,popなど配列の要素を変更するメソッドを全て試してみた結果も全く同じ。
消したはずなのに反映されていないとはどういうことなのか。

terminalにて確認

Started PATCH "/cart/1" for 127.0.0.1 at 2018-08-25 16:19:19 +0900
Processing by CartController#update as HTML
  Parameters: {"authenticity_token"=>"uCSoRxnlGlRIT/Z4YHFcppIA91GQpnIqbLXMtTZnOnsFi+1X4TNMGN49eBZO+qzgd+kCcZovUw48uosD2pdLjQ==", "id"=>"1"}
  Item Load (0.4ms)  SELECT  "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
  ↳ app/controllers/cart_controller.rb:26
  CACHE Item Load (0.0ms)  SELECT  "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
  ↳ app/controllers/cart_controller.rb:26
  Item Load (0.4ms)  SELECT  "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2  [["id", 3], ["LIMIT", 1]]
  ↳ app/controllers/cart_controller.rb:26
Redirected to http://localhost:3000/cart
Completed 302 Found in 5ms (ActiveRecord: 0.8ms)

terminalをみるとしっかり実行しているように見える。しかしSELECTの実行結果に変化がみられない上に三回もSELECTを行っているということはそもそもdelete_atメソッドが機能していないということではないのだろうか。

そこで正しい挙動例を探した

自動生成されたコードで問題なく動いている場合を見れば、メソッドの命令に対する正しい挙動を確認できるだろうと、item.controller.rbに移動。

item.controller.rb
def update
respond_to do |format|
      if @item.update(item_params)
        format.html { redirect_to @item, notice: 'Item was successfully updated.' }
        format.json { render :show, status: :ok, location: @item }
      else
        format.html { render :edit }
        format.json { render json: @item.errors, status: :unprocessable_entity }
      end
    end
end
Started PATCH "/items/6" for 127.0.0.1 at 2018-08-25 16:47:38 +0900
Processing by ItemsController#update as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"/kBs+Rr5q7QrdhS2NnjYtqV6+tqygHMlZxYwVWId13iXa+39oo3kyetT3gXfBhHgd3gHl7zwOD6POe0yijpXMw==", "item"=>{"name"=>"5", "description"=>"5", "price"=>"6"}, "commit"=>"Update Item", "id"=>"6"}
  Item Load (0.5ms)  SELECT  "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2  [["id", 6], ["LIMIT", 1]]
  ↳ app/controllers/items_controller.rb:67
   (52.8ms)  BEGIN
  ↳ app/controllers/items_controller.rb:44
  Item Update (181.5ms)  UPDATE "items" SET "price" = $1, "updated_at" = $2 WHERE "items"."id" = $3  [["price", 6], ["updated_at", "2018-08-25 07:47:38.429656"], ["id", 6]]
  ↳ app/controllers/items_controller.rb:44
   (72.8ms)  COMMIT
  ↳ app/controllers/items_controller.rb:44
Redirected to http://localhost:3000/items/6
Completed 302 Found in 361ms (ActiveRecord: 307.5ms)

SQL文のSELECTUPDATEになっていることを確認。

最終解決


def update
@cart = session[:cart].delete_at(session[:cart].find_index(params[:id]))
redirect_to cart_index_path
end

パラメーターで送られているidの数値はそのまま

controller
def index
    if session[:cart] != nil
      session[:cart].sort!
      @cart = session[:cart].map { |item_id| Item.find(item_id) }
    end
  end

indexに定義されている@cartItem.findで付与されているItemの情報を保持している為、idはそのままItem(商品)を特定する番号であり、また

@cart = session[:cart].delete_at(session[:cart].find_index(params[:id]))

session[:cart]が保持する情報はindexで再定義されたものがsession[:cart]に代入されている為、パラメーターにあるid@cartの商品を特定するidであるということは、代入されているsession[:cart]でも同じことが言える為、find_indexメソッドを用いることで任意の特定の一つのsessionを削除することが可能になるのだと分かった。

二週間近く悩んだ実装ではあったが、パラメータで送られている情報やparamsを用いてその情報をどう処理すれば目標の実装が出来る様になるかのとてもいい学習機会になった...。

3
3
0

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
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?