2

I'm stuck on this:

class Worker < ActiveRecord::Base
  has_many :skills
  has_many :jobs, through: :skills
..
end

class Skill < ActiveRecord::Base
  belongs_to :worker
  has_many :jobs
..
end

class Job < ActiveRecord::Base
  has_many :skills
  has_many :workers, through: :skills
..
end

What I'm trying to do is set up a many to many between Skill and Job inside of the `has_many' through relationship?

My question has three parts

  1. Is this possible - using the has_many jobs rather than belongs_to jobs.
  2. If it can be done and the code is wrong, how do I fix it?
  3. How can I create Worker, Skill and Job records? (looking for syntax)

This is a picture (of sorts) of what I'm trying to do, hope it helps... :(

enter image description here

rrivermcd
  • 45
  • 2
  • 8
  • 1
    A suggestion.If you want to set `many to many` between `Skill` and `Job`,then either do with `has_and_belongs_to_many` or a normal `has_many :through`,but don't mess up with the both. – Pavan Jul 31 '14 at 17:12
  • So I already have a has_many through that uses Skill. Typically that would be set as belongs_to jobs, not has_many jobs. So my question is whether the use of has_many jobs is valid within the context of the has_many through? If so, it seems you're recommending creating another has_many through using Skill, Job and another, yet to be defined class? Can you provide an example? – rrivermcd Jul 31 '14 at 20:42
  • A normal join model has two `belongs_to` relationships that relates A to B in a many-to-many structure. I don't see one of those here. I'm not totally following what your relationships should be. Normally `belongs_to` pairs with `has_many`, but you apparently have two `has_many` relationships between Skill and Job. – tadman Jul 31 '14 at 20:57
  • tadman, that is my question exactly. Can I actually use has_many though with a many to many relationship between Skill and Job? If so, how might this be done? – rrivermcd Jul 31 '14 at 21:04
  • Im not sure if it makes sense but you could add a `belongs_to :jobs` to your `Skill` model and then a `belongs_to :skills` in your `Jobs` model. Seems like it is a little hacky though – CWitty Jul 31 '14 at 21:57
  • CWitty, that's exactly what I did!! – rrivermcd Aug 01 '14 at 19:18

1 Answers1

2

You're not giving active record enough information about your relationships. Every :has_many should have a corresponding :belongs_to so that active record knows which table holds the foreign key for each association. Notice that you only have one :belongs_to for three relationships. That smells.

As for fixing the problem, you have at least 2 options:

  • add :has_and_belongs_to_many associations
  • use explicit join tables

My preference is for the latter option. Being forced to name join tables often clarifies the nature of a relationship. On the flip side, I've found that :has_and_belongs_to_many is often too implicit and ends up making my designs more obscure.

Explicit join tables

You might setup your relationships like this (untested):

class Assignment < ActiveRecord::Base
  belongs_to :worker
  belongs_to :job
end

class Qualification < ActiveRecord::Base
  belongs_to :worker
  belongs_to :skill
end

class Worker < ActiveRecord::Base
  has_many :qualifications
  has_many :skills, through: :qualifications

  has_many :assignments
  has_many :jobs, through: :assignments
  ..
end

class Skill < ActiveRecord::Base
  has_many :qualifications
  has_many :workers, through: :qualifications
  has_many :jobs
  ..
end

class Job < ActiveRecord::Base
  has_many :skills
  has_many :workers, through: :assignments
  ..
end

By making the relationships more explicit I think the model is clearer. It should be easier to troubleshoot from here.

EDIT:

If you need to do a traversal like Job.find(1).qualified_workers try making the following adjustment to the above model:

class Job
  has_many :required_competencies
  has_many :skills, through: :required_competencies
  has_many :qualifications, through: :skills
  has_many :qualified_workers, through: qualifications, class_name: :workers
end

class RequiredCompetency
  belongs_to :job
  belongs_to :skill
end

This is explicit about each traversal and names it. If you find these paths through your system are getting really long, I'd consider that a smell. There might be a more direct way to fetch your data or perhaps a better way to model it.

Dane O'Connor
  • 75,180
  • 37
  • 119
  • 173
  • Thank you! I am considering not using the assignments and finding jobs for workers through a call to skills....thoughts? – rrivermcd Aug 01 '14 at 19:15
  • Can you explain the has_many :qualified_workers? I would like to traverse from workers to Jobs and visa versa, so this is feeling right. I would say that this is an example that masks my actual class names, so for the example please also consider that Worker to Skill is a 1:many not many:many. Thanks!!! :-\ – rrivermcd Aug 01 '14 at 20:12
  • Qualified_workers allows you to go from Jobs to Workers. I could have changed the name to just `workers` but I think that makes your model less obvious. You can't directly go from Jobs to Workers, your requirements dictate intermediate criteria. Prefixing the method with something descriptive makes that more obvious. – Dane O'Connor Aug 01 '14 at 20:57
  • To do the reverse query you'd setup something similar on `Workers`. Personally I try to avoid these long chains. It usually means that my object model could be improved... – Dane O'Connor Aug 01 '14 at 20:59
  • I was kind of feeling that might be the case, and have reduced the model. You input was thoughtful and spot on. Thanks!! – rrivermcd Aug 01 '14 at 21:55