Ben Smith
Ben Smith

Reputation: 819

Ruby on Rails: Add form for another model on a form

Within my Ruby on Rails application I am trying to implement a relationship between Group and Contact, whereby one group can contain many contacts and one contact can be part of many groups. I am using a model called Contactgroup to deal with this relationship, and so the tables are:

Group (id, name)
Contact (id, firstname, surname)
Contactgroup (group_id, contact_id)

With example data being:

Groups:
ID      Name
1       Singers
2       Drummers

Contacts:
ID      Firstname    Surname
1       Freddy       Mercury
2       Roger        Taylor
3       Kurt         Cobain
4       Dave         Grohl

Contact Groups:
Group_ID        Contact_ID
1               1
1               3
1               4
2               2
2               4

What I am trying to do is get it so that when a user creates a group, they can select the contacts that they want to add to that group. This means that there is the group form, whereby the user types the group name, and on this form I want to display checkboxes for each of the user's contacts so that the user can select the contacts they want to add to the group, and when they click submit the new group will be saved in the Group table and the new contact group records will be saved in the Contactgroup table.

This is the app/views/groups/_form.html.erb code:

<%= form_for @group do |f| %>

  <% if @group.errors.any? %>
    <div id="error_explanation">
      <h2>
        <%= pluralize(@group.errors.count, "error") %> prohibited this group from being saved:
      </h2>
      <ul>
        <% @group.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <p>
    <%= f.label :name %><br>
    <%= f.text_field :name %>
  </p>

  <h2>Add members:</h2>
    <%= form_for([@group, @group.contactgroups.build]) do |f| %>
      <p>
        <%= f.collection_check_boxes(:contact_id, @contacts, :id, :firstname) %>
      </p>
      <p>
        <%= f.submit %>
      </p>
    <% end %>
  <p>
    <%= f.submit %>
  </p>

<% end %>

On here you can see the code I am trying to use to do this:

<h2>Add members:</h2>
    <%= form_for([@group, @group.contactgroups.build]) do |f| %>
      <p>
        <%= f.collection_check_boxes(:contact_id, @contacts, :id, :firstname) %>
      </p>
      <p>
        <%= f.submit %>
      </p>
    <% end %>
  <p>
    <%= f.submit %>
  </p>

<% end %>

I have got this from rails guides (http://guides.rubyonrails.org/getting_started.html) but I get the error undefined methodcontactgroups' for #` and don't think this will give me what I want.

My routes file is:

Rails.application.routes.draw do
  get 'sessions/new'
  get 'sessions/create'
  get 'sessions/destroy'
  resources :users
  get 'welcome/index'
  root 'welcome#index'
  resources :contacts

  resources :groups do
    resources :contactgroups
  end
  resources :contactgroups


  get 'sessions/new'
  get 'sessions/create'
  get 'sessions/destroy'
  controller :sessions do
    get  'login' => :new
    post 'login' => :create
    get 'logout' => :destroy
  end 
end

My groups_controller:

class GroupsController < ApplicationController
    def index
        @groups = Group.where(user_id: session[:user_id])
    end
    def show
        @group = Group.find(params[:id])
        @members = Contactgroup.where(group_id: @group.id)
    end
    def new
        @group = Group.new
        @contacts = Contact.where(user_id: session[:user_id])
    end
    def edit
        @group = Group.find(params[:id])
    end
    def create
        @group = Group.new(group_params)
        @group.user_id = session[:user_id]
        if @group.save
            redirect_to @group
        else
            render 'new'
        end
    end
    def update
        @group = Group.find(params[:id])
        if @group.update(group_params)
            redirect_to @group
        else
            render 'edit'
        end
    end
    def destroy
        @group = Group.find(params[:id])
        @group.destroy
        redirect_to groups_path
    end
    private
    def group_params
        params.require(:group).permit(:name, :user_id)
    end
end

And contactgroups_controller:

class ContactgroupsController < ApplicationController
    def destroy
        @contactgroup = Contactgroup.find(params[:id])
        @contactgroup.destroy
        redirect_to(:back)
    end
end

My models are as follows:

Contact.rb:

class Contact < ActiveRecord::Base
end

Group.rb:

class Group < ActiveRecord::Base
end

Contactgroup.rb:

class Contactgroup < ActiveRecord::Base
  belongs_to :contact
  belongs_to :group
end

There must be a simple solution to solve this as I assume it is commonly done on other systems, but I am not sure how to do this.

Can someone please help.

Upvotes: 1

Views: 1926

Answers (3)

Ben Smith
Ben Smith

Reputation: 819

This was much simpler than initially thought/suggested.

What I needed to do was change the models to:

Contactgroup
  belongs_to :contact
  belongs_to :group

Contact
  has_many :contactgroups
  has_many :groups, through: :contactgroups, :dependent => :destroy

Group
  has_many :contactgroups
  has_many :contacts, through: :contactgroups, :dependent => :destroy

In the groups_controller I needed to change the new method and params to:

def new
    @group = Group.new
    @group.contactgroups.build
end

private
def group_params
    params.require(:group).permit(:name, :user_id, { contact_ids: [] })
end

And then add the following line of code into app/views/groups/_form.html.erb:

<%= f.collection_check_boxes :contact_ids, Contact.where(user_id: session[:user_id]), :id, :firstname ,{ prompt: "firstname" } %>

This provides me with a checkbox for each contact, and allows contactgroup records to be created from the group form.

Upvotes: 1

Taryn East
Taryn East

Reputation: 27747

Ok so the issue is very simple. You are calling @group.contactgroups but you haven't actually set up that association on the group model yet. only have associations set up from the contactgroup side. So you can do contactgroup.group but not group.contactgroups

Your best bet is to actually model this as habtm - as I mentioned earlier. This is how you'd do that:

Contact.rb:

class Contact < ActiveRecord::Base
  has_and_belongs_to_many :groups 
end

Group.rb:

class Group < ActiveRecord::Base
  has_and_belongs_to_many :contacts
end

Note: you still have the concept of the contact-group for HABTM but using Rails standard naming it would be in your database as the contacts_groups table. Then you could build your forms that way.

With a quick google, here's a S/O question on using checkboxes with HABTM (haven't vetted it for usefulness to your situation): Rails 4 - checkboxes for has_and_belongs_to_many association

Using HABTM is Rails standard practice for lots of very good reasons. It really does actually fit your situation (honest!) and it does not actually break the requirement you have of wanting to see it in the SQL (seriously!).

Give it a try first :) I can tell you how to break Rails conventions... but it's generally well-understood that you shouldn't break conventions until you know what the conventions are there for.

Upvotes: 0

Priyank Gupta
Priyank Gupta

Reputation: 421

You cannot use form inside form. The correct way to use collection_check_boxes is following.

Replace

<%= form_for([@group, @group.contactgroups.build]) do |f| %>
  <p>
    <%= f.collection_check_boxes(:contact_id, @contacts, :id, :firstname) %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>

With just

<p>
  <%= f.collection_check_boxes(:contact_ids, @contacts, :id, :firstname) %>
</p>

Upvotes: 1

Related Questions