"belongs_to" two different people

I have a People table and a Tasks table. Some People are Managers, some
are Workers. Each Task has a Manager and a Worker.

How do I define the relationships? I tried a STI setup, where Managers
and Workers inherit from People, and People has a type column. In my
Task table, I have worker_id and manager_id rows. Then:

task belongs_to worker
task belongs_to manager
worker has_many tasks
manager has_many tasks

But when I ask for task.worker, or task.manager, it throws an error.

How can I do this without separate Worker and Manager tables?

I think this could work:

class Task < ActiveRecord::Base
belongs_to :worker
belongs_to :manager
end

class Person < ActiveRecord::Base
end

class Manager < Person
has_many :tasks
end

class Worker < Person
has_many :tasks
end

task = Task.create
manager = Manager.create
worker = Worker.create

task.manager = manager
task.worker = worker

You don’t need to create two tables, only to files more… extending the model that is related to the table.

Daniel Gaytán

I forgot to mention that you must to define a default scope for getting first all Managers or Workers in each case and a before create filter to set the role the person you are creating will have or whatever:

#manager.rb

before_create :set_manager_role

default_scope :where => {:role => “manager”}

private

def set_manager_role
self.role = “manager”
end

The same for the worker model

script/generate migration add_roles_mask_to_users roles_mask:integer
rake db:migrate

# in models/user.rb

ROLES = %w[Manager Worker]

def roles=(roles)
  self.roles_mask = (roles & ROLES).map { |r| 2**ROLES.index(r) }.sum

end

def roles
  ROLES.reject do |r|

    ((roles_mask || 0) & 2**ROLES.index(r)).zero?

  end
end

def is?(role)
  roles.include?(role.to_s)

end

Or install the gem that does this [http://rubygems.org/gems/role_model](http://rubygems.org/gems/role_model)

i like this olution because roles can grow easily

Not exactly the same thing but I think you can relate.

In an application I am working on right now I have 2 separate tables,
one for Roles (manager, worker, etc) and another one for Users
(people). I chose that design instead of keeping a list of roles in
the code because it keeps my application 'cleaner' in the sense that
if a new role is needed a person with enough authority to do so can
just add the new role to the DB through a maintenance page. The User
record has a column for 'role_id' that you can easily maintain also
through the user pages.

That will work as long as the OP doesn't ever need a person to be a
manager on one task, and a worker on another.

Alternatively, create two foreign keys in Tasks and link them both to
the Person model:

class Task < ActiveRecord::Base
  belongs_to :worker, :foreign_key => "worker_id", :class_name => "Person"
  belongs_to :manager, :foreign_key => "manager_id", :class_name => "Person"
end

No STI is required, so there's more flexibility for the Person model.

oh... and if you do have some criteria that determine who can be
assigned to be a manager or worker, then then you can add a
:conditions element too. So if you have a "people_permitted_to_manage"
table, you can join to that to filter the person_id values that can go
into manager_id in Task.

HTH

This works, as far as finding both workers and managers for any given
task. All CRUD look good.

I also need to sort on either the worker name or the manager name, or
both. I can't make this work, since there is no actual field named
"worker.name", for example.

My code looks like

Task.find(:all, :order => "worker.name, manager.name")

Since both workers and managers are actually in the Person table, I
can't see how this could work.

Michael Pavling wrote:

Try :
Task.find(:all, :include => [:worker, :manager], :order =>
"worker.name, manager.name")

although that's off the top of my head and I'm not able to look up the
syntax of multiple includes right this second.

HTH

No luck. The query ends with "ORDER BY worker.name, manager.name", and
as these are not columns in the Tasks table, it won't work.

Of course, it is possible to do this in SQL (i.e., sort tasks based on
the names in the People table, with separate sorts for managers and
workers).

SELECT t.job_number, p1.name, p2.name FROM tasks AS t LEFT JOIN people
AS p1 ON t.worker_id = p1.id LEFT JOIN people AS p2 ON t.manager_id =
p2.id ORDER BY p1.name, p2.name

Here, p1.name will be the worker name, and p2.name will be the manager
name.

The question is, should I struggle to generate this through ActiveRecord
constructs, or just call the raw SQL? If there is a reasonable way to do
this via AR, I'd rather do that, but I have no fear of the raw call.

Michael Pavling wrote:

Sorry, my quick answer was hideously wrong to assume the joins that AR
would create would be called "worker" and "manager".

I've just tried this on a table of mine, which has two associations to
a "people" table, one as :person, and one as :owner, and the following
works:
Enrollment.all(:include => [:person, :owner], :order =>
"owners_enrollments.lastname, people.lastname")

Please note that I discovered the table alias depends on the position
in the array of includes (for instance if I had ":include => [:owner,
:person]", the alias was different. But if you determine (I used the
console to troubleshoot) what the alias is, then your query should
work as you want as long as you don't fiddle with it!