konchanxxx / menta

MENTAのタスク管理用リポジトリ
0 stars 0 forks source link

複数チェックボックス機能の実装にあたってのエラー #64

Closed creamstew closed 5 years ago

creamstew commented 5 years ago

概要

複数選択可能なチェックボックスを作成し、選択した値のみをそれぞれDBに保存したいが一番最後にチェックをつけた項目しかDBに保存されない。 Userモデルとuser_genreモデルは一対多の関係

実現したいこと

1.選択した値のみをそれぞれDBに保存したい。 2.updateする際にチェックを外した場合はレコードをデリートしたい

困っていること

1.選択した値のみをそれぞれDBに保存できない。 2.updateする際にチェックを外した際にレコードをデリートしたい

下記の記事が自分のやりたいことと同じなのですが、記事内のviewとストロングパラメータの記載部分が理解できていません。

https://ja.stackoverflow.com/questions/14891/rails%E3%81%AE%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E3%81%A7%E3%83%9E%E3%83%AB%E3%83%81%E3%83%81%E3%82%A7%E3%83%83%E3%82%AF%E3%83%9C%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%AE%9F%E7%8F%BE%E3%81%97%E3%81%9F%E3%81%84

ストロングパラメーター

params[:user][:car_selections_attributes].each { |index, hash| \ 
        hash[:_destroy] = hash[:maker].blank? }

view

  <% car_selections = @user.car_selections.map {|record| [record.maker, record] }.to_h %>
  <% CarSelection.makers.each_with_index do |(maker, value), index| %>
    <label>
      <% car_selection = car_selections[maker] %>
      <% name_prefix = "user[car_selections_attributes][#{index}]" %>
      <%= hidden_field_tag "#{name_prefix}[id]", car_selection.try(:id) %>
      <% checked = car_selection.present? && !car_selection.marked_for_destruction? %>
      <%= check_box_tag "#{name_prefix}[maker]", maker, checked %>
      <%= maker %>
    </label>
  <% end %>

自分のコード

user.rb

class User < ApplicationRecord
  has_many :user_genres, dependent: :destroy
  accepts_nested_attributes_for :user_genres, allow_destroy: true
end

user_genre.rb

class UserGenre < ApplicationRecord
  belongs_to:user
end

user_controllerのストロングパラメーター

    def user_params
      params.require(:user).permit(:name, :age, user_genres_attributes: [:genre])
    end

_form.rb

<%= form_with(model: user, local: true) do |form| %>
  <% if user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
      <% user.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :name %>
    <%= form.text_field :name %>
  </div>

  <div class="field">
    <%= form.label :age %>
    <%= form.number_field :age %>
  </div>

  <%= form.fields_for :user_genres do |genre| %>
    <%= genre.check_box :genre, {}, "BEAUTY", nil %> <%= genre.label :genre, "美容" %>
    <%= genre.check_box :genre, {}, "OUTDOOR", nil %> <%= genre.label :genre, "アウトドア" %>
    <%= genre.check_box :genre, {}, "FASHION", nil %> <%= genre.label :genre, "ファッション" %>
    <%= genre.check_box :genre, {}, "COOKING", nil %> <%= genre.label :genre, "料理" %>
    <%= genre.check_box :genre, {}, "COOKING", nil %> <%= genre.label :genre, "音楽" %>
  <% end %>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

問題となっている箇所の予想

一番最後にチェックをつけた値に書き換わってしまうため。

問題となっているアプリケーションのGitHub URL

https://github.com/creamstew/multi_check_box

creamstew commented 5 years ago

下記の記事の見よう見まねで機能自体は実装することができました。

https://ja.stackoverflow.com/questions/14891/rails%E3%81%AE%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E3%81%A7%E3%83%9E%E3%83%AB%E3%83%81%E3%83%81%E3%82%A7%E3%83%83%E3%82%AF%E3%83%9C%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%AE%9F%E7%8F%BE%E3%81%97%E3%81%9F%E3%81%84

しかし下記のコードがあまり理解できず、もし可能でしたら解説いただけないでしょうか。

ストロングパラメーター

params[:user][:car_selections_attributes].each { |index, hash| \ 
        hash[:_destroy] = hash[:maker].blank? }

view

  <% car_selections = @user.car_selections.map {|record| [record.maker, record] }.to_h %>
  <% CarSelection.makers.each_with_index do |(maker, value), index| %>
    <label>
      <% car_selection = car_selections[maker] %>
      <% name_prefix = "user[car_selections_attributes][#{index}]" %>
      <%= hidden_field_tag "#{name_prefix}[id]", car_selection.try(:id) %>
      <% checked = car_selection.present? && !car_selection.marked_for_destruction? %>
      <%= check_box_tag "#{name_prefix}[maker]", maker, checked %>
      <%= maker %>
    </label>
  <% end %>
