I have a polymorphic association in my application and I want to be able to count the number of users in a group, and list the users in each group. How can I achieve this?
class Group < ApplicationRecord
has_many :user_groups
has_many :users, through: :user_groups
end
class User < ApplicationRecord
has_many :user_groups
has_many :groups, through: :user_groups
end
class UserGroup < ApplicationRecord
belongs_to :user
belongs_to :group
end
I at the moment I can call User.first.groups however I am not able to call User.groups.all and User.all.groups but none of these work.
First off, that's not a polymorphic association. Just FYI.
It's hard to know what you're asking, because to count the users in a group, you do:
group.users.count
When you say, 'list them', do you mean in a view? That would be something like:
<%- group.users.each do |user| %>
# do something with user
<% end %>
Just to expand on the comment, if the class Image had a polymorphic belongs_to association, that might look something like:
# == Schema Information
#
# Table name: images
#
# id :integer not null, primary key
# imageable_id :integer
# imageable_type :string
# created_at :datetime not null
# updated_at :datetime not null
#
class Image < ApplicationRecord
belongs_to :imageable, polymorphic: true
end
As you can see, the Image class has the attributes of imageable_id and imageable_type. Then, if you wanted to have Document have many images, you would same something like:
class Document < ApplicationRecord
has_many :images, as: :imageable
end
Then you could do:
image.imageable
To get the object that the image belongs to. To get all the images for a document, then simply:
document.images
Related
I have the following problem,
A user can have several professions, more than 10. For example, a user may be a doctor, teacher, and N. Each profession has its own attributes.
I could do, Doctor belongs_to User, but if I want to know all the professions of this user I will have to check each row of the User table.
I created the following code
class User < ApplicationRecord
has_many :jobables
end
class Job < ApplicationRecord
belongs_to :user
belongs_to :jobable
end
class Jobable < ApplicationRecord
has_one :job
end
class Medic < Jobable
end
class Programmer < Jobable
end
But I do not know if that would be the best answer
I would think that it would be much easier to do something like:
class User < ApplicationRecord
has_many :user_professions
has_many :professions, through: :user_professions
end
# == Schema Information
#
# Table name: professions
#
# id :integer not null, primary key
# name :string
# created_at :datetime not null
# updated_at :datetime not null
#
class Profession < ApplicationRecord
has_many :user_professions
has_many :users, through: :user_professions
end
class UserProfession < ApplicationRecord
belongs_to :user
belongs_to :profession
end
You could then create logic to ensure that a Profession is only assigned to a User once.
Then, you could simply do:
#user.professions
And get all the Professions for a User.
You could also do:
#profession.users
And get all the Users that belong to the Profession.
Based on the edit to your question, you could do something like:
class UserProfession < ApplicationRecord
belongs_to :user
belongs_to :profession
belongs_to :profession_detail, polymorphic: true
end
In which case you might have something like:
class DoctorDetail < ApplicationRecord
end
And you could do something like:
#user.professional_detail_for(:doctor)
Of course, you would need to implement the professional_detail_for method on the User model which might look something like:
class User < ApplicationRecord
has_many :user_professions
has_many :professions, through: :user_professions
def professional_detail_for(profession_type)
user_profession_for(profession_for(profession_type)).try(:profession_detail)
end
private
def profession_for(profession_type)
Profession.find_by(name: profession_type.to_s)
end
def user_profession_for(profession)
user_professions.find_by(profession: profession)
end
end
That's a little rough, but I imagine you get the idea.
I wanted to know the best way to implement the following:
Users can create a Post and each Post is associated with a US City and State.
I wanted to have a form where there is a dropdown box that a user can select the State and select the relevant City for example: [Hollywood] [California].
I have setup a State and City model.
The State model:
# == Schema Information
#
# Table name: states
#
# id :integer not null, primary key
# name :string default(""), not null
# short :string default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
#
class State < ApplicationRecord
has_many :cities
end
The City model:
# == Schema Information
#
# Table name: cities
#
# id :integer not null, primary key
# name :string
# state_id :integer
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_cities_on_state_id (state_id)
#
class City < ApplicationRecord
belongs_to :state
end
With the Post model
# == Schema Information
#
# Table name: posts
#
# id :integer not null, primary key
# title :string default(""), not null
# body :string default(""), not null
class Post < ApplicationRecord
end
With the Post model I thought I would to a belongs_to and has_many association with references/foreign key setup like:
class Post < ApplicationRecord
belongs_to :city
belongs_to :state
end
class City < ApplicationRecord
belongs_to :state
has_many :posts
end
class State < ApplicationRecord
has_many :cities
has_many :posts
end
The view would use a form_for with a grouped_collection for cities and collection for the states but I don't think this is a good implementation because:
Selecting the city already as the association with the state. It seems redundant having the user select both model options?
The dropdown box in view will load 60000 city records and it will slow down the browser.
I wanted to know if a Polymorphic association would fit my use case and if someone can forward me in the right direction.
My thoughts: I was thinking something where the user selects the the State in a dropdown box eg. California, but enters the city in a text field and if the city does not exists in Cities table it create it, otherwise it will link to it?
Thanks for your help.
What you want is a has_one through: association. This indirect relation tells rails to join through another association and removes the need for a duplicated foreign key.
class Post < ApplicationRecord
belongs_to :city
has_one :state, through: :city
end
To create a full hierarchy you would do it like so:
class Post < ApplicationRecord
belongs_to :city
has_one :state, through: :city
has_one :country, through: :state
end
class City < ApplicationRecord
belongs_to :state
has_one :country, through: :state
has_many :posts
end
class State
belongs_to :country
has_many :cities
has_many :posts, through: :cities
end
class Country < ApplicationRecord
has_many :states
has_many :cities, through: :states
has_many :posts, through: :cities
end
Polymorphic associations are a very different thing altogether - its used when an association can be to different models. In this example a Comment can be belong to a Post or a Page.
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
end
class Post < ApplicationRecord
# this tells AR to look at the `commentable` association on
# Comment.
has_many :comments, as: :commentable
end
class Page < ApplicationRecord
has_many :comments, as: :commentable
end
What is the preferred way of selecting a specific record out of a has_many relation for a specific model in Rails? (I'm using Rails 5.)
I have a model User and a model Picture which are related via the following code:
class User < ApplicationRecord
has_many :pictures, as: :imageable
# ...
end
class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: true
# ...
end
What I want to do is to allow a User to set a profile picture from the images associated with it, so I can call #user.profile_picture on a User object and retrieve the profile picture.
You can add an additional one-to-one relationship.
# create by running:
# rails g migration AddPrimaryPictureToUser
class AddPrimaryPictureToUser < ActiveRecord::Migration[5.0]
def change
add_column :users, :primary_picture_id, :integer
add_index :users, :primary_picture_id
add_foreign_key :users, :pictures, column: :primary_picture_id
end
end
class User < ApplicationRecord
has_many :pictures, as: :imageable
# This should be belongs_to and not has_one as the foreign key column is
# on the users table
belongs_to :primary_picture,
class_name: 'Picture',
optional: true
end
class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: true
has_one :primary_user,
foreign_key: 'primary_picture_id',
class_name: 'User'
# ...
end
The main reason to do it this way vs for example a boolean flag on the pictures table is that having a separate relationship makes it easy to join which is important for avoiding N+1 queries which is an issue if you are listing a bunch of users together with their primary image.
#users = User.includes(:primary_picture).all
Mr X likes only one post made by Mr Y
How can I create a relationship between Mr X and Mr Y to see the Mr Y's posts? (to see the suggested posts)
user.rb
has_many :posts
has_many :liked_posts, through: :liked, source: :post
post.rb
def liked_by?(user)
likes.where(user: user).any?
end
likes.rb
# id :integer not null, primary key
# user_id :integer not null
# like_id :integer not null
Should I use uniq ?
I'm not 100% certain if you mean this, but in case you want to relate User with Post through Like then proceed as:
user.rb
has_many :posts
has_many :likes
has_many :liked_posts, through: :likes
post.rb
has_many :likes
def liked_by?(user)
self.likes.where(user: user).any?
end
likes.rb
belongs_to :user
belongs_to :post
You'd have to add the respective database migrations to add the attributes to likes
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.