Selecting objects direction from Favorite model - ruby-on-rails

We have these models setup for users, categories and favorites:
class Favorite < ActiveRecord::Base
belongs_to :favoritable, polymorphic: true
belongs_to :user, inverse_of: :favorites
end
class User < ActiveRecord::Base
has_many :favorites, inverse_of: :user
end
class Category < ActiveRecord::Base
has_many :favorites, as: :favoritable
end
There are also some other objects that can be favorited (SubCategories, etc), and I would like to be able to grab the Category objects directly instead of a list of favorites:
#categories = #user.favorites.where(favoritable_type: "Category")
Is there way to grab a list of the actual Category objects through this #user object?

Have you tried just setting up a realtionship in user?
class User < ActiveRecord::Base
has_many :favorites, inverse_of: :user
has_many :categories, through: :favorites
end

Related

Rails Complex Model Association, Shared Document Between Users and Teams

I have a complex model association in mind, and was wondering how I could accomplish it. This is what i want to accomplish.
I have a User and a Document model
A User can create documents. He is now the document admin.
He can add other users to his document, and give them permissions such as Editor, Viewer, Admin
He can also make a team, a group of users, and add multiple teams to his document. Each user on a team that the User has added to his document will also have permissions. A user can belong to many teams.
I am a little bit confused about the associations I will have to setup. This is the code I have so far, which has not incorporated the team aspect:
class User < ApplicationRecord
has_many :participations
has_many :documents, through: :participations
end
class Document < ApplicationRecord
has_many :participations
has_many :users, through: :participations
end
class Participation < ApplicationRecord
belongs_to :user
belongs_to :document
enum role: [ :admin, :editor, :viewer ]
end
I would recommend introducing a Team and TeamMembership models in a similary way to existing models. Also change the belongs_to association on Participation from user to a polymorphic participant.
class Team < ApplicationRecord
has_many :team_memberships
has_many :users, through: :team_memberships
has_many :participations, as: :participant
end
class TeamMembership < ApplicationRecord
belongs_to :team
belongs_to :user
end
class User < ApplicationRecord
has_many :team_memberships
has_many :teams, through: :team_memberships
has_many :participations, as: :participant
end
class Participation < ApplicationRecord
belongs_to :participant, polymorphic: true
belongs_to :document
enum role: [ :admin, :editor, :viewer ]
end
class Document < ApplicationRecord
has_many :participations
# if desired create a users method to conveniently get a list of users with access to the document
def users
#users ||= participations.flat_map do |participation|
case participation.partipant
when User
[participation.participant]
when Team
participation.participant.users
end
end
end
end
I would only add has_many :through associations as you discover a benefit/need to having them. That will reduce complexity of maintaining them unless you have specific use case for them. In the case of User having a teams association, it's pretty obvious that you'll be likely to want to get the teams that the user is a part of and since there's no specific information in the TeamMembership object that you are likely to need in that determination, it's a good has_many :through to have.
EDIT: Added Document model.
Since you already have a participation model, you can use that as the join model between users and teams. Since a user can belong to multiple teams, and a document can have multiple teams, you can use a has_many through relationship between teams and documents. We'll call it the DocumentTeam model.
class User < ApplicationRecord
has_many :participations
has_many :documents, through: :participations
has_many :teams, through: :participations
end
class Participation < ApplicationRecord
belongs_to :document
belongs_to :user
belongs_to :team, optional: true
enum role: [ :admin, :editor, :viewer ]
end
class Team < ApplicationRecord
has_many :participations
has_many :users, through: :participations
has_many :document_teams
has_many :document, through: :document_teams
end
class Document < ApplicationRecord
has_many :participations
has_many :users, through: :participations
has_many :document_teams
has_many :teams, through: :document_teams
end
class DocumentTeam < ApplicationRecord
belongs_to :document
belongs_to :team
end

when to use polymorphic associations vs STI vs another table in rails

I have taken a look at Polymorphic associations and STI but I don't really know if they apply to my specific use case. In my app I have the following relevant classes:
class Restaurant < ApplicationRecord
belongs_to :owner, class_name: "User"
has_many :menus
has_many :dishes
has_many :categories
has_many :bookings
has_many :simple_bookings
mount_uploader :photo, PhotoUploader
end
class Dish < ApplicationRecord
has_many :menu_dishes
has_many :menus, through: :menu_dishes
belongs_to :category
belongs_to :restaurant
mount_uploader :photo, PhotoUploader
end
class Menu < ApplicationRecord
belongs_to :restaurant
has_many :bookings
has_many :categories
has_many :menu_dishes
has_many :dishes, through: :menu_disheshas_many :menu_dishes
end
class MenuDish < ApplicationRecord
belongs_to :dish
belongs_to :menu
end
So far the user flow is the following:
A restaurant owner signs up and creates restaurant dishes. Then this owner creates a menu containing different MenuDishes.
Afterwards, users can search for different menus and book them.
My problem is the following:
I want to implement a feature where owners can add up to 3 MenuDishOptions (that belong to MenuDishes) so that a user can change each MenuDish with the available MenuDishOption for that MenuDish.
In other words, I want each MenuDish to contain many MenuDishOptions. Once a MenuDishOption is selected, the MenuDish passes to be a MenuDishOption and the selected MenuDishOption to a MenuDish.
Therefore the MenuDishOption class would look something like this:
class MenuDishOption < ApplicationRecord
belongs_to :menu_dish
has_one :dish
belongs_to :menu, through: menu_dishes
end
The MenuDish class would be updated to:
class MenuDish < ApplicationRecord
has_many :menu_dish_options
belongs_to :menu
belongs_to :dish
end
Please let me know if I need to share more info and thanks a million to anyone that takes the time to help this newbie.

