How to access two polymorphic associations with one query? - ruby-on-rails

In my Rails 6 app I have these models:
class Account < ApplicationRecord
has_many :carriers
has_many :clients
# Unfortunately, these two don't work together. I have to uncomment one of them to get the other one to work:
has_many :people, :through => :carriers # works but omits clients
has_many :people, :through => :clients # works but omits carriers
end
class Carrier < ApplicationRecord
belongs_to :account
has_many :people, :as => :personalizable
end
class Client < ApplicationRecord
belongs_to :account
has_many :people, :as => :personalizable
end
class Person < ApplicationRecord
belongs_to :personalizable, :polymorphic => true
end
How can I access an account's carriers and clients in one query?
I would love to do something like account.people to show all the account's people but haven't found a way to achieve that yet.
How can it be done?

You cannot use same method name for two associations instead you can rename it as carrier_people and client_people and eager load both.
class Account < ApplicationRecord
has_many :carriers
has_many :clients
has_many :carrier_people, :through => :carriers, source: :people # works but omits clients
has_many :client_people, :through => :clients, source: :people # works but omits carriers
end
You can eager load like this.
Account.includes(:carrier_people, :client_people)

Related

Is it possible to have an association with the same model, but some records have another association to go through?

Kind of a difficult concept to put into a title, but here is the use case:
I have users, the users can either belong to a partner or a customer. A partner can have many customers, but all users have a partner either directly or indirectly. Is there any way to create an association that would allow for partner access on any user regardless of whether or not they are directly related to it?
This is my current setup:
class User < ActiveRecord::Base
has_one :partner, through: :user_join, :source => :userable, :source_type => "Licensee"
has_one :customer, through: :user_join, :source => :userable, :source_type => "Customer"
end
class Partner < ActiveRecord::Base
has_many :customers
has_many :user_joins, as: :userable
has_many :users, through: :user_joins
end
class Customer < ActiveRecord::Base
belongs_to :partner
has_many :user_joins, as: :userable
has_many :users, through: :user_joins
end

Right association between three models

I am struggling to find the easiest solution for associating three models:
User
Organization
Role
User and Organization is a HABTM association - one user can have multiple organizations and vice versa.
One user can also have multiple roles, but just one per organization.
Right now I have this in my model:
user.rb
class User < ActiveRecord::Base
has_many :roles, through: :organizations
has_and_belongs_to_many :organizations, :join_table => :organizations_users
end
organization.rb
class Organization < ActiveRecord::Base
has_and_belongs_to_many :users, :join_table => :organizations_users
has_many :roles
end
role.rb
class Role < ActiveRecord::Base
has_many :users, through: :organizations
belongs_to :organizations
end
Does that make sense?
Here are my though:
Given that you're using the has_and_belongs_to_many and given Rails' defaults, your specification of the join_table is redundant
Your has_many :roles, through: :organizations will only work if you have both a role and a user field in the organizations tables, as Rails will expect to do a SQL select of that table looking for those fields.
Since you want users to have up to one one role per organization, then I would think the most straightforward thing would be to add a role field to the organizations_users model, as follows:
user.rb
class User < ActiveRecord::Base
has_many :roles, through: :organizations_users
has_many :organizations, :through => :organizations_users
has_many :organizations_users
end
organization.rb
class Organization < ActiveRecord::Base
has_many :users, :through => :organizations_users
has_many :roles, :through => :organizations_users
has_many :organizations_users
end
organization_user.rb
class OrganizationUser < ActiveRecord::Base
belongs_to :user
belongs_to :organization
belongs_to :role
end
role.rb
class Role < ActiveRecord::Base
end
The above assumes that you have some reason to want Role to continue to be an ActiveModel as opposed to just a string field in the organizations_users table.

association through has_many and multiple belongs to

I've got the following models and associations:
class User < ActiveRecord::Base
has_and_belongs_to_many :songs
end
class Song < ActiveRecord::Base
has_and_belongs_to_many :users
belongs_to :album
delegate :artist, :to => :album, :allow_nil => true
end
class Album < ActiveRecord::Base
has_many :songs
belongs_to :artist
end
class Artist < ActiveRecord::Base
has_many :albums
has_many :songs, :through => :albums
end
I need to be able to call user.albums and user.artists on a regular basis. Is the most efficient option to create has_and_belongs_to_many associations between User and Artist/Album ?
It seems like there should be a better way but I haven't been able to find anything yet.
You could just use has_many :albums, :through => :songs on the user object. Arel(AR) will automatically create the joins for you resulting in one query and will only fetch the albums related to the songs belonging to the User. The same goes for the artists on the User object.

