Validate object based on the owner its parent - ruby-on-rails

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

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

How to create an object - has one :through

class Card < ApplicationRecord
has_one :card_rating
has_one :rating, through: :card_rating
end
class Rating < ApplicationRecord
has_many :card_ratings
has_many :cards, through: :card_ratings
end
class CardRating < ApplicationRecord
belongs_to :card
belongs_to :rating
end
I want to do something along the lines of the following:
c = card.card_rating.new
c << rating
But there doesn't seem to be any association going on at all, because already at the first statement I receive the following error:
undefined method `new' for nil:NilClass
You do not need a join table for a one to many relation:
class Card
belongs_to :rating
end
class Rating
has_many :cards
end
Rather you would use has_one :through in case the domain dictates that the relation is indirect.
class Student
belongs_to :school
has_one :headmaster, through: :school
end
class School
has_many :students
end
class Headmaster
belongs_to :school
has_many :students, through: :school
end

Creating a custom validation across two HMT associations

I have two HMT associations. The first is between my Artist and Album models, basically signifying that multiple artists can belong to an album. The other association is setup between my Artist and Track models via TrackFeature. The purpose of this association is add a featured artist to a track.
I'd like to setup a validation that none of the possible album artists cannot be featured artists on any tracks pertaining to that particular album. I know that this validation would need to be on the join model. The problem is that what I have now doesn't seem to work. What do I need to change in order for this to successfully validate?
class Artist < ActiveRecord::Base
has_many :album_artists
has_many :albums, through: :album_artists
has_many :track_features
has_many :tracks, through: :track_features
end
class Album < ActiveRecord::Base
has_many :album_artists
has_many :artists, through: :album_artists
end
class AlbumArtist < ActiveRecord::Base
belongs_to :album
belongs_to :artist
end
class Track < ActiveRecord::Base
has_many :track_features
has_many :featured_artists, through: :track_features
belongs_to :album
end
class TrackFeature < ActiveRecord::Base
belongs_to :track
belongs_to :featured_artist, class_name: "Artist", foreign_key: :artist_id
validate :track_artist
private
def track_artist
if track.album.artists.exists?(name: artist.name)
track.errors[:base] << "Album artist cannot be a featured artist"
end
end
end

Selecting objects direction from Favorite model

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

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

Resources