Validate object based on the owner its parent

I have a has_many through association setup between my artist and album models. To add to this an album has_many tracks. The tracks also have a has_many through association between artists (i.e. featured artist) where the Feature model serves as the join table.
I want to prevent the album artist(s) from being a featured artist on a track. So for instance:
album = Album.find_by(name: "Justified")
track = Album.track.first
artist = Artist.find_by(name: "Justin Timberlake")
track.featured_artists << artist
# ^^ Should raise error since Justin Timberlake is the album's artist
Model Setup
class Album < ActiveRecord::Base
has_many :album_artists
has_many :artists, through: :album_artists
end
class Track < ActiveRecord::Base
belongs_to :album
has_many :features
has_many :featured_artists, through: :features, class_name: "Artist", foreign_key: "artist_id"
end
class AlbumArtist < ActiveRecord::Base
belongs_to :album
belongs_to :artist
validates_uniqueness_of :artist_id, scope: :album_id
end
class Feature < ActiveRecord::Base
belongs_to :track
belongs_to :featured_artist, class_name: "Artist", foreign_key: "artist_id"
end
class Artist < ActiveRecord::Base
has_many :album_artists
has_many :albums, through: :album_artists
has_many :features
has_many :tracks, through: :features
end
Is there a way to accomplish this using one of the out-of-the-box validation methods? If not how would I go about in creating a custom validator without writing a ridiculously long lookup chain to the album's owner, assuming the validator is in the Feature model?
You can test that the intersection (&) of the two artists array is empty.
class Track < ActiveRecord::Base
belongs_to :album
has_many :features
has_many :featured_artists, through: :features, class_name: "Artist", foreign_key: "artist_id"
validate :featured_artists_cannot_include_album_artists
def featured_artists_cannot_include_album_artists
if (album.artists & featured_artists).present?
errors.add(:featured_artists, "can't include album artists")
end
end
end

has_many :through and has_many relationship between same 2 models

A list has one owner (a user). A list also has a number of panelists (also users). I have tried defining the relationships between the three models: User, List, and Panelist. But I'm getting nowhere.
user.rb
class User < ActiveRecord::Base
has_many :lists
has_many :panelMemberships, :through => :panelists, :source => :lists
end
list.rb
class List < ActiveRecord::Base
belongs_to :user
has_many :panelMembers, :through => :panelists, :source => :user
end
panelist.rb
class Panelist < ActiveRecord::Base
belongs_to :list
belongs_to :user
end
I've tried all different combinations but nothing seems to work. Thanks in advance for any help you can provide.
The model also has to have a has_many relationship for whatever the through model is, so wherever you have has_many :x, through: :y, you also need to say has_many :y. You also shouldn't have a panelist model separate from your user model if panelists are users (unless you're doing STI, which you're not). From what I understand, you're trying to do something like this:
class User < ActiveRecord::Base
has_many :owned_lists, class_name: "List", foreign_key: :owner_id # this is for the owner/list relationship
has_and_belongs_to_many :lists # for the normal panelist / list relationship
end
class List < ActiveRecord::Base
belongs_to :owner, class_name: "User"
has_and_belongs_to_many :users
end
Then you'll need to make a migration for a users_lists (with user id and list id) table which will be your join table but won't need its own model. But if you really want to keep the through relationship (good for if you do other stuff with the join model), then you'd do:
class User < ActiveRecord::Base
has_many :owned_lists, class_name: "List", foreign_key: :owner_id # this is for the owner/list relationship
has_many :panel_memberships
has_many :lists, through: :panel_memberships
end
class List < ActiveRecord::Base
belongs_to :owner, class_name: "User"
has_many :panel_memberships
has_many :users, through: :panel_memberships
end
class PanelMembership < ActiveRecord::Base
belongs_to :user
belongs_to :list

ActiveRecord::Associations::CollectionProxy HABTM create/build not working

I have three models. Two are related through a has_and_belongs_to_many association with the appropriate join table and one with an has_many association.
class Item < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :colors
end
class Color < ActivRecord::Base
belongs_to :item
end
class User < ActiveRecord::Base
has_and_belongs_to_many :items
end
I can create new items with a color in the following way:
#item = Item.new(name: "ball")
#item.users << #user
#item.save
#item.colors.create!(name: "blue")
The item is now linked to the user referenced by #user.
But I think there has to be another way to create items for users, like the way I added the color.
#user.item.create!(name: "car")
This doesn't work because the users-array of the created item is empty and the item does now not belong to a user.
What is the problem with this approach?
has_and_belongs_to_many should not be used. ( github )
Instead, use a has_many with the through flag: guides.rubyonrails.org...
Something like:
class Item < ActiveRecord::Base
has_many :user_items
has_many :users, through: :user_items # I can never think of a good name here
has_many :colors
has_many :item_colors, through: :colors
end
class Color < ActiveRecord::Base
has_many :item_colors
has_many :items, through: :item_colors
end
class User < ActiveRecord::Base
has_many :user_items
has_many :items, through: :user_items
end
class ItemColor < ActiveRecord::Base
belongs_to :item
belongs_to :color
end
class UserItem < ActiveRecord::Base
belongs_to :user
belongs_to :item
end
It may seem complicated, but it will solve your problems. Also, consider the situation where many items share the same color, if there isn't a join table there, it would be more difficult to categorize items by color.

Resources