1

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
  • 1
    Don't you think this is exactly what you need ? This is a Many-to-Many Relation with a bridge table `contact_sector` that defines the relation b/w `Contact` and `Sector`. When some sector checkbox is unchecked that sector is not suppose to have an association with the contact and hence the association is removed from the bridge table . – Kinaan Khan Sherwani Aug 29 '15 at 07:00
  • 1
    You need to whitelist `:_destroy` to allow deleting. `contact_sectors_attributes: [:id, :sector_id, :contact_id, :_destroy]` – max Aug 29 '15 at 07:16
  • @KinaanKhanSherwani Alright, it's good to know structure-wise everything is okay. The error now seems to be with the params. These are the contact_sectors attributes with two ticked `contact_sectors_attributes"=>{"0"=>{"sector_id"=>["1", "3", ""]}` and it says sector_id is unpermitted. What syntax to I use to permit it? I tried adding something like `sector_attributes: [:id]` but that of course didn't work. – Misha Griffiths-Prasolova Aug 29 '15 at 07:31
  • Thanks @max I made sure that was included! – Misha Griffiths-Prasolova Aug 29 '15 at 07:32

2 Answers2

1

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 %>
max
  • 96,212
  • 14
  • 104
  • 165
  • I see a SELECT that looks promising `Sector Load (1.0ms) SELECT "sectors".* FROM "sectors" INNER JOIN "contact_sectors" ON "sectors"."id" = "contact_sectors"."sector_id" WHERE "contact_sectors"."contact_id" = ? [["contact_id", 1]]` but no checkboxes are rendered. – Misha Griffiths-Prasolova Aug 29 '15 at 08:10
  • Unfortunately I'm in the middle of moving so I don't have any time to spend on this right now but I did something very similar for this question http://stackoverflow.com/questions/31064695/rails-4-rolify-gem-user-roles-arent-being-updated-via-ui/31064910#31064910 – max Aug 29 '15 at 08:33
  • Thanks for all of your help, I'll revisit this in the morning with a fresh mind. Good luck with your move! – Misha Griffiths-Prasolova Aug 29 '15 at 08:37
1

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

Community
  • 1
  • 1