konchanxxx commented 5 years ago

確認しますー

konchanxxx commented 5 years ago

元記事がviewで変数代入していて割と汚いコードだなと思ったらチェリー本書いたito sanの回答だった

konchanxxx commented 5 years ago

元記事を参考に解説しますね。

params[:user][:car_selections_attributes].each { |index, hash|  hash[:_destroy] = hash[:maker].blank? }

まずこの部分はストロングパラメータではないです。リクエストパラメータを前処理している感じですね。params.requireの処理をした結果がストロングパラメータです。 ネスト(入れ子状態になった)したハッシュをeachというメソッドで処理しています。 ハッシュのeachについては下記を参照ください each, each_pair (Hash) - Rubyリファレンス

ハッシュをeachで処理するとkeyとvalueのペアがループで処理できるようになります。 どういったリクエストパラメータが出力されているかはターミナルで実際にリクエストを発生させてみて出力をみるとかbinding.pryで実行を止めて確認してみると良いです:bow: paramsの中身を見れば確認できます。

hash[:_destroy] = hash[:maker].blank?

の部分はチェックボックスで選択されていなかったらhash[:maker]のvalueとしてnilとか空文字が入ってくるのでその場合にtrueを設定して削除対象にするようなことをしています。

多分こんな感じのイメージでリクエストパラメータがきてるのでmakerに値が入っていなかったらtrueを設定するみたいなことをしていると思います:bow:

{
  "user" => {
    "car_selections_attributes" => {
      "0" =>  {
        "maker" => 'toyota'        
      },
      "1" => {
        "maker" => 'nissan'
      }
      "2" => {
        "maker" => ""
      }
    }
  }
}

続いてviewの方ですが

<% car_selections = @user.car_selections.map {|record| [record.maker, record] }.to_h %>

の部分は配列をhashに変換している感じですね

【Ruby】Array から Hash を作る方法7選(a.k.a. やっぱり Array#zip はかわいいよ) | RakSul Tech Blog

<% CarSelection.makers.each_with_index do |(maker, value), index| %>

の部分はまたhashをeach_with_indexというメソッドで処理している感じですね ハッシュからeach_with_indexでkeyとvalとindexを取り出す。 - bismar's blog

ざっくり説明しました:bow:不明な点あれば追記してください:bow:補足しますmm

creamstew commented 5 years ago

丁寧な解説ありがとうございます。 また、質問したにも関わらずご返信遅れ申し訳ありません。

params[:user][:car_selections_attributes].each { |index, hash| hash[:_destroy] = hash[:maker].blank? } についてはおかげさまで理解できました。 viewの部分で追加で何点か質問させてください。

creamstew commented 5 years ago

<% checked = car_selection.present? && !car_selection.marked_for_destruction? %> の!が文頭に来ているのはどういった意味でしょうか?

creamstew commented 5 years ago

<% CarSelection.makers.each_with_index do |(maker, value), index| %> のvalueはmakerを配列のインデックスでいちいち取り出さないために記載している意味合いでしょうか?

creamstew commented 5 years ago

<%= hidden_field_tag "#{name_prefix}[id]", car_selection.try(:id) %> のcar_selection.try(:id)はどういう意味で記載しているのかがわかりません。

konchanxxx commented 5 years ago

!が文頭に来ているのはどういった意味でしょうか?

論理の否定ですね変数がtrueの時にfalse, falseの時にtrueを返すようになります:bow: https://blog.codecamp.jp/posts-34186

konchanxxx commented 5 years ago

<% CarSelection.makers.each_with_index do |(maker, value), index| %> のvalueはmakerを配列のインデックスでいちいち取り出さないために記載している意味合いでしょうか?

これは #each_with_index というメソッドのブロック変数の書き方ですね hashのkeyとvalueをブロック変数にマッピングするために使っています:bow: http://bismar.hatenablog.com/entry/2012/12/01/215111

konchanxxx commented 5 years ago

<%= hidden_field_tag "#{name_prefix}[id]", car_selection.try(:id) %>

tryが対象のkey(今回は:id)が存在する時にそのメソッドを実行して存在しない場合はnilを返すというメソッドなのでcar_selectionが存在する時にそのIDを返して存在しない場合はnilを設定している感じですね

イメージはこんな感じ

[1] pry(main)> class Hoge
[1] pry(main)*   def id
[1] pry(main)*     'unko!!'
[1] pry(main)*   end
[1] pry(main)* end
=> :id
[2] pry(main)> Hoge.new.try(:id)
=> "unko!!"
[3] pry(main)> nil.try(:id)
=> nil
creamstew commented 5 years ago

ありがとうございます。おかげでしっかりと理解することができました!!