Fetching records through multiple tables

The image shows part of my data model. I would like to fetch all items that are associated with a user (through organizations and items_group). How should I change the models and write this query in the controller? Using :through => organizations I can get all items_groups but I don't how to include one more relation to query related items.
class User < ActiveRecord::Base
has_and_belongs_to_many :organizations
has_many :items_groups, :through => :organizations
end
class Organization < ActiveRecord::Base
has_and_belongs_to_many :users
has_and_belongs_to_many :items_groups
has_many :items, :through => :items_groups
end
class ItemsGroup < ActiveRecord::Base
has_many :items, :inverse_of => :items_group
has_and_belongs_to_many :organizations
has_many :users, :through => :organizations
end
I think you might have to do it back-to-front and find items joined back to your user.
You could define method like this in your User class:
def items
Item.joins(:items_group => {:organizations => :users}).where("users.id" => self.id).select("distinct items.*")
end
The items it returns will be read-only because of the explicit select but I think you'll want that to avoid returning individual items more than once.
If you set in your models the relationships this should work:
users.organizations.item_groups.items
Though for it to work your models should contain this:
class User < ActiveRecord::Base
has_many :organizations, :through => :organization_users
end
class Organization < ActiveRecord::Base
has_many :item_groups, :through => :items_groups_organizations
end
class ItemsGroup < ActiveRecord::Base
belongs_to :item
end
Hope it works for you!

How do you model "Likes" in rails?

I have 3 models: User, Object, Likes
Currently, I have the model: a user has many Objects. How do I go about modeling:
1) A user can like many objects
2) an Object can have many likes (from different users)
So I want to be able to do something like this:
User.likes = list of objects liked by a user
Objects.liked_by = list of Users liked by object
The model below is definitely wrong...
class User < ActiveRecord::Base
has_many :objects
has_many :objects, :through => :likes
end
class Likes < ActiveRecord::Base
belongs_to :user
belongs_to :object
end
class Objects < ActiveRecord::Base
belongs_to :users
has_many :users, :through => :likes
end
To elaborate further on my comment to Brandon Tilley's answer, I would suggest the following:
class User < ActiveRecord::Base
# your original association
has_many :things
# the like associations
has_many :likes
has_many :liked_things, :through => :likes, :source => :thing
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :thing
end
class Thing < ActiveRecord::Base
# your original association
belongs_to :user
# the like associations
has_many :likes
has_many :liking_users, :through => :likes, :source => :user
end
You are close; to use a :through, relation, you first must set up the relationship you're going through:
class User < ActiveRecord::Base
has_many :likes
has_many :objects, :through => :likes
end
class Likes < ActiveRecord::Base
belongs_to :user
belongs_to :object
end
class Objects < ActiveRecord::Base
has_many :likes
has_many :users, :through => :likes
end
Note that Objects should has_many :likes, so that the foreign key is in the right place. (Also, you should probably use the singular form Like and Object for your models.)
Here is a simple method to achieve this. Basically, you can create as many relationships as needed as long as you specify the proper class name using the :class_name option. However, it is not always a good idea, so make sure only one is used during any given request, to avoid additional queries.
class User < ActiveRecord::Base
has_many :likes, :include => :obj
has_many :objs
has_many :liked, :through => :likes, :class_name => 'Obj'
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :obj
end
class Obj < ActiveRecord::Base
belongs_to :user
has_many :likes, :include => :user
has_many :users, :through => :likes
# having both belongs to and has many for users may be confusing
# so it's better to use a different name
has_many :liked_by, :through => :likes, :class_name => 'User'
end
u = User.find(1)
u.objs # all objects created by u
u.liked # all objects liked by u
u.likes # all likes
u.likes.collect(&:obj) # all objects liked by u
o = Obj.find(1)
o.user # creator
o.users # users who liked o
o.liked_by # users who liked o. same as o.users
o.likes # all likes for o
o.likes.collect(&:user)
Models & associations as per naming conventions of rails modeling
class User < ActiveRecord::Base
has_many :likes
has_many :objects, :through => :likes
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :object
end
class Object < ActiveRecord::Base
belongs_to :user
has_many :likes
has_many :users, :through => :likes
end
Also, you can use of already built-in gems like acts-as-taggable-on to have same functionality without code :)

Resources