I'm looking to make sure my methods are correct before pursuing this association. The implementation sounds too complicated, so I think there must be something wrong with my plan. I am using structured (SQL) data storage, conforming to the rails storage conventions. What I have is a User model, it has an email address, password_digest, and name in the Schema.
class User < ActiveRecord::Base
has_many :posts
end
I'd like to implement a has_many association to a friends collection, so that Users can belong_to Users (as friends). I'm hoping to be able to have User.last.friends.last return a User object when properly built and populated.
I believe I can create a model for this association like:
Class Friend < ActiveRecord::Base
belongs_to :user
belongs_to :friendlies, class: 'User'
end
Class User < ActiveRecord::Base
has_many :posts
has_many :friends
has_many :friendly, class: 'Friend'
end
But I think that will require me to add an as to the models and query using User.last.friends.last.user So what I was thinking is this is a has_and_belongs_to_many relationship. Can I get away with the following (or something similar):
class User < ActiveRecord::Base
has_and_belongs_to_many :friends, class: 'User'
end
I found this:
class User < ActiveRecord::Base
has_many :user_friendships
has_many :friends, through: :user_friendships
class UserFriendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, class_name: 'User', foreign_key: 'friend_id'
And this(self proclaimed 'standard'):
has_many :friendships, :dependent => :destroy
has_many :friends, :through => :friendships, :dependent => :destroy
has_many :inverse_friendships, :class_name => "Friendship", :foreign_key => "friend_id", :dependent => :destroy
has_many :inverse_friends, :through => :inverse_friendships, :source => :user, :dependent => :destroy
Which I assume requires a Friendship model. I don't feel like I need a friendship model. I think that the class UserFriendship method looks right, but it requires an additional model. Now onto the questions:
Can I do a has_and_belongs_to_many relationship with a table that relates user to friends which are users, without incurring an additional model to do so?
Is it prudent to have an additional model 'in case' additional requirements pop up later?
What you're looking for is something called a self referential join, which means that you can reference the same model with a different association:
#app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :friendships,
class_name: "User",
join_table: :friendships,
foreign_key: :user_id,
association_foreign_key: :friend_user_id
end
You'd have to create a habtm join table to have a reference:
$ rails g migration CreateFriendshipsTable
#db/migrate/create_friendships_table____.rb
class CreateFriendShipsTable < ActiveRecord::Migration
def change
create_table :friendships, id: false do |t|
t.integer :user_id
t.integer :friend_user_id
end
add_index(:friendships, [:user_id, :friend_user_id], :unique => true)
add_index(:friendships, [:friend_user_id, :user_id], :unique => true)
end
end
$ rake db:migrate
There is a great reference here: Rails: self join scheme with has_and_belongs_to_many?
Is it prudent to have an additional model 'in case' additional requirements pop up later?
Only if you know you're going to have extra attributes. If not, you can get away with the habtm for as long as you want.
You can try this
--friendship.rb
class Friendship < ApplicationRecord
belongs_to :user
belongs_to :friend, :class_name => 'User'
end
--user.rb
class User < ApplicationRecord
has_many :friendships
has_many :friends, through: :friendships
end
--db migrate xxxx_create_friendship.rb
class CreateFriendships < ActiveRecord::Migration[5.0]
def change
create_table :friendships do |t|
t.belongs_to :user
t.belongs_to :friend, class: 'User'
t.timestamps
end
end
end
Rich's answer was an excellent resource. I managed to subvert a few steps by following this convention:
# app/model/user.rb
has_and_belongs_to_many :friends,
class_name: "User",
join_table: :friends_users,
foreign_key: :user_id,
association_foreign_key: :friend_id
This migration was sufficient (without modifying the resulting code):
rails g migration CreateFriendlyJoinTable users friends
The JoinTable syntax was found using rails generate migration -h
Related
I have two tables:
Users and Groups
a User has_many groups and a group, has_many a users:
u = User.last
u.groups
g = Group.last
g.users
Supposed I wanted a second list of different groups, for some strange reason. Where once again a User has may groups (called other_group in this example) and a group has many users.
u = User.last
u.other_groups
g = Group.last
g.other_users
How do I associate two models in this relationship, twice using Active Record? Do I need multiple has and belongs to many tables? perhaps a has and belongs to many "through". What does this look like?
Answer:
class Matter < ActiveRecord::Base
has_many :matters_lawfirms
has_many :matters_other_lawfirms
has_many :lawfirms, class_name: 'Lawfirm', through: :matters_lawfirms, :source => :lawfirm
has_many :other_lawfirms, class_name: 'Lawfirm', through: :matters_other_lawfirms, :source => :lawfirm
end
class Lawfirm < ActiveRecord::Base
has_many :matters_lawfirms
has_many :matters_other_lawfirms
has_many :matters, class_name: 'Matter', through: :matters_lawfirms, :source => :matter
has_many :other_matters, class_name: 'Matter', through: :matters_other_lawfirms, :source => :matter
end
class MattersLawfirm < ActiveRecord::Base
belongs_to :matter
belongs_to :lawfirm
end
class MattersOtherLawfirm < ActiveRecord::Base
belongs_to :matter
belongs_to :lawfirm
end
migrations:
class AddMatterOtherLawfirms < ActiveRecord::Migration
def change
create_table :matters_other_lawfirms, :id => false do |t|
t.references :matter, :lawfirm
end
add_index :matters_other_lawfirms, [:matter_id, :lawfirm_id],
name: "matters_other_lawfirms_index",
unique: true
end
end
class AddMatterLawfirmsHabtmt < ActiveRecord::Migration
def change
create_table :matters_lawfirms, :id => false do |t|
t.references :matter, :lawfirm
end
add_index :matters_lawfirms, [:matter_id, :lawfirm_id],
name: "matters_lawfirms_index",
unique: true
end
end
Assuming you already know how to handle has_many relationships in your data model (the point to which Alper appears to be writing), it's quite easy to accomodate multiple relationships between two tables (I do something right now which involves linking users distinctly to a projects on which they are working, and to projects which they own. I believe this is very similar to what you're seeking to accomplish). The code would look something like this:
User Model
has_many :regular_groups, class_name: 'Group', through: :user_regular_groups
has_many :other_groups, class_name: 'Group', through: :user_other_groups
Group Model
has_many :regular_users, class_name: 'User', through: :user_regular_groups
has_many :other_users, class_name: 'User', through: :user_other_groups
Obviously in this case we're using two distinct association tables (user_regular_groups and user_other_groups), but you could accomplish something similar with one using a scope (along the lines of what Alper is recommending).
I hope that helps!
You simply can't define many to many relationship using belong_to
You should implement has_and_belongs_to_many or has_many :through relationship instead of has_many - belongs_to relationship.
EDIT
Ok i think i get it now,
You can't achive this using a single has_and_belongs_to_many table, i'd go for a has_many :through relation. If you have only two group category, set a flag for that category in your join table.
Not tested yet, but something similar should work
class GroupMembership #.. with category field or something
belongs_to :group
belongs_to :user
class User
has_many :group_memberships
has_many :local_groups, -> { where group_memberships: { category: 'local' } }, :through => :group_memberships, :source => :group
has_many :outside_groups, -> { where group_memberships: { category: 'outside' } }, :through => :group_memberships, :source => :group
class Group
has_many :group_memberships
has_many :local_users, -> { where group_memberships: { category: 'local' } }, :through => :group_memberships, :source => :user
has_many :outside_users, -> { where group_memberships: { category: 'outside' } }, :through => :group_memberships, :source => :user
For the HABTM relationship, you need to define more then one join table.
I have to following relationship:
class Course < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :courses
end
Then I have the following table:
create_table :courses_users, :force => true, :id => false do |t|
t.integer :user_id
t.integer :course_id
t.integer :middle_value
end
How can I access (edit/update) the middle value in the many to many record?
HABTM should be used to only store the relation. If you have any fields you want to store in the relation, you should create another model, eg. CourseSignup. You would then use this model to create a has_many :through => :course_signups relation, so your models would look like this:
class Course < ActiveRecord::Base
has_many :course_signups
has_many :users, :through => :course_signups
end
class CourseSingup < ActiveRecord::Base
belongs_to :course
belongs_to :user
end
class User < ActiveRecord::Base
has_many :course_signups
has_many :courses, :through => :course_signups
end
Then you can add your middle_value in the CourseSignup model.
You can find more details in the guide to ActiveRecord associations.
You want a has_many :though, not a HABTM.
A HABTM does not have a join model, but a has_many :through does. Something like:
class Course < ActiveRecord::Base
has_many :enrollments
has_many :users, :through => :enrollments
end
class Enrollment < ActiveRecord::Base
belongs_to :course
belongs_to :user
end
class User < ActiveRecord::Base
has_many :enrollments
has_many :courses, :through => :enrollments
end
I am trying to add a "following" like functionality to my site but I am having trouble finding the right way to use a polymorphic association. A user needs to be able to follow 3 different classes, these 3 classes do not follow the user back. I have created a user following user in the past but this is proving to be more difficult.
My Migration was
class CreateRelationships < ActiveRecord::Migration
def change
create_table :relationships do |t|
t.integer :follower_id
t.integer :relations_id
t.string :relations_type
t.timestamps
end
end
end
My Relationship model is
class Relationship < ActiveRecord::Base
attr_accessible :relations_id
belongs_to :relations, :polymorphic => true
has_many :followers, :class_name => "User"
end
In my User model
has_many :relationships, :foreign_key => "supporter_id", :dependent => :destroy
and in the other 3 models
has_many :relationships, :as => :relations
Am I missing something with setting up this association?
You basically have it right, except for a few minor errors:
attr_accessible :relations_id is redundant. Remove it from your Relationship model.
Both Relationship and User models call has_many to associate with each other. Relationship should call belongs_to because it contains the foreign key.
In your User model, set :foreign_key => "follower_id".
Here is how I would do it.
Have a Follow middle class with polymorphic association on the followable content side and has_many on the follower user side (user has many follows).
First, create a follows table:
class CreateFollows < ActiveRecord::Migration
def change
create_table :follows do |t|
t.integer :follower_id
t.references :followable, :polymorphic => true
t.timestamps
end
end
end
Replace Relationship model with a Follow model:
class Follow < ActiveRecord::Base
belongs_to :followable, :polymorphic => true
belongs_to :followers, :class_name => "User"
end
Include in User model:
has_many :follows, :foreign_key => :follower_id
Include in your three followable classes:
has_many :follows, :as => :followable
You can now do this:
TheContent.follows # => [Follow,...] # Useful for counting "N followers"
User.follows # => [Follow,...]
Follow.follower # => User
Follow.followable # => TheContent
I've got a Project model, and a Contact model. The Project model has an owner and a client, both of which are Contacts. I've obviously got something ambiguous going on, because if I have a contact and ask for its projects, Rails won't know whether I'm asking for it's projects where it's the client or where it's the owner. So far I've got this:
class Contact < ActiveRecord::Base
has_many :projects
end
class Project < ActiveRecord::Base
belongs_to :owner, :class_name => 'Contact', :foreign_key => 'owner_id'
belongs_to :client, :class_name => 'Contact', :foreign_key => 'client_id'
end
How do I make two relationships here?
Its similar to the way belongs_to is defined in the other class.
So Basically
class Contact < ActiveRecord::Base
has_many :projects_owned, :class_name => "Project", :foreign_key => "owner_id"
has_many :projects_as_client, :class_name => "Project", :foreign_key => "client_id"
end
names of associations could be better. The Single Table inheritance approach described before me is also a neat way, but go for it if you have a lot of different behaviour for each of the OwnerContact and ClientContact class, otherwise it might be just a useless overhead.
I think here's should be polymorphic association, something like this
class Owner < ActiveRecord::Base
has_many :projects, :as => :person
end
class Client < ActiveRecord::Base
has_many :projects, :as => :person
end
class Project < ActiveRecord::Base
belongs_to :person, :polymorphic => true
end
Now you can retrieve projects by #client.projects or #owner.projects. If you want to get person from #project you should add to Project migration this:
class CreateProjects < ActiveRecord::Migration
def self.up
create_table :projects do |t|
t.references :person, :polymorphic => true
t.timestamps
end
end
...
You should try to use a single table inheritance on the Contact table. All you need to do for this to work is to implement a 'type' column (string). Rails will handle the rest
class Contact < ActiveRecord::Base
# implement a type column
has_many :projects
end
class OwnerContact < Contact
end
class ClientContact < Contact
end
class Project < ActiveRecord::Base
belongs_to :owner, :class_name => 'OwnerContact'
belongs_to :client, :class_name => 'ClientContact'
end
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