Using has_many through in Rails 4 - ruby-on-rails

I'm actually trying to create a has_many through association. Let me first explain a bit about how things are supposed to work.
I have a users, groups and members tables. The rules are as follow :
A user can create a group (depending on it's role) (groups table has a user_id)
A user can be member of one or many groups (members table contain user_id and group_id)
Here is my current relationship classes :
class User < ActiveRecord::Base
# Associations
has_many :groups # As user, I create many groups
has_many :members
has_many :groups, through: :members # But I can also belongs to many groups
end
class Group < ActiveRecord::Base
# Associations
belongs_to :user
has_many :members
has_many :users, through: :members
end
class Member < ActiveRecord::Base
# Associations
belongs_to :user
belongs_to :group
end
My problem is about the group relationship. You see a user can create groups, which means :
has_many :groups
but a user can also be member of groups :
has_many :groups, through: :members
Because of this new relationship, 75% of my specs are now broken. Also, I notice that if I logged in with a user associated to a group, I can see actually groups list. But when I'm trying to logged in as group owner (the one who created the group), I can not see the groups created by that user).
Idea?

You are not looking for an has_many through relationship here
Try that :
class User < ActiveRecord::Base
# Associations
has_and_belongs_to_many :groups
has_many :created_groups, class_name: 'Group', foreign_key: 'creator_id'
end
class Group < ActiveRecord::Base
# Associations
belongs_to :creator, class_name: 'User'
has_and_belongs_to_many :members, class_name: 'User'
end
This is a solution if you don't need the member class to do any special treatment.
You should have a migration that looks like that:
class CreateGroupsUsers < ActiveRecord::Migration
def change
create_table :groups_users, id: false do |t|
t.references :group
t.references :user
end
add_index :groups_users, [:group_id, :user_id]
add_index :groups_users, :user_id
end
end
And you have to make sure that your groups table have a creator_id !

Related

Polymorphic has-many-through relationships in Rails

I am trying to set up a polymorphic has-many-through relationship with ActiveRecord. Here's the end goal:
Users can belong to many organizations and many teams
Organizations have many users and many teams
Teams have many users and belong to an organization
I am trying to use has-many-through instead of has-and-belongs-to-many, since I need to associate some information along with the relationships (like user role in the organization or team), so I made a join table Membership.
How would I implement this?
I would design the schema like this:
Organization has many Team
Team has many TeamMember
User has many TeamMember
TeamMember belongs to User and Team
The models will be:
organization.rb
class Organization < ActiveRecord::Base
has_many :teams
has_many :team_members, through: :teams
has_many :users, through: :team_members
end
team.rb
class Team < ActiveRecord::Base
belongs_to :organization # fk: organization_id
has_many :team_members
has_many :users, through: :team_members
end
user.rb
class User < ActiveRecord::Base
has_many :team_members
has_many :teams, through: :team_members
has_many :organizations, though: :teams
end
team_member.rb
class TeamMember < ActiveRecord::Base
belongs_to :team # fk: team_id
belongs_to :user # fk: user_id
attr_accessible :role # role in team
end
So, compare with your requirements:
Users can belong to many organizations and many teams
=> Okay
Organizations have many users and many teams
=> Okay
Teams have many users and belong to an organization
=> Okay
Btw, we don't use any polymorphic here, and TeamMember stands for Membership in your early idea!
For polymorphic association,
class User
has_many :memberships
end
class Team
belongs_to :organization
has_many :memberships, :as => :membershipable #you decide the name
end
class Organization
has_many :memberships, :as => :membershipable
has_many :teams
end
class Membership
belongs_to :user
belongs_to :membershipable, polymorphic: true
end
Note that User is indirectly associated to Team and Organization, and that every call has to go through Membership.
In my projects, I use a Relationship class (in a gem I've named ActsAsRelatingTo) as the join model. It looks something like this:
# == Schema Information
#
# Table name: acts_as_relating_to_relationships
#
# id :integer not null, primary key
# owner_id :integer
# owner_type :string
# in_relation_to_id :integer
# in_relation_to_type :string
# created_at :datetime not null
# updated_at :datetime not null
#
module ActsAsRelatingTo
class Relationship < ActiveRecord::Base
validates :owner_id, presence: true
validates :owner_type, presence: true
validates :in_relation_to_id, presence: true
validates :in_relation_to_type, presence: true
belongs_to :owner, polymorphic: true
belongs_to :in_relation_to, polymorphic: true
end
end
So, in your User model, you would say something like:
class User < ActiveRecord::Base
has_many :owned_relationships,
as: :owner,
class_name: "ActsAsRelatingTo::Relationship",
dependent: :destroy
has_many :organizations_i_relate_to,
through: :owned_relationships,
source: :in_relation_to,
source_type: "Organization"
...
end
I believe you may be able to leave the source_type argument off since the joined class (Organization) can be inferred from :organizations. Often, I'm joining models where the class name cannot be inferred from the relationship name, in which case I include the source_type argument.
With this, you can say user.organizations_i_relate_to. You can do the same set up for a relationship between any set of classes.
You could also say in your Organization class:
class Organization < ActiveRecord::Base
has_many :referencing_relationships,
as: :in_relation_to,
class_name: "ActsAsRelatingTo::Relationship",
dependent: :destroy
has_many :users_that_relate_to_me,
through: :referencing_relationships,
source: :owner,
source_type: "User"
So that you could say organization.users_that_relate_to_me.
I got tired of having to do all the set up, so in my gem I created an acts_as_relating_to method so I can do something like:
class User < ActiveRecord::Base
acts_as_relating_to :organizations, :teams
...
end
and
class Organization < ActiveRecord::Base
acts_as_relating_to :users, :organizations
...
end
and
class Team < ActiveRecord::Base
acts_as_relating_to :organizations, :users
...
end
and all the polymorphic associations and methods get set up for me "automatically".
Sorry for the long answer. Hope you find something useful in it.

many to many polymorphic association

I'm not sure how to create this, I'd like to create a many-to-many polymorphic association.
I have a question model, which belongs to a company.
Now the question can has_many users, groups, or company. Depending on how you assign it.
I'd like to be able to assign the question to one / several users, or one / several groups, or the company it belongs to.
How do I go about setting this up?
In this case I would add a Assignment model which acts as an intersection between questions and the entities which are assigned to it.
Create the table
Lets run a generator to create the needed files:
rails g model assignment question:belongs_to assignee_id:integer assignee_type:string
Then let's open up the created migration file (db/migrations/...__create_assignments.rb):
class CreateAssignments < ActiveRecord::Migration
def change
create_table :assignments do |t|
t.integer :assignee_id
t.string :assignee_type
t.belongs_to :question, index: true, foreign_key: true
t.index [:assignee_id, :assignee_type]
t.timestamps null: false
end
end
end
If you're paying attention here you can see that we add a foreign key for question_id but not assignee_id. That's because the database does not know which table assignee_id points to and cannot enforce referential integrity*. We also add a compound index for [:assignee_id, :assignee_type] as they always will be queried together.
Setting up the relationship
class Assignment < ActiveRecord::Base
belongs_to :question
belongs_to :assignee, polymorphic: true
end
The polymorpic: true option tells ActiveRecord to look at the assignee_type column to decide which table to load assignee from.
class User < ActiveRecord::Base
has_many :assignments, as: :assignee
has_many :questions, through: :assignments
end
class Group < ActiveRecord::Base
has_many :assignments, as: :assignee
has_many :questions, through: :assignments
end
class Company < ActiveRecord::Base
has_many :assignments, as: :assignee
has_many :questions, through: :assignments
end
Unfortunately one of the caveats of polymorphic relationships is that you cannot eager load the polymorphic assignee relationship. Or declare a has_many :assignees, though: :assignments.
One workaround is:
class Group < ActiveRecord::Base
has_many :assignments, as: :assignee
has_many :questions, through: :assignments
def assignees
assignments.map(&:assignee)
end
end
But this can result in very inefficient SQL queries since each assignee will be loaded in a query!
Instead you can do something like this:
class Question < ActiveRecord::Base
has_many :assignments
# creates a relationship for each assignee type
['Company', 'Group', 'User'].each do |type|
has_many "#{type.downcase}_assignees".to_sym,
through: :assignments,
source: :assignee,
source_type: type
end
def assignees
(company_assignees + group_assignees + user_assignees)
end
end
Which will only cause one query per assignee type which is a big improvement.

Designing Two Models to Have A Complex Association

I have two tables, users and groups. An user owns a group and can be apart of multiple groups. A group belongs to one user and can have many users.
Thus for my user model I have
has_and_belongs_to_many :groups
has_many :groups
While for my group model I have
has_and_belongs_to_many :users
belongs_to :user
I also have a join table in my migrations..
def change
create_table :groups_users, :id => false do |t|
t.integer :group_id
t.integer :user_id
end
end
My question is does this make sense? I feel like I'm doing something wrong by having has_many and belongs_to on top of has_and_belongs_to_many.
The way I would approach this, and this is my own personal methodology, is with 3 tables/models like so:
group_user.rb
class GroupUser < ActiveRecord::Base
attr_accessible :user_id, :group_id
belongs_to :group
belongs_to :user
end
group.rb
class Group < ActiveRecord::Base
attr_accessible :owner_id
validates_presence_of :owner_id
has_many :group_users
has_many :users, through: :group_users
end
user.rb
class User < ActiveRecord::Base
attr_accessible :some_attributes
has_many :group_users
has_many :groups, through: :group_users
end
Then, whenever you create a Group object, the User that created it would have its id placed in the owner_id attribute of Group and itself into the GroupUser table.
Another option, so as to not have multiple foreign keys pointing to the same relationship, is to use a join model and then add a flag on the join model to denote if the user is the owner.
For example:
class User < ActiveRecord::Base
has_many :memberships
has_many :groups, through: :memberships
has_many :owned_groups, through: memberships, conditions: ["memberships.owner = ?", true], class_name: "Group"
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :group
#This model contains a boolean field called owner
#You would create a unique constraint on owner, group and user
end
class Group < ActiveRecord::Base
has_many :memberships
has_many :users, through: :memberships
has_one :owner, through: :memberships, conditions: ["memberships.owner = ?", true], class_name: "User"
end

In RoR, how do I create TWO one to one relationship between two tables?

In RoR3,
I have Users and Skills and each skill is created by a user. I wanted to record that, so I created a one to many relationship.
class User < ActiveRecord::Base
has_many :skills
end
class Skill < ActiveRecord::Base
belongs_to :user
end
However, each user also has many skills in the sense that, user "Bob" created skill "Kung Fu", user "Charlie" created skill "Karate" and user "Bob" both created and is able to do both "Kung Fu" and "Karate"
How should I represent this with ActiveRecord? Should I just create a new table "user_skills" which has_many :skills? and belong_to :user?
There are two different associations here. The first is a one-to-many association. An user can be the creator of any number of skills. The second one is a many-to-many association, an user can have many skills and a skill can have many users.
The first one is a simple belongs_to <-> has_many declaration. For the second one, you either need a has_and_belongs_to_many declaration in both models, and a related join table, or a dedicated join model, and a has_many :through declaration. Let's try the first one:
Method 1: HABTM
class User < ActiveRecord::Base
has_many :created_skills, :class_name => 'Skill', :inverse_of => :creator
has_and_belongs_to_many :skills
end
class Skill < ActiveRecord::Base
belongs_to :creator, :class_name => 'User', :inverse_of => :created_skills
has_and_belongs_to_many :users
end
This requires a join table called "skills_users" that has columns named user_id and skill_id
Method 2: Has many through (Join model)
The second one is similar, but adds a model that acts as the middleman. This has an added benefit that you can include additional columns in the join model, like for example a skill level.
class User < ActiveRecord::Base
has_many :created_skills, :class_name => 'Skill', :inverse_of => :creator
has_many :user_skills
has_many :skills, :through => :user_skills
end
class Skill < ActiveRecord::Base
belongs_to :creator, :class_name => 'User', :inverse_of => :created_skills
has_many :user_skills
has_many :users, :through => :user_skills
end
class UserSkill < ActiveRecord::Base
belongs_to :user
belongs_to :skill
end
Having those two models
class User < ActiveRecord::Base
has_and_belongs_to_many :skills
end
class Skill < ActiveRecord::Base
has_and_belongs_to_many :users
end
You would have to create an extra migration (without the model)
rails generate migration CreateSkillsUsersJoin
which will give you
class CreateSkillsUsersJoin < ActiveRecord::Migration
def self.up
create_table :skills_users, id => false do |t|
t.references "user"
t.references "skill"
end
add_index :skills_users,["user_id","skill_id"]
end
def self.down
drop_table :skills_users
end
end
The methods self.up and self.down you will have yo add them
You'd be well served using a gem like acts_as_taggable_on which you'd be able to simply setup and use in your User model, something like:
acts_as_taggable_on :skills
Honestly, they've figured all this stuff out, as it's not as simple as what you're trying to do, OR I should rephrase that and say, what you are trying to do is overtly 'complex' and this gem allows you to just keep on, keeping on after it's set up.
Read the Readme.

Rails model associations, has_many :through and has_one with the same model

I have two models: User and State. The state model has records for each of the 50 states in the United States.
I would like each User to have two attributes: one "home" state, and many states to "visit".
I know I have to set up some sort of model associations to achieve this, but not sure what the best approach is.
Here's what I have so far, but I know there must be something wrong with have has_many and has_one association to the same model.
#user.rb
class User < ActiveRecord::Base
has_many :visits
has_many :states, :through => :visits
has_one :state
end
#visit.rb
class Visit < ActiveRecord::Base
belongs_to :user
belongs_to :state
end
#state.rb
class State < ActiveRecord::Base
has many :visits
has many :users, :through => :visits
belongs_to :user
end
Any suggestions?
In my opinion what you already have is almost right, except you would store the home state foreign key on the user like thus:
# user.rb
class User < ActiveRecord::Base
belongs_to :state
has_many :visits
has_many :states, through: visits
end
# visit.rb
class Visit < ActiveRecord::Base
belongs_to :user
belongs_to :state
end
# state.rb
class State < ActiveRecord::Base
has_many :visits
has_many :users, through: :visits
end
You would then access the home state like thus:
u = User.first
u.state
And the visited states, like thus:
u = User.first
u.states
For programming clarity, you can rename your relations:
# user.rb
class User < ActiveRecord::Base
belongs_to :home_state, class_name: "State"
has_many :visits
has_many :visited_states, class_name: "State", through: visits
end
# state.rb
class State < ActiveRecord::Base
has_many :residents, class_name: "User"
has_many :visits
has_many :visitors, class_name: "User", through: :visits
end
Your domain model would make more sense:
u = User.first
u.home_state
u.visited_states
s = State.first
s.residents
s.visitors
I expect you'll probably want to store additional information about the visit, so keeping the HMT join table for the Visit model will allow you to do this, rather than going with a HABTM relation. You could then add attributes to the visit:
# xxxxxxxxxxxxxxxx_create_visits.rb
class CreateVisits < ActiveRecord::Migration
def change
create_table :visits do |t|
t.text :agenda
t.datetime commenced_at
t.datetime concluded_at
t.references :state
t.references :user
end
end
end
You can't have a has_many and has_one relationship on a single model, in this case state. One solution is to:
create a static model of states, they do not need to be a database model, they could be a static variable on the state model: US_STATES = {'1' => 'AK', '2' => 'AL', etc} or you could use fixtures to load a table of states into the database (more complicated because you need to use a rake task or the db:seed task to load the fixtures into the db, but nice because you can use active record to manage the model).
then you can provide a home_state_id on the user model that defines the home_state and the visits are simply a join between user_id and the state_id.
I would like each User to have two attributes: one "home" state, and many states to "visit".
In your models, a state may only be home to one user (belongs_to).
The correct semantics would be
class User < AR::Base
belongs_to :home_state, :class_name => "State", :foreign_key => "home_state_id", :inverse_of => :users_living
has_and_belongs_to_many :visited_states, :through => :visits
# ...
end
class State < AR::Base
has_many :users_living, :class_name => "User", :inverse_of => :home_state
# ...
end

Resources