user3166452
user3166452

Reputation: 105

Rails Joining multiple models on a single table

New to rails (using 4.1), making a small project management tool as a learning project. I have run into a slightly more complex model association, and I want to make sure I am on the right track here, and ask how to extend it a bit.

Consider this situation:

Models:

class Website < ActiveRecord::Base

    has_many :website_user
    has_many :users, through: :website_user
    has_many :tasks, through: :website_user

end

class User < ActiveRecord::Base

    has_many :websites, through: :website_user
    has_many :website_user

end


class WebsiteUser < ActiveRecord::Base

   belongs_to :website
   belongs_to :user
   belongs_to :role
   has_many :tasks

end

class Task < ActiveRecord::Base

    belongs_to :website_user
    has_one :website, through: :website_user

end

class Role < ActiveRecord::Base
    has_many :website_user
end

DB:

create_table "roles", force: true do |t|
    t.string "name"
end

create_table "tasks", force: true do |t|
    t.text    "description"
    t.string  "title"
    t.integer "website_user_id"
end

create_table "users", force: true do |t|
    t.string "name"
    t.string "email"
    t.string "password"
    t.string "password_hash"
    t.string "password_salt"
end

create_table "website_users", force: true do |t|
    t.integer "website_id"
    t.integer "user_id"
    t.integer "role_id"
end

create_table "websites", force: true do |t|
    t.string "name"
    t.string "url"
end

What I have going on here is basically Websites get users (team members working on sites) associated though the website_user table. That table belongs to roles, so that a team member would have a specific job on this website, and finally, tasks belong to the website_user association, so that you could swap out a user, but the task would stay associated with the role and website.

I am looking into extending it more, so that the task would be associated on website_user twice, once for the assigner, once for the assigned user of the task. However, at this point, it feels like I will have an awful lot of things attached to a big join table in the middle, and without a ton of experience under my belt, it is starting to smell like there might be a better way.

If this all looks good, how would you join the tasks to the website_user twice, once for assigner, once for assigned? Or alternatively, how would rearrange the model association?

Upvotes: 2

Views: 571

Answers (3)

Misha Slyusarev
Misha Slyusarev

Reputation: 1383

A simple solution that first comes to head is to keep assigner and assignee ids in Task model.

Migration AddAssigneeAssignerToTask

class AddAssigneeAssignerToTask < ActiveRecord::Migration
  change do
    add_reference :task, :assignee, index: true
    add_reference :task, :assigner, index: true
  end
end

Adding belongs_to into Task model

class Task < ActiveRecord::Base

  belongs_to :assignee, class: 'WebsiteUser'
  belongs_to :assigner, class: 'WebsiteUser'

  has_one :website, through: :assignee
end

Modifying WebsiteUser

class WebsiteUser < ActiveRecord::Base

  belongs_to :website
  belongs_to :user
  belongs_to :role

  has_many :assigned_tasks, class_name: 'Task', foreign_key: 'assigner_id'
  has_many :received_tasks, class_name: 'Task', foreign_key: 'assignee_id'
end

So afterwards you can use it like this

@website_user.assigned_tasks # => []  
@website_user.received_tasks # => [Task1, Task2]

BUT

If you think to add some different functionality to either assigner or assignee, you should consider to use STI or MTI

Upvotes: 1

Danny Ocean
Danny Ocean

Reputation: 1121

I can not advice you in the database design, but you can assign users twice using an option called class_name. You can read more here: http://guides.rubyonrails.org/association_basics.html#belongs-to-association-reference

But you will have to add additional foreign_key to your Tasks model as well.

And I also advice you to read following chapter of M. Hartle book, as it have really good explanation between relationships of models: https://www.railstutorial.org/book/following_users#cha-following_users

Upvotes: 1

ilan berci
ilan berci

Reputation: 3881

class Task < ActiveRecord::Base
  belongs_to :assignee, class_name: WebsiteUser, foreign_key:website_user_id
  belongs_to :assigner, class_name: WebsiteUser, foreign_key:assigner_user_id
end

class WebsiteUser < ActiveRecord::Base
  has_many :assigned_tasks, class_name: Task, inverse_of: :assignee, dependent: :destroy, foreign_key: :website_user_id
  has_many :tasks_assigned, class_name: Task, inverse_of: assigner, dependent: :destroy, foreign_key: :assigned_user_id
end

You will have to add another foreign key in your tasks table..

just a starting point but this should get you going..

Upvotes: 1

Related Questions