21137


## Activeレコードではないオブジェクト(Hoge)の項目をマルチチェックボックスでUserの子テーブルHogeUserに登録、更新させたい

  * UserクラスとHogeUserクラスは一対多の親子クラスです。
  * HogeオブジェクトはActiveRecordでは無くid,nameといった項目を持っています。
  * Userクラスの保存と共にHogeでチェックが入った項目をHogeUserレコードに複数登録したいです。(チェックが入ってないのは登録されないようにしたいです。)
  * バージョンアップ前のruby1.9.3,rails3.2を使ってます。

まだ中途半端で動いてないですが以下が現在の実装です。

### Userクラス

[code]

    class User < ActiveRecord::Base
      has_many :hoge_users
    
      attr_accessible ::hoge_users_attributes
    
    
      accepts_nested_attributes_for :hoge_users
    
      validates_associated :hoge_users
      validate :should_have_at_least_one_hoge_user
    
      def should_have_at_least_one_hoge_user
        errors.add(:hoge_users, 'should have at least one.') if hoge_users.empty?
      end
    
[/code]

### HogeUserクラス

[code]

    class HogeUser < ActiveRecord::Base
      belongs_to :hoge
      attr_accessible :hoge, :name, :hoge_id
      validates :hoge_id, :presence => true
    end
    
[/code]

### UsersControllerクラス

[code]

    class UsersController < ActionController::Base
      def new
        @user = User.new(User::DEFAULT, :as => :admin)  
        respond_to do |format|
          set_objects_for_new_view
          ～
    
      def create
        @user = User.new(params[:user], :as => :admin)
        hoge_ids = params[:hoge_users_attributes]
        unless hoge_ids == nil
          hoge_ids.each do |hoge_id|
            @user.hoge_users.new(:hoge_id => hoge_id)
          end
        end
        respond_to do |format|
          if @user.save
          ～
    
    
       def set_objects_for_new_view
         @hoges = GET::Zone.all
         @hoge_users = @user.hoge_users.new
    
[/code]

### _form.html.erb

[code]

    <%= f.fields_for :hoge_users do |hoge_user_f| %>
        <%= render :partial => 'hoge_user_form', :locals => {:f => hoge_user_f} %>
    <% end %>
    
[/code]

### `_hoge_user_form.html.erb`

[code]

    <% @hoges.each do |hoge| %>
      <tr>
        <%= hoge.name %>
        <%= check_box_tag 'hoge_users_attributes[]', hoge.id %>
      </tr>
    <% end %>
    
