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

###前回のおさらい
[Rails session情報削除ボタンの作り方](https://qiita.com/shiroux/items/b9a2888b56565e9f4b89)　より、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
```

全てにおいて

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

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

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

```erb
<% @cart.each do |item| %>
<%= button_to "Clear Cart", cart_path(item), method: :delete %><br>
<% end %><br>
```
`cart_path(item)`と記述を変更していた。
改めてrailsが吐いたエラーを確認すると

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

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

###linkのpath変更

```erb
<%= link_to 'Destroy', item, method: :delete, data: { confirm: 'Are you sure?' } 
```
商品を作成する際の`item.controller`に対応する`index.html.erb`における記述を参考に

```erb
<%= button_to "Clear Cart", item, method: :delete %>
```
`item`に変更してみた。というのもループで回す際の変数が`|item|`と同じなのでこれでいけるだろうという甘い誤算。

```terminal
Couldn't find Item with 'id'=16
@cart = session[:cart].map { |item_id| Item.find(item_id) }
```
結果はsessionではなく大元の商品自体を削除してしまう結果となる。それもそのはず、指定した`item`は`|item|`の変数などではなく、`routes`の`item delete`パスに飛ばすものだったからだ。

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

```terminal
ActiveRecord::RecordNotFound in CartController#index
Couldn't find Item with 'id'=16
```
しかし結果は変わらない。
ブラウザに出現したterminalでデバッグを試してみる

```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に中身がある時のコードを中身がないにも関わらず参照し始めるということは、元々このコードで動いていたことはひとまずおいて、条件分岐を正しく設定出来ていないのかもしれない。

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

```terminal
ActiveRecord::RecordNotFound in CartController#index
Couldn't find Item with 'id'=16
```
しかし結果は変わらない。
奇怪至極に思えてならないのが

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

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

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

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

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

###解決
[Chromeブラウザで特定のキャッシュやCookieだけを削除する方法](https://productforums.google.com/forum/#!topic/chrome-ja/JDRi8use1bA)を参考に
Chrome => Developer tool => Application => Clear Storage => Clear Cite dataの手順でキャッシュを削除
・・・するとようやくエラーが消えた！！！！万歳！！！

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

```erb
<%= 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`のみに。そして結果が

```terminal
undefined method `destroy' for nil:NilClass
```
うーむ。なぜdestroyメソッドをcart_controllerでは使えないのだろう。

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

```erb
<%= button_to "Clear Product", cart_path(item), method: :patch, 
data: { confirm: 'Are you sure?' } %><br>
```
`destroy`メソッドではなく`update`メソッドを用いることに。

###そして再び詰む

```ruby

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にて確認
```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
```

```terminal
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文の`SELECT`が`UPDATE`になっていることを確認。

###最終解決

```ruby

def update
@cart = session[:cart].delete_at(session[:cart].find_index(params[:id]))
redirect_to cart_index_path
end
```
パラメーターで送られている`id`の数値はそのまま

```ruby
controller
def index
    if session[:cart] != nil
      session[:cart].sort!
      @cart = session[:cart].map { |item_id| Item.find(item_id) }
    end
  end
```
`index`に定義されている`@cart`に`Item.find`で付与されている`Item`の情報を保持している為、`id`はそのまま`Item（商品）`を特定する番号であり、また

```ruby
@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`を用いてその情報をどう処理すれば目標の実装が出来る様になるかのとてもいい学習機会になった...。