Recently for some specific reasons I’m studying Ruby on Rails (RoR). One day I was stuck in a problem about how to handle many-to-many relationship.
At first, let’s have a look at the example:
Suppose there’re two models: User
and Skill
, a user can have many skills, and a skill may belong to many users. what’s more, the proficiency of skills owned by different users may be different. For example, user-1 may be primary in skill_1 and normal in skill_2, while user-2 may be professional in skill_1, and primary in skill_2.
So how to handle this kind of relationship in Ruby on Rails? And what if I want to add skill_3 to user-1, who are normal in such skill, then what should I do?
After searching google, I found two very useful tutorials: one from RailsCast, and one from StackOverflow and Github.
For the first one from RailsCast, it is a tutorial about two ways to have many-to-many relationship:
has_and_belongs_to_manyhas_many :through
As said in the cast, has_many :through
can be used when the joint table has some other fields.
So in my case, I should use has_many :through
way, because in my joint table there may be a proficiency
field.
For the second one, it is a example written by szines in order to reply to a question on StackOverflow, the example is about how to use has_many :through
to realize many-to-many relationship.
According to the above two materials, I desided to use the has_many :through
relationship. Then following is how I did that.
Firstly I add another Model called Ability
, which has a user_id, skill_id and a field called “proficiency”. The basic relationship looks like this:
In Ruby on Rails, for User
model:
class User < ActiveRecord::Base has_many :abilities has_many :skills, :through => :abilities ...end
for Skill
model:
class Skill < ActiveRecord::Base has_many :abilities has_many :users, :through => :abilities ...end
for Ability
model:
class Ability < ActiveRecord::Base belongs_to :user belongs_to :skill ...end
in db/migrate/xxx_create_users.rb
class CreateUsers < ActiveRecord::Migration def change create_table :users do |t| t.string :name t.timestamps end endend
in db/migrate/xxx_create_skills.rb
class CreateSkills < ActiveRecord::Migration def change create_table :skills do |t| t.string :name t.timestamps end endend
in db/migrate/xxx_create_abilities.rb
class CreateAbilities < ActiveRecord::Migration def change create_table :abilities do |t| t.belongs_to :user, index: true t.belongs_to :skill, index: true t.string :proficiency t.timestamps end endend
So the final relationship looks like this:
Then, when we need to add a normal skill_3 to user-1, we can add code in users_controller.rb
...@user = User.find(1)skill = Skill.where("name = skill_3").first@user.abilities.create(proficiency: "normal", skill_id: skill.id)...
Here we done!