[/code]

  * [ググってみましたが](http://blog.sanojimaru.com/post/20811733359/rails3%E3%81%A7hasmany-through%E3%81%AA%E9%96%A2%E9%80%A3%E3%82%92checkbox%E3%81%A7%E7%99%BB%E9%8C%B2%E3%81%99%E3%82%8B)Activeレコード以外から取得する例がありませんでした。HogeをActiveRecordに入れる必要がありますでしょうか？⇒回答で解決。コード修正済み

  * 【追記】上記のようにコード修正すると登録までできるようになりました。ただチェックボックス未選択時のバリデーションが動いてくれません（hoge.idが空）。  
何か間違っている箇所が有ったりしますでしょうか？⇒回答で解決。コード修正済み

### 【追記２】詳細更新画面について

  * 頂いたご回答で登録処理は動作するようになりました。続いて詳細更新画面が上手く動きません。
  * 詳細でHogeUserに登録されていたものはチェックがついてる状態にしたいです。
  * チェックボックスを外したら削除されるようにしたいです。
  * 新しくチェックを入れたものは追加されるようにしたいです。

### UsersControllerクラス

詳細出す前にHogeオブジェクトを取るようにしました。

[code]

    class UsersController < ActionController::Base
      def show
        @user = target_user
        @hoges = GET::Zone.all
    
        respond_to do |format|
          format.html { render :partial => 'show' }
          ～
      def update
        @user = target_user
    
        respond_to do |format|
          if @user.update_attributes(params[:user], :as => :admin)
          ～
    
[/code]

### _show_user.html.erbビュー

check_boxにhoge_idを指定して登録されていたものはチェックがついてる状態になりました。  
`"hoge_accounts_attributes"=>{"hoge_id"=>"チェックボックスで選択したhoge.id",
"0"=>{"id"=>"登録されたTBL.id"}}}`  
paramsは↑のものが渡るようになりました。update前にhoge.idを取得してhoge_users.newする形になるのでしょうか？

[code]

        <th>ホゲ</th>
        <td>
        <%= f.fields_for :hoge_users do |hoge_user_f| %>
          <% @hoges.each do |hoge| %>
              <%= hoge.name %>
              <%= hoge_user_f.check_box :hoge_id, {:name =>'user[hoge_users_attributes][hoge_id]'}, hoge.id %>
          <% end %>
        <% end %>
        </td>
      </tr>
    
[/code]

### Userクラス

削除には`allow_destroy`オプションつけて_destroyというパラメータを[渡さなければいけない](http://d.hatena.ne.jp/akm/20090714/1247557847)とあったのですがチェックを外すと_destroyパラメータが付く形にしたいです。

[code]

    class User < ActiveRecord::Base
      accepts_nested_attributes_for :hoge_users, :allow_destroy => true
    
[/code]


CC BY-SA 3.0
2016-01-20T09:33:41.740
21125
2016-01-26T10:24:16.343
2016-01-22T10:35:55.813
10851
10851
question
ruby-on-rails ruby rails-activerecord
railsでマルチチェックボックスになった子テーブル登録、更新を実現したい


マルチチェックボックスというのは、その実、同じフォームの中で同じ`name`を持った複数個のチェックボックスでしかないです。(ただし、 name
の末尾が`[]`であるようにする) なので、自分でチェックボックスタグを記述し、(それは、質問文にもあるように `check_box_tag`
で実現できるはずです) Controller で適切な保存処理を記述すれば、 Hoge を ActiveRecord にする必要はないはずです。

# HTML の話

HTML 上のチェックボックスは、そのフォームの submit
が行われる際に、もし自分がチェックされていれば`NAME=value`のクエリパラメータを送付し、チェックされていなければ何も送信しません。

[code]

    <input type="checkbox" name="vehicle[]" value="Bike"> Bike <br>
    <input type="checkbox" name="vehicle[]" value="Car"> Car <br>
    
[/code]

たとえば上記のような input タグたちが form タグの中に存在したときに、submit ボタンを押した時に何が起きるかというと、

  * どっちも選択されていないとき: `vehicle[]`についてのクエリパラメータはなし
  * Bike のみが選択されているとき: `vehicle[]=Bike` がクエリパラメータに含まれる
  * Car のみが選択されているとき: `vehicle[]=Car` がクエリパラメータに含まれる
  * Bike も Car も選択されているとき: `vehicle[]=Bike&vehicle[]=Car`がクエリパラメータに含まれる

として、POST が実行されます。

# rails の話

rails では、名前の末尾が`[]`であるクエリパラメータが複数到着した場合には、 params
を構築する際にそれらをまとめ上げて、配列にしてプログラムに渡してくれます。これは、 rails に限らず PHP
でもそのようにクエリパラメータを処理します。

なので、

[code]

    <% @hoges.each do |hoge| %>
      <tr>
        <%= hoge.name %>
        <%= check_box_tag 'hoge_users_attributes[]', hoge.id %>
      </tr>
    <% end %>
    
[/code]

で書いた後、上の input を含んでいる form の submit を受け付けるコントローラーで、
`params[:hoge_users_attributes]` とかしてやると、チェックが付いていた `Hoge#id`
の一覧(配列)が取得できるはずです。

一つ注意は、上記のような動作をするため、ひとつもチェックがついていない場合には、クエリ自体が何一つ飛んでいないので、
`param[:hoge_users_attributes]` が nil になります。その分岐処理を記述する必要があります。

# 参考

<http://blog.scimpr.com/2015/04/11/rails%E3%81%A7%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%81%A7%E8%A4%87%E6%95%B0%E9%81%B8%E6%8A%9E%E3%81%99%E3%82%8B%E3%81%A8%E3%81%8D%E3%80%9Ccheck_box_tag/>

<http://ruby-rails.hatenadiary.com/entry/20150113/1421149061#view-helpers-
check_box>


CC BY-SA 3.0
2016-01-20T18:12:25.130
21137
2016-01-20T18:12:25.130
754
21125
answer


Userモデルにある、`validates_associated :hoge_users`
は、関連モデルが存在する場合に、validationが実行されますので、存在しないとき（フォームから渡されたデータに、関連モデルが含まれないとき）は、validtionが実行されません。

もしも、関連モデルが **1つ以上含まれていることを保証する場合** は、次のように、カスタムvalidationを追加する必要があります。

Model:

[code]

    class User < ActiveRecord::Base
      has_many :hoge_users
      validates_associated :hoge_users
      validate :should_have_at_least_one_hoge_user
    
      def should_have_at_least_one_hoge_user
        errors.add(:hoge_users, 'should have at least one.') if hoge_users.empty?
      end
    end
    
    class HogeUser < ActiveRecord::Base
      belongs_to :user
    end
    
[/code]

Pry:

[code]

    [1] pry(main)> user = User.new
    [2] pry(main)> user.valid?
    [3] pry(main)> user.errors.full_message
    => ["HogeUsers should have at least one."]
    
[/code]

なお、上記コードは、カスタムvalidationの内容を伝えるために必要最低限な実装にしていますのでご注意ください。

以上、ご参考になれば幸いです。


CC BY-SA 3.0
2016-01-22T00:25:56.963
21161
2016-01-22T00:25:56.963
14015
21125
answer


[こちら](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)を参考に詳細更新画面作成できましたm(_
_)m

### UsersControllerクラス

チェックボックスが付いてないものはdestroyをつけるようにしました。

[code]

    class UsersController < ActionController::Base
      def show
        @user = target_user
        @hoges = GET::Hoge.all
    
        respond_to do |format|
          format.html { render :partial => 'show' }
          ～
      def update
        @user = target_user
        params[:user][:hoge_users_attributes].each {|index,hash|  hash[:_destroy] = hash[:name].blank?}
    
        respond_to do |format|
          if @user.update_attributes(params[:user], :as => :admin)
          ～
    
[/code]

### _show_user.html.erbビュー

[code]

        <tr>
          <th>ホゲ</th>
          <td>            
            <% hoge_users = @user.hoge_users%>
            <% @hoges.each_with_index do |(hoge, value), index| %>
              <% hoge_user = hoge_users.select{|z| z.name==hoge.name}.first %>
              <% name_prefix = "user[hoge_users_attributes][#{index}]" %>
              <%= hidden_field_tag "#{name_prefix}[id]", hoge_user.try(:id) %>
              <%= hidden_field_tag "#{name_prefix}[hoge_id]", hoge.id %>
              <% checked = hoge_user.present? && !hoge_user.marked_for_destruction? %>
              <%= check_box_tag "#{name_prefix}[name]", hoge.name, checked %>
              <%= hoge.name %>
            <% end %> 
          </td>
        </tr>
      <% end %>
    
[/code]

### Userクラス

[code]

    class User < ActiveRecord::Base
      accepts_nested_attributes_for :hoge_users, :allow_destroy => true
      validate :validate_hoge_user, :if => "!admin?"
      def validate_hoge_user
        errors.add(:base, :must_choose_hoge) if hoge_users.reject(&:marked_for_destruction?).size < 1
      end
    
[/code]


CC BY-SA 3.0
2016-01-25T23:02:12.860
21309
2016-01-26T10:24:16.343
2017-04-13T12:52:39.113
-1
10851
21125
answer