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.
Related
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)
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!
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 :)
I am creating an app that tracks a users employments and where they are in the companies. I need some help trying to route the app, I has made scaffolds of user, company, and department.
user
company (user has_many :through => employments)
department
user.rb
class User < ActiveRecord::Base
#associations
has_many :employments
has_many :companies, :through => :employments
has_one :department, :through => :employments
end
employment.rb
class Employment < ActiveRecord::Base
belongs_to :user
belongs_to :company
belongs_to :department
has_many :employment_histories
end
employment_history.rb
class EmploymentHistory < ActiveRecord::Base
belongs_to :employment
end
company.rb
class Company < ActiveRecord::Base
has_many :employments
has_many :users, :through => :employments
has_many :departments
end
department.rb
class Department < ActiveRecord::Base
belongs_to :company
end
For routing, the simplest way to do it is to declare your resources as resources, and then you access them through the id of the object.
http://guides.rubyonrails.org/routing.html#resource-routing-the-rails-default
Then to find through associations, I suggest you check out this railscast: http://railscasts.com/episodes/3-find-through-association?view=asciicast
If routing and finding through associations doesn't come easily, you will probably want to refactor your hierarchy to make more sense metaphorically. For instance my approach would be:
User has_many :employments and give it default_scope order: 'employments.created_at DESC' in the User model to make sure the most recent employment is first.
Employment has_one :department
Department belongs_to :company
Company has_many :departments
Then you can access all of this data through associations (see link above). You only need to route the resources, and then know how to access them in the right controllers.
I am new to rails, and have a situation that I can't quite get my head around.
Lets say I have two resources, users and widgets.
Users can use widgets, but widgets are also user created, and should be owned by the user that created them. There needs to be a user uses widget, and a user owns widget. Is the following what I am looking for?
Class User < ActiveRecord::Base
has_many :uses
has_many :widgets, :through => :uses
has_many :owns
has_many :widgets, :through => :owns
end
Class Widget < ActiveRecord::Base
has_one :own
has_many :uses
has_many :users, :through => :uses
end
Class Use < ActiveRecord::Base
belongs_to :user
belongs_to :widget
end
Class Own < ActiveRecord::Base
belongs_to :user
belongs_to :widget
end
I would do it slightly different.
Class User < ActiveRecord::Base
has_many :uses
has_many :widgets, :through => :uses
has_many :owned_widgets, :class_name => "Widget"
end
Class Widget < ActiveRecord::Base
belongs_to :owner, :class_name => "User"
has_many :uses
has_many :users, :through => :uses
end
Class Use < ActiveRecord::Base
belongs_to :user
belongs_to :widget
end
I changed the names a bit, you had a name conflict on widget, you cannot have two associations with the same name. I also removed the has_one and just set an owner which will have a foreign_key of owner_id with the class set to User. Other than that, you set the many-to-many relationship up nicely.