requesting membership to a group mvc - ruby-on-rails

I have a group model that has_many :members, and has_many :memberships. What I would like to do is make it so that in some groups the creator of the group would make it so that you have to request membership in order to join that specific group. How could I set this up in my rails application?
I have added a boolean field to the memberships ActiveRecord but I dont know how to set it up in a way that would allow me to join groups that dont require the "request a membership" function but also to create a "request a membership" function.
as of right now my models look like this:
class Group < ActiveRecord::Base
belongs_to :creator, :class_name => "User"
has_many :members, :through => :memberships
has_many :memberships, :foreign_key => "new_group_id"
has_many :events
end
class User < ActiveRecord::Base
has_many :groups, foreign_key: :creator_id
has_many :memberships, foreign_key: :member_id
has_many :new_groups, through: :memberships
end
class Membership < ActiveRecord::Base
belongs_to :member, class_name: "User"
belongs_to :new_group, class_name: "Group"
validates :new_group_id, uniqueness: {scope: :member_id}
has_many :accepted_memberships, -> {where(memberships: { approved: true}) }, :through => :memberships
has_many :pending_memberships, -> {where(memberships: { approved: false}) }, :through => :memberships
end
and my membership controller:
class MembershipsController < ApplicationController
def create
#group = Group.find(params[:new_group_id])
#membership = current_user.memberships.build(:new_group_id => params[:new_group_id])
if #membership.save
flash[:notice] = "Joined #{#group.name} "
else
flash[:notice] = "You're already in this group"
end
redirect_to groups_url
end
def destroy
#group = Group.find(params[:id])
#membership = current_user.memberships.find_by(params[membership_id: #group.id]).destroy
redirect_to groups_url
end
end

I believe that you are already very close to your solution, and that it is more of a business problem than a technical one. First I would add a boolean to the group to indicate that approval is required. e.g.
rails generate migration add_require_approval_to_groups require_approval:boolean
This would get set when the creator first creates the group depending upon the type of group that they have created.
Now, somehow a user has to be able to discover that there are groups that they can join, and you need to communicate an awareness to them that for some groups, membership is not automatic, but must be approved by the group creator.
So, assuming that you have communicated this to the user, and that they are on a page with a selection box listing all of the groups that they can become a member of (not necessarily the best design choice, but will do for this example). You need to have a query in your model that will gather all of the available groups that a user can still join.
def self.available_groups(user_id)
where("id not in (select group_id from group_members where user_id = ?)", user_id)
.select("id, name")
.collect{ |g| [g.name, g.id] }
end
In your controller:
#available_groups = Group.available_groups(#current_user)
And in your view:
<h2>Please select the group to join:</h2>
<p>
<%= form_tag :action => 'join_group' do %>
<%= select("group", "id",
#available_groups) %>
<%= submit_tag "Join" %>
<% end %>
</p>
Now, when you process the "post" in your membership_controller, you need to inform the creator that someone is trying to join the group that requires approval (perhaps a mailer). If the require_approval boolean is not set, then you need to automatically approve the user so that they can access the group immediately.

Related

How to structure a has_many association with a dynamic scope?

I have a users table in my db. A user can be either of type 'admin' or 'manager'.
Given the models and schema below, I would like that for each instance of 'manager' user, an 'admin' user could select one, some or all the locations of the tenant that the manager belongs to in order to select which locations the manager can have control over.
My models
class User < ActiveRecord::Base
belongs_to :tenant
class Tenant < ActiveRecord::Base
has_many :users, dependent: :destroy
has_many :locations, dependent: :destroy
class Location < ActiveRecord::Base
belongs_to :tenant, inverse_of: :locations
I've tried two paths
First, trying to establish a scoped has_many association between the User and the Location models. However, I can't wrap my head around structuring this scope so that an 'admin' user could select which locations the 'manager' users can control.
Second, setting up a controlled_locations attribute in the users table. Then I set up some code so that an 'admin' user can select which locations a 'manager' can control, populating its 'controlled_locations' attribute. However, what gets saved in the database (inside the controlled_locations array) is strings instead of instances of locations.
Here's the code that I tried for the second path:
The migration
def change
add_column :users, :controlled_locations, :string, array: true, default: []
end
In the view
= f.input :controlled_locations, label: 'Select', collection: #tenant_locations, include_blank: "Anything", wrapper_html: { class: 'form-group' }, as: :check_boxes, include_hidden: false, input_html: {multiple: true}
In the users controller (inside the update method)
if params["user"]["controlled_locations"]
params["user"]["controlled_locations"].each do |l|
resource.controlled_locations << Location.find(l.to_i)
end
resource.save!
end
What I expect
First of all, I'm not quite sure the second path that I tried is a good approach (storing arrays in the db). So my best choice would be to set up a scoped association if it's possible.
In case the second path is feasible, what I would like to get is something like this. Let's say that logging in an Admin, I selected that the user with ID 1 (a manager) can control one location (Boston Stadium):
user = User.find(1)
user.controlled_locations = [#<Location id: 55, name: "Boston Stadium", created_at: "2018-10-03 12:45:58", updated_at: "2018-10-03 12:45:58", tenant_id: 5>]
Instead, what I get after trying is this:
user = User.find(1)
user.controlled_locations = ["#<Location:0x007fd2be0717a8>"]
Instead of instances of locations, what gets saved in the array is just plain strings.
First, your code is missing the locations association in the Tenant class.
class Tenant < ActiveRecord::Base
has_many :users, dependent: :destroy
has_many :locations
Let's say the variable manager has a User record. Then the locations it can control are:
manager.tenant.locations
If you want, you can shorten this with a delegate statement.
class User < ActiveRecord::Base
belongs_to :tenant
delegate :locations, to: :tenant
then you can call this with
manager.locations
A common pattern used for authorization is roles:
class User < ApplicationRecord
has_many :user_roles
has_many :roles, through: :user_roles
def add_role(name, location)
self.roles << Role.find_or_create_by(name: name, location: location)
end
def has_role?(name, location)
self.roles.exists?(name: name, location: location)
end
end
# rails g model role name:string
# make sure you add a unique index on name and location
class Role < ApplicationRecord
belongs_to :location
has_many :user_roles
has_many :users, through: :user_roles
validates_uniqueness_of :name, scope: :location_id
end
# rails g model user_role user:references role:references
# make sure you add a unique compound index on role_id and user_id
class UserRole < ApplicationRecord
belongs_to :role
belongs_to :user
validates_uniqueness_of :user_id, scope: :role_id
end
class Location < ApplicationRecord
has_many :roles
has_many :users, through: :roles
end
By making the system a bit more generic than say a controlled_locations association you can re-use it for different cases.
Let's say that logging in an Admin, I selected that the user with ID 1
(a manager) can control one location (Boston Stadium)
User.find(1)
.add_role(:manager, Location.find_by(name: "Boston Stadium"))
In actual MVC terms you can do this by setting up roles as a nested resource that can be CRUD'ed just like any other resource. Editing multiple roles in a single form can be done with accepts_nested_attributes or AJAX.
If you want to scope a query by the presence of a role then join the roles and user roles table:
Location.joins(roles: :user_roles)
.where(roles: { name: :manager })
.where(user_roles: { user_id: 1 })
To authenticate a single resource you would do:
class ApplicationController < ActionController::Base
protected
def deny_access
redirect_to "your/sign_in/path", error: 'You are not authorized.'
end
end
class LocationsController < ApplicationController
# ...
def update
#location = Location.find(params[:location_id])
deny_access and return unless current_user.has_role?(:manger, #location)
# ...
end
end
Instead of rolling your own authorization system though I would consider using rolify and pundit.

Rails ActiveRecord - Users behave as Team after migrations

I have a users and a teams table. The users table has a column called team_id. But now the goal is to allow users to have multiple teams.
So I created the user_teams table using migrations like so:
class CreateUserteams < ActiveRecord::Migration[5.0]
def change
create_table :user_teams do |t|
t.integer :user_id
t.integer :team_id
t.timestamps
end
end
end
And my code is as follows:
models/user.rb
class User < ApplicationRecord
#belongs_to :team
# should now have multiple teams
has_many :user_teams
has_many :teams, through: :user_teams
models/team.rb
class Team < ApplicationRecord
has_many :members, foreign_key: "team_id", class_name: "User"
has_many :user_teams
has_many :users, through: :user_teams
models/user_teams.rb (new file)
class UserTeam < ApplicationRecord
belongs_to :user
belongs_to :team
end
(This works fine) To invite a user to join a team I send him an invite and if he accepts, I add him to the new users_team table using:
controllers/team_invites_controller.br
def accept
user = #team_invite.user
#team_invite.update(status: TeamInvite.statuses[:accepted])
user.team_id = #team_invite.team_id
user.company_id = #team_invite.team.company_id
user.teams << Team.find(user.team_id)
user.save(:validate => false)
respond_to do |format|
if user.shadow?
format.html {
redirect_to new_user_registration_path({
:email => user.email,
:role => user.main_role,
})
}
else
format.html { redirect_to dashboard_root_path, notice: 'Invite accepted' }
end
format.json { head :no_content }
end
end
The problem is, now "user" somehow became a Team and not a User, and it broke all of the code using "user.something". Also because of this I cannot access User methods because "user" is no longer a User but a Team.
For example, now:
<% if user.photo.exists? %>
gives:
undefined method `photo' for Team:0x00007f381dea4708
Or:
<% if current_user.teams.members.count > 1 %>
gives:
undefined method `members' for
Team::ActiveRecord_Associations_CollectionProxy:0x00007f3845954eb0 Did you mean? member?
Why does users.something now behaves as a team? I already re-did migrations and got the same error. Any idea how thatvhappened and how to fix it? How can I access users properties and methods correctly?
Thank you
EDIT
On
<% if user.photo.exists? %>
it's meant to display the image of another user and it gives the undefined method `photo' error. But if I use (although I don't want to)
<% if current_user.photo.exists? %>
It works. How come?
Your users have multiple teams and thus current_user.teams is not a Team anymore, it's a collection proxy, to access individual teams from there, depending on your logic you can use:
<% if current_user.teams.any?{|team| team.members.count > 1 } %>
(note that this code may be inefficient if there're many teams)

Rails 5.1, delete multiple records with conditions

I try to delete several records in my DB according to a few conditions. Before I talk about my problem I will explain how my app is working.
A User can create groups and links. This User own the groups he created same for the links. Other Users can then join this group by providing a token (which is created automatically when the group is created) via a Member model. Where member has the user_id and the group_id in the DB. Then once the User is part of a group he can share the links he created in the groups via Grouplink model. Where grouplink has groupd_id and link_id.
I managed to code the fact that if there is no member in a group the group is destroyed. But how do I manage to remove the links a user shared in the group if he leaves the group ? (when the user is destroyed automatically all the links are deleted so I think that the sharing should be gone as well, I have to try that tho'). When a User leaves the group I destroy his member record and he is gone but the links remain. I display the shared links, the fact that you can kick users (member destroy) and the list of the members of a group in the group show btw.
I thought about a few things. I write down what I did in the console
g = Group.find(66)
u = g.users.find(1)
u.links
and that give me all the links from the user in the group. Next to that
g.grouplinks
would give me all the shared links in the group.
g.grouplinks.map(&:link_id) returns [16, 17, 14, 13, 15]
u.links.map(&:id) returns [13, 15]
Now what can I do here ? My user is leaving the group. The member record is destroyed and how can I destroy those grouplinks according to the links the users has ?
Is there a magic trick I don't know yet ?
Thanks for your help.
EDIT
class User < ApplicationRecord
has_secure_password
has_many :links, dependent: :destroy
has_many :grouplinks, dependent: :destroy
has_many :members, :dependent => :destroy
has_many :groups, :through => :members
has_one :owned_group, foreign_key: "owner_id", class_name: "Group"
end
class Member < ApplicationRecord
belongs_to :user
belongs_to :group
validates :user_id, :presence => true
validates :group_id, :presence => true
validates :user_id, :uniqueness => {:scope => [:user_id, :group_id]}
end
class Link < ApplicationRecord
has_many :grouplinks, :dependent => :destroy
belongs_to :user
end
class Grouplink < ApplicationRecord
belongs_to :group
belongs_to :link
end
class Group < ApplicationRecord
has_secure_token :auth_token
has_many :members, :dependent => :destroy
has_many :users, through: :members, source: :user
belongs_to :owner, class_name: "User"
has_many :links, through: :grouplinks
has_many :grouplinks, :dependent => :destroy
def to_param
auth_token
end
end
I thought that actually I could add the user_id in the grouplinks so I could delete_all according to the user_id in the links and in the groupslinks. Not sure how to that tho' and don't know if there is a better solution.
EDIT 2
I tried your solution within the models. Actually it is smart and I didn't think about that...
Problem is now with the creation of my grouplink (share the link). I had this :
def create
user = current_user if current_user
group = user.groups.find_by(auth_token: params[:auth_token])
share = group.id
group_link = group.grouplinks.build(link_id: params[:link_id])
gl = group.grouplinks
if gl.where(group_id: share).where(link_id: params[:link_id]).exists?
flash[:error] = "You shared this link in '#{group.name}' already."
redirect_to mylinks_path
else
if group_link.save
group_link.toggle!(:shared)
flash[:success] = "You shared your link in '#{group.name}'."
redirect_to mylinks_path
else
render 'new'
end
end
end
And this is obviously not working anymore and I have this error when I try to share a link : First argument in form cannot contain nil or be empty <%= form_for #grouplink do |f| %>.
I tried to change it like this :
def create
group = Group.find_by(auth_token: params[:auth_token])
share = group.id
group_link = group.grouplinks.build(link_id: params[:link_id])
gl = group.grouplinks
if gl.where(group_id: share).where(link_id: params[:link_id]).exists?
flash[:error] = "You shared this link in '#{group.name}' already."
redirect_to mylinks_path
else
if group_link.save
group_link.toggle!(:shared)
flash[:success] = "You shared your link in '#{group.name}'."
redirect_to mylinks_path
else
render 'new'
end
end
end
But it is not working either
How about:
class Member < ApplicationRecord
belongs_to :user
belongs_to :group
has_many :group_links, dependent: :destroy
validates :user_id, :presence => true
validates :group_id, :presence => true
validates :user_id, :uniqueness => {:scope => [:user_id, :group_id]}
end
and
class Grouplink < ApplicationRecord
belongs_to :link
belongs_to :member
end
Now, when a Member record is destroyed (i.e., the user is kicked out of or leaves the group), any links shared with the group (i.e., group_links) are also destroyed. But, if the user has shared the link in another group, the link will continue to be shared with the other groups.
As mentioned by #Pablo in the comments, you probably also want to do:
class Group < ApplicationRecord
has_secure_token :auth_token
has_many :members, :dependent => :destroy
has_many :grouplinks, through: :members
has_many :users, through: :members, source: :user
belongs_to :owner, class_name: "User"
has_many :links, through: :grouplinks
def to_param
auth_token
end
end
Which will allow you to do:
group.grouplinks
I also agree with #amr-el-bakry that Member is a bit confusing. I suggest GroupUser as it makes it quite clear that it is an association between Group and User.
Also, I think it might be a bit more conventional to say GroupLink instead of Grouplink. Or, if you want to stick with naming based on associated classes, perhaps MemberLink. If you change Member to GroupUser, then perhaps GroupUserLink.
I'm thinking your create code should probably look something like:
def create
if group
if member
if link
unless group_link
#group_link = member.group_links.build(link: link)
if group_link.save
group_link.toggle!(:shared)
flash[:success] = "You shared your link in '#{group.name}'."
redirect_to mylinks_path
else
render :new
end
else
flash[:error] = "You shared this link in '#{group.name}' already."
else
flash[:error] = "That link does not exist."
redirect_to somewhere #fix this
end
else
flash[:error] = "You must be a member of this group to add a link."
redirect_to somewhere #fix this
end
else
flash[:error] = "There is no group with that token."
redirect_to somewhere #fix this
end
end
private
def group
#group ||= Group.find_by(auth_token: params[:auth_token])
end
def member
#member ||= current_user.members.where(group: group)
end
def link
#link ||= Link.find_by(id: params[:link_id])
end
def group_link
#group_link ||= member.group_links.where(link: link)
end
You may be able to write this as:
def create
flash[:error] = "There is no group with that token."
redirect_to somewhere unless group
flash[:error] = "You must be a member of this group to add a link."
redirect_to somewhere unless member
flash[:error] = "That link does not exist."
redirect_to somewhere unless link
flash[:error] = "You shared this link in '#{group.name}' already."
redirect_to mylinks_path if group_link
flash[:error] = nil
#group_link = member.group_links.build(link: link)
if group_link.save
group_link.toggle!(:shared)
flash[:success] = "You shared your link in '#{group.name}'."
redirect_to mylinks_path
else
render :new
end
end
But I can't remember if those redirects will give you heartache.
What you're looking for is dependent :delete_all
In your Group Model, you want to you should have a line like:
has_many :links, dependent :delete_all
This says, the group has many links and if you destroy the group, destroy all the related links.
In your Member model, you could use an after_destroy callback to destroy all user links in the group after membership record is destroyed:
class Member < ApplicationRecord
after_destroy do |record|
record.user.links.each { |link| link.grouplinks.where(group_id: record.group.id).destroy_all }
end
belongs_to :user
belongs_to :group
...
end
Also, I suggest you change Member to Membership to be more clear.
I think the best idea is that the group_links belong to a member and a link (and not a group and a link). And a member (not a user) has many group_links. When your destroy the member, it will destroy the group_links.
EDIT
This is what jvillian suggested in his answer, just before I did. So I believe his answer is the right one (with some minor enhancements I suggested in comments that jvillian will surely accept and add :-).
EDIT2
Regarding the problem you faced after applying jvillian suggestion, when creating a new grouplink, it must be done from a member (not a group). So in the create action you must search the member (by user_id and group_id) and create the grouplink as member.grouplinks.build

Rails 4 self referential many to many relationships

I can't wrap my head around this any help would be appreciated. I went through many articles and other postings on here and I can't seem to get the results I'm looking for.
I have a User model and Team model.
Team Model has user_id and team_id
The user who creates the team will be user_id and users who are members of the team will be in the team_id
User Model
has_many :teams, foreign_key: :team_id
has_many :team_members, class_name: "Team", foreign_key: :user_id
Team Model
belongs_to :user, foreign_key: :team_id
belongs_to :team_member, class_name: "User", foreign_key: :user_id
The end result of what I'm trying to achieve is:
Each user can add as many team members
Each user can see a list of users who are part of their team.
A view where, Users who are part of a team can see which team they are part of.
I believe what you're looking for is a join table model. The issue is that both a User and a Team may have many of each other. So the relationship must be stored separately.
See this answer here on a similar question: https://stackoverflow.com/a/15442583/5113832
So you might choose a model structure of User, Team and TeamMembership.
Updated to destroy dependent team memberships when a user or team is destroyed.
#app/models/user.rb
class User < ActiveRecord::Base
has_many :team_memberships, :dependent => :destroy
has_many :teams, :through => :team_memberships
end
#app/models/team.rb
class Team < ActiveRecord::Base
has_many :team_memberships, :dependent => :destroy
has_many :users, :through => :team_memberships
end
#app/models/team_membership.rb
class TeamMembership < ActiveRecord::Base
belongs_to :user
belongs_to :team
end
Updated to reply to question:
How would my controller look on create? (Adding a user to a team) – Michael
In order to add a user to to a team you COULD do it in UserController, you COULD do it in TeamController. However, because you are now creating a TeamMembership resource you would want to create a TeamMembership record in a TeamMembershipsController. This keeps with the "Rails" way of doing things. So for example:
# app/controllers/team_memberships_controller.rb
class TeamMembershipsController < ApplicationController
def index
#team_memberships = TeamMembership.all
end
def new
#team_membership = TeamMembership.new
end
def create
#team_membership = TeamMembership.new(team_membership_params)
if #team_membership.save
flash[:success] = 'Team membership created'
redirect_to team_memberships_path
else
flash[:error] = 'Team membership not created'
render :new
end
end
def destroy
#team_membership = TeamMembership.find_by_id(params[:id])
if #team_membership && #team_membership.destroy
flash[:success] = 'Team membership destroyed'
else
flash[:error] = 'Team membership not destroyed'
end
redirect_to team_memberships_path
end
private
def team_membership_params
params.require(:team_membership).permit(
:user_id,
:team_id
)
end
end
The advantage to having the TeamMembership resource is using this pattern to manage when a user is added (#create), or removed (#destroy) from a team.
The magic of Rails associations will take care of accessing a team's members (users) and a user's teams for each instance of those models.
You just go about your business doing CRUD on these resources and Rails takes care of the organization for you by your conforming to it's conventions.
Also I updated my original model code to destroy team memberships when a user or team is destroyed. This ensures no orphaned records are in your team_memberships table.
As a final note. You should also be able to easily use form_for to send a TeamMembership to your controller to be created. This could be done using select option dropdowns for users and teams with Rails' collection_select:
<%# app/views/team_memberships/new.html.erb %>
<h1>
Create Team Membership
</h1>
<%= form_for(#team_membership) do |f| %>
<%= f.label(:user) %>
<%= f.collection_select(
:user_id,
User.all,
:id,
:username
) %>
<%= f.label(:team) %>
<%= f.collection_select(
:team_id,
Team.all,
:id,
:name
) %>
<%= f.submit %>
<% end %>
The above code will render dropdown for all users and teams allowing you to select a specific combination to create a team membership from. Deleting a team membership is as easy as sending a DELETE #destroy request with the id of the team membership.
Another consideration might be adding a unique pair constraint to your database table and model within the migration and model validations.
Hope this helps!

User has several skills

I want my users to have many skills. I do have a users and skills database table.
I used has_many_and_belongs_to association in user.rb
has_many :skills
which I am not sure if its correct. And in skill.rb
has_and_belongs_to_many :users
I also created a migration like that:
def change
create_table :user_skills do |t|
t.belongs_to :users
t.belongs_to :skills
end
Is this correct?
So IF this is correct, how do I add new skills to my user? What is the general approach?
What I thought of,
In my users controller on update action I will be updating user's skill and update the user_skills table.
How is this done?
Also How do I iterate through my user_skills table for a specific user? (in view)
Any guidance, resource, tip will be great help for me as its the first time i do something like this in Rails.
Thanks
In Rails, most would prefer to use has_many :through over habtm associations. Here's a guide on how to use it: ActiveRecord guide.
A has_many through association for users and skills would look like this in your relevant models:
class User < ActiveRecord::Base
has_many :user_skills
has_many :skills, through: :user_skills
end
class UserSkill < ActiveRecord::Base
belongs_to :user
belongs_to :skill
end
class Skill < ActiveRecord::Base
has_many :user_skills
has_many :users, through: :user_skills
end
Your migration would look like:
def change
create_table :user_skills do |t|
t.references :user, index: true
t.references :skill, index: true
end
end
The indexes in the migration are for faster look-ups for using the reference_id. It's advisable to do that for all references.
To add new skills to your user, you can refer to this SO answer.
To update a user's skill, you could do this:
#skill = #user.skills.find(params[:skill_id])
#skill.update(skill_params)
To create a user's skill, you could do this:
#user.skills.create(skill_params)
To add a skill to user, you could do this in your update action:
#user.update(user_params)
#app/views/users/edit.html.erb
<%= f.select :skill_ids, Skill.all.collect {|x| [x.name, x.id]}, {}, :multiple => true %>
When working with has_many through, you won't need to go through the user_skills table to get a specific user. You would, however, might need to get a specific user from a skill. To do this:
#skill.users.find(user_id)
Hope that helps!
If you set user to have_and_belong_to_many :skills also then this will work.
To create a new skill for a user do
user.skills.create!{...}
or to associate an existing skill with a user do
user << skill
"In my users controller on update action I will be updating user's skill and update the user_skills table. How is this done?"
user = User.find params[:id]
skills = user.skills
You can then do what you like to users skills
"Also How do I iterate through my user_skills table for a specific user? (in view)"
user.skills.each do |skill|
...
end
for more on HABTM association see http://guides.rubyonrails.org/association_basics.html#has-and-belongs-to-many-association-reference
Forgive me If I get it wrong, try to fill in the gaps but I think you want something that looks like this.
controller
def index
#to fetch all skills associated to users (add where u.id=? to fetch for a single user)
#users = User.select("u.name, s.name").
from("users u, skills s, users_skills us").
where("u.id = us.user_id").
where("s.id = us.skill_id")
end
def new
#user = User.new
#skills = Skill.all
end
def create
#user = User.new(params[:user])
...............................
end
in the create form
<%= form_for #user do |f| %>
<%= f.collection_select(:skill_ids, #skills,:id,:name)%>
<%= f.submit "Save" %>
<% end %>
In order to use HABTM you need a join table named either users_skills or skills_users (not sure it matters). It should contain two integer columns named user_id and skill_id. You should create indices for them as well. In your User model you want has_and_belongs_to_many :skills and in your Skill model you want has_and_belongs_to_many :users.
You need has_and_belongs_to_many on both sides of the realtionship.
class User
has_and_belongs_to_many :skills
class Skill
has_and_belongs_to_many :users
Alternatively (and better, in my opinion) would be to use has_many :through:
class User
has_many :user_skills
has_many :skills, through: :user_skills
class Skill
has_many :user_skills
has_many :users, through: :user_skills

Resources