Unique has_many through combination - ruby-on-rails

I just have this simple setup:
class Team < ActiveRecord::Base
has_many :players
has_many :users, -> { uniq }, through: :players
end
class User < ActiveRecord::Base
has_many :players
has_many :teams, -> { uniq }, through: :players
end
class Player < ActiveRecord::Base
belongs_to :team
belongs_to :user
validates :user_id, :uniqueness => { :scope => :team_id }
end
With this I can create multiple Teams with the same user combination by calling this twice:
Team.create(user_ids: ["1","2"])
How to make sure that there is not already another team with these users?

Related

Rails: has_many through multiple models

I am trying to access all the projects for which there are expenses from or payments to a given supplier.
class Project < ApplicationRecord
has_many :payments #, dependent: :destroy
has_many :expenses, dependent: :restrict_with_error
has_many :suppliers,-> { distinct }, through: :expenses
end
class Supplier < ApplicationRecord
has_many :expenses, dependent: :destroy
has_many :payments, dependent: :destroy
has_many :projects, through: :expenses
end
class Expense < ApplicationRecord
belongs_to :project
belongs_to :supplier #, optional: true
end
class Payment < ApplicationRecord
belongs_to :project
belongs_to :supplier
scope :belongs_to_project, -> (project_id) { joins(:project).where("projects.id = ?", "#{project_id}")}
end
What would be ideal is if I could do
class Supplier < ApplicationRecord
has_many :projects, through: [:expenses, :payments]
end
but since that does not work, I have resorted to
def balances
project_ids = #supplier.projects.pluck(:id)
Project.all.each do |project|
#if there are no expenses (project not in #supplier.projects) but there are payments to supplier for the project, append the project.id to project_ids
if !project_ids.include? project.id and #supplier.payments.belongs_to_project(project.id).any?
project_ids << project.id
end
end
#projects = Project.find( project_ids )
end
Is there a more elegant way to do this?

ActiveRecord grab shared model from polymorphic association

I'm looking for a better way to query Users from 2 different Models used in a polymorphic association. Here is the setup
class Schedule < ApplicationRecord
belongs_to :announcement
has_many :targets, dependent: :destroy
has_many :lists, through: :targets, source: :target, source_type: 'List'
has_many :accounts, through: :targets, source: :target, source_type: 'Account'
end
class Target < ApplicationRecord
# belongs_to :announcement
belongs_to :schedule
belongs_to :target, polymorphic: true
delegate :announcement, to: :schedule
end
class List < ApplicationRecord
belongs_to :account
has_many :targets, as: :target, dependent: :destroy
has_many :lists_users
has_many :users, through: :lists_users
end
class Account < ApplicationRecord
has_many :announcements, dependent: :destroy
has_many :targets, as: :target, dependent: :destroy
has_many :users, dependent: :destroy
end
At the moment I'm solving this by creating a method inside the Schedule model that grabs Users this way:
def subscribers
targets.map(&:target).map(&:users).flatten.uniq
end
I looked at something similar with this question, but didn't seem to solve it.
I would do that like this:
class Schedule < ApplicationRecord
def subscribers
# fetch all associated user IDs
lists_user_ids = lists.joins(:lists_users).distinct.pluck("lists_users.user_id")
accounts_user_ids = accounts.joins(:users).distinct.pluck("users.id")
user_ids = (lists_user_ids + accounts_user_ids).uniq
# fetch users by IDs
User.where(id: user_ids)
end
end

Create ActiveRecord has_one through association

Given the ActiveRecord models and associations below, I need to add a has_one :owner association on the account model to reference a user whose account_user role is set to "owner".
AccountUser model have a role attribute
class AccountUser < ApplicationRecord
enum role: [:user, :admin, :owner]
belongs_to :account
belongs_to :user
end
Account Model Has many users through account users.
class Account < ApplicationRecord
has_many :account_users
has_many :users, through: :account_users
has_one :owner, -> { where(role: :owner) } #, correct options here.
end
User Model Has many accounts through account users
class User < ApplicationRecord
has_many :account_users
has_many :accounts, through: :account_users
end
Try to make an intermediate association account_owner:
class Account < ApplicationRecord
has_many :account_users
has_many :users, through: :account_users
has_one :account_owner, -> { where(role: :owner) }, class_name: 'AccountUser'
has_one :owner, through: :account_owner, source: :user
end

Rails custom validation on has_many association

Given the following models:
Business
class Business < ActiveRecord::Base
## OWNERS / EMPLOYEES
has_many :business_users, as: :business, dependent: :destroy
has_many :users, through: :business_users
accepts_nested_attributes_for :business_users, allow_destroy: true, reject_if: lambda { |a| a[:user_id].blank? || a[:role].blank? }
end
BusinessUser
class BusinessUser < ActiveRecord::Base
belongs_to :business, polymorphic: true
belongs_to :user
enum role: {:owner => 0, :employee => 1, :admin => 2}
validates_presence_of :user
validates :user, uniqueness: { scope: :business, message: "Each user can only have one role" }
end
User
class User < ActiveRecord::Base
has_many :business_users
has_many :businesses, through: :business_users, source_type: Business, source: :business
end
How can i make sure that each business has at least one business_user with role :owner?

How do I properly alias a has_many through a join in Rails ActiveRecord?

Rails/ActiveRecord newbie here. Consider the following models for a Classroom, User and ClassroomEnrollments (join between the two)
class Classroom < ActiveRecord::Base
has_many :fulltime_enrollments, -> { where(duration: 'full-time') }, class_name: "ClassroomEnrollments"
has_many :fulltimers, :through => :fulltime_enrollments, class_name: "User"
has_many :parttime_enrollments, -> { where(duration: 'part-time') }, class_name: "ClassroomEnrollments"
has_many :parttimers, :through => :parttime_enrollments, class_name: "User"
end
class ClassroomEnrollment < ActiveRecord::Base
# columns: user_id, classroom_id, duration
belongs_to :user
belongs_to :classroom
end
class User < ActiveRecord::Base
has_many :classroom_enrollments
has_many :classrooms, :through => :classroom_enrollments
end
The following model for a classroom and classroom_enrollments does not work. Specifically the :fulltimers and :parttimers aliases throw undefined method 'to_sym' for nil:NilClass errors when I try to access them via my_classroom.fulltimers or my_classroom.parttimers.
If I remove the :parttimers alias and rename :fulltimers to :users it works fine (and displays only the full time students), so it seems to me that it has something to do with it figuring out that :fulltimers is of type User, even though I've specified the classname: "User" in the has_many condition.
What am I doing wrong?
Since the source association cannot be inferred automatically, you need specify it using the :source option:
class Classroom < ActiveRecord::Base
has_many(
:fulltime_enrollments,
-> { where(duration: 'full-time') },
class_name: "ClassroomEnrollments"
)
has_many :fulltimers, :through => :fulltime_enrollments, :source => :user
has_many(
:parttime_enrollments,
-> { where(duration: 'part-time') },
class_name: "ClassroomEnrollments"
)
has_many :parttimers, :through => :parttime_enrollments, :source => :user
end
http://guides.rubyonrails.org/association_basics.html#options-for-has-many-source
How about trying a cleaner, more readable approach? Something like this:
class Classroom < ActiveRecord::Base
has_many :classroom_enrollments
has_many :users, through: :classroom_enrollments
def full_timers
users_by_duration("full-time")
end
def part_timers
users_by_duration("part-time")
end
private
def users_by_duration(duration)
users.where(classroom_enrollments: { duration: duration })
end
end
Then:
my_classroom = Classroom.find(1)
my_classroom.full_timers
I stumbled on this while working on something similar. This is will generate the same sql and is a bit easier to look at.
class Classroom < ActiveRecord::Base
has_many :classroom_enrollments
has_many :users, through: :classroom_enrollments
def fulltimers
users.merge(ClassroomEnrollment.full_time)
end
def parttimers
users.merge(ClassroomEnrollment.part_time)
end
end
class ClassroomEnrollment < ActiveRecord::Base
belongs_to :user
belongs_to :classroom
scope :fulltime, ->{ where(duration: 'full-time') }
scope :parttime, ->{ where(duration: 'part-time') }
end
class User < ActiveRecord::Base
has_many :classroom_enrollments
has_many :classrooms, :through => :classroom_enrollments
end

Resources