3

Wrestling my head to model following

  1. User has many Contact categories (family/friends)
  2. One Contact category has many contacts
  3. One contact can be in one or more contact categories.

I end up with following, but I still believe, there must be better solution outhere.

class User < ActiveRecord::Base  
    has_many :contact_groups  

    def get_all_contacts  
        self.contact_groups.each do |group|  
        contacts << group.users  
    end  
end  

class ContactGroup < ActiveRecord::Base  
    has_and_belongs_to_many :users  
end 
Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367
Jakub Kuchar
  • 1,665
  • 2
  • 23
  • 39

2 Answers2

6

Assumptions

  • This is a system for many users, not just one, so you need to specify the Owner on the Contact and Group records with a foreign key.
  • A Contact can belong to multiple Groups, and a Group has multiple contacts, so this is a has_and_belongs_to_many relationship.

Migrations

Create Users table:

class CreateUsers < ActiveRecords::Migrations
  def change
    create_table :users do |t|
      t.text :username
    end
  end
end

Create Contacts table. Has a foreign key for the User the Contact is owned by as well as the User that the Contact refers to.

class CreateContacts < ActiveRecord::Migrations
  def change
    create_table :contacts do |t|
      t.references :owner
      t.references :user
    end
  end
end

Create Groups table. Also has a foreign key for the User that the group belongs to.

class CreateGroups < ActiveRecord::Migrations
  def change
    create_table :groups do |t|
      t.references :owner
      t.text :name
    end
  end
end

Create the table for the has_and_belongs_to_many relationship between Contacts and Groups. Has two foreign keys; one for the Contact and the other for the Group.

class CreateContactsGroups < ActiveRecord::Migrations
  def change
    create_table :contacts_groups do |t|
      t.references :contact
      t.references :group
    end
  end
end

Models

The User model. A User has many Contacts and the foreign key on the contacts table that relates a contact to a user is the 'owner_id' column. Same thing for Groups.

class User
  has_many :contacts, :foreign_key => 'owner_id'
  has_many :groups, :foreign_key => 'owner_id'
end

The Contact model. A Contact belongs to an Owner and we are using the 'User' model for the Owner relationship. Also, a Contact refers to another User (or belongs to, Rails parlance, which is kind of confusing in this circumstance), but :user matches the name of the model, so we don't need to specify that like we do for :owner. Finally, the has and belongs to many relationship with Groups.

class Contact
  belongs_to :owner, :class_name => 'User'
  belongs_to :user
  has_and_belongs_to_many :groups
end

The Group model. A Group belongs to an Owner and has and belongs to many Contacts.

class Group
  belongs_to :owner, :class_name => 'User'
  has_and_belongs_to_many :contacts
end

We don't need to create a Model for the contacts_groups table since we wont be accessing it directly and instead will only be accessing it through either a Contact or a Group.

Results

This would allow you to do stuff like the following:

@user.contacts # all of the user's contacts
@user.groups # all of the user's groups
@user.groups.find(id).contacts # all the contacts in a group
@user.contacts.find(id).groups # all of the groups that a contact belongs to

Adding Contacts to a Group

This is in response to @benrmatthews comment asking what the views would look to add a Contact to a Group.

There are actually a bunch of different ways to achieve this, but the general idea is that you need to pass the Contact id and Group id to a controller action that will then create an entry in the contact_groups table to indicate that the contact has been added to the group.

Your routes might look like this:

resources :contact_groups, only: [:create]

Then you will have a controller that looks something like this:

class ContactGroupsController < ApplicationController
  def create
    @contact = Contact.find(params[:contact_id])
    @group = Group.find(params[:group_id])
    @contact.groups << @group
  end
end

Now all you need are forms which pass the contact_id and group_id to the controller action. Here's what that would look like:

# This is for adding the contact to a group
form_tag contact_groups_path do
  hidden_field_tag :contact_id, @contact.id
  select_tag :group_id, options_from_collection_for_select(Group.all, :id, :name)
  submit_tag "Add to Group"
end

This creates a form with a hidden field that contains the contact_id and a select field that will display a list of group names. When this form gets submitted, it will pass the contact_id and group_id to the controller action, which then has enough information to do it's thing.

Richard Jones
  • 4,760
  • 3
  • 27
  • 34
2

You can use has_many :through Association

class User
    has_many :contacts
    has_many :contact_groups, :through => :contacts
end

class Contact
    belongs_to :user
    has_many :contact_groups
end

class ContactGroup
    belongs_to :contacts
end

this will work.

abhas
  • 5,193
  • 1
  • 32
  • 56
  • Thank you for a prompt answer. But I would like to keep model Contact as a reference back to User model since this is kind of social network and users will have contacts only from User model and no one else. Adding belongs_to: user to Contact class will solve my pain? – Jakub Kuchar May 11 '12 at 10:31
  • I am updating the above answer with new structure as i think it will be better in your situation. – abhas May 11 '12 at 10:50