Rails: Use a collection of checkboxes to add/remove entries on a different table with ActiveRecord

I have three objects: Contact, Sector, and Contact_sector.

Contact contains an id and some other irrelevant (non-reference) columns

Sector contains an id and sector column with ~10 editable entries

Contact_sector has a contact_id reference and a sector_id reference. In my mind I imagine that every sector that applies to some contact can be found here, and if a sector is un-applied it is removed from here.

I want to have a collection of checkboxes in the contact _form formed from list of entries in :sectors, but updating the form with certain boxes ticked adds/removes entries from :contact_sectors.

Where am I going wrong?

UPDATED: Fixed strong_params to permit sectors, now I am unable to find the sectors by id ActiveRecord::RecordNotFound (Couldn't find Sector with ID=["1", ""] for Contact with ID=1)

Models:

class Contact < ActiveRecord::Base
    has_many :contact_sectors
    has_many :sectors, through: :contact_sectors

    accepts_nested_attributes_for :contact_sectors, :reject_if => :all_blank, :allow_destroy => true
    accepts_nested_attributes_for :sectors, :reject_if => :all_blank, :allow_destroy => true
end

class Sector < ActiveRecord::Base
    has_many :contact_sectors
    has_many :contacts, through: :contact_sectors

    def name_with_initial
      "#{sector}"
    end
end

class ContactSector < ActiveRecord::Base
    belongs_to :contact
    belongs_to :sector
end

View:

<%= f.fields_for(:sectors) do |s| %>
    <%= s.collection_check_boxes :id, Sector.all,
                                 :id, :name_with_initial,
                                 { prompt: true }, { class: 'form-control' } %>
<% end %>

Controller

def edit
    @contact.sectors.build
end

def contact_params
    #Not sure if I need something like this or not
    params['contact']['sectors'] = params['contact']['sectors']['id'].split(',')

    params.require(:contact).permit(:firstname, :lastname,
                                contact_sectors_attributes: [:id],
                                sectors_attributes: [:_destroy, {:id => []}])
end

Upvotes: 1

Views: 1353

Answers (2)

models

class Contact < ActiveRecord::Base
  has_many :sectors, through: :contact_sectors
  has_many :contact_sectors

  accepts_nested_attributes_for :sectors
end

class Sector < ActiveRecord::Base
  has_many :contacts, :through => :contact_sectors
  has_many :contact_sectors
end

class ContactSector < ActiveRecord::Base
  belongs_to :contact
  belongs_to :sector
end

view

<%= form_for(@contact) do |f| %>
    <% Sector.all.each do |sector| %>
        <%= check_box_tag "contact[sector_ids][]", sector.id, f.object.sectors.include?(sector) %>
        <%= sector.sector %>
    <% end %>
<% end %>

controller

def update
    #To make sure it updates when no boxes are ticked
    @contact.attributes = {'sector_ids' => []}.merge(params[:contact] || {})
end

def contact_params
    params.require(:contact).permit(:firstname, :lastname, sector_ids: [])
end

Recommended reading:

http://millarian.com/rails/quick-tip-has_many-through-checkboxes/

Rails 4 Form: has_many through: checkboxes

Upvotes: 1

max
max

Reputation: 102046

Instead of creating the join model explicitly you can just declare the relationship as has_many through: and let ActiveRecord handle the join model:

class Contact < ActiveRecord::Base
  has_many :contact_sectors
  has_many :sectors, through: :contact_sectors
  accepts_nested_attributes_for :sector, 
    reject_if: :all_blank, allow_destroy: true
end

class Sector < ActiveRecord::Base
  has_many :contact_sectors
  has_many :contacts, through: :contact_sectors
end

class ContactSector < ActiveRecord::Base
  belongs_to :contact
  belongs_to :sector
end

<%= form_for(@contact) do |f| %>
  <%= f.fields_for(:sectors) do |s| %>
    <%= s.collection_check_boxes :id, Sector.all, 
        :id, :name_with_initial, 
        { prompt: true }, { class: 'form-control' } %>
  <% end %>
<% end %>

Upvotes: 1

Related Questions