Rails STI validation should have one - ruby-on-rails

How do I do a (I think a custom) validation to determine that my model should have one of either of my STI models?
My models are like this:
class Account < ActiveRecord::Base
has_many :users
has_one :admin, class_name: Admin, dependent: :destroy
has_many :members, class_name: Member, dependent: :destroy
accepts_nested_attributes_for :admin, reject_if: proc { |attributes| attributes['name'].blank? }
accepts_nested_attributes_for :members, reject_if: proc { |attributes| attributes['name'].blank? }
# Validate should have one of either a member or a user
# validates :users, ...
end
class User < ActiveRecord::Base
end
class Admin < User
end
class Member < User
end
I want to validate, when an account is created it should have one admin or at least one member.
I can provide more information if needed.
Thanks!

You might want to add an error if both of them are not present by using a custom validation.
class Account < ActiveRecord::Base
validate :require_at_least_one_user
def require_at_least_one_user
errors.add(:user, "At least one user is required.") if self.admin.blank? && self.members.blank?
end
end

I would validate the presence of at least one user, regardless of its type.
Maybe this can help.
Good Luck.

Related

Trouble with validation check for has_many :through association; how to create?

Rails newbie here. I am having trouble with ensuring the presence of at least one "has_many :through" relationship in my users model.
I have:
class User < ApplicationRecord
has_many :company_users, dependent: :destroy
has_many :companies, through: :company_users
#validates :company_users, presence: true
end
class Company < ApplicationRecord
has_many :company_users, dependent: :destroy
has_many :users, through: :company_users
end
class CompanyUser < ApplicationRecord
belongs_to :user
belongs_to :company
end
I got the commented validates line from the excellent answers at:
Validate that an object has one or more associated objects
Rails 3: validate presence of at least one has many through association item
However when I implement this line, then doing the following in rails console:
c = Company.create
c.users.create!(email: "test#example.com")
gives
`raise_validation_error': Validation failed: Company users can't be blank (ActiveRecord::RecordInvalid)
From my newbie perspective, it seems like the HMT entry isn't created until after the user is created, which creates a validation error preventing the user in the first place! It's probably a very simple error, but how do I get the above behaviour to work with that validation in place?
I have tried setting inverse_of in a couple place, without any success.
And before it's mentioned, I'm purposefully using HMT instead of HABTM because I have additional attributes on the company_users model that will be set.
One option would be to change the validation to
validates :companies, presence: true
and then create the user with
User.create(companies: [Company.create])
Edit:
Adding inverse_of to the CompanyUser model should also work. The validation would be left as validate :company_users, presence: true:
class CompanyUser
belongs_to :user, inverse_of: :company_users
belongs_to :company
end

Rails 4.2 has_many through

Problem
I have two models a client and a user.
A client can have many administrators and a user can be the administrator of many clients.
I found a couple of people that suggest using has_many :through is the better way to model this relationship in my situation versus has_and_belongs_to_many.
User model
class V1::User < ActiveRecord::Base
has_many :administrators,
class_name: 'V1::ClientAdministrator'
has_many :clients,
through: :administrators
class_name: 'V1::Clients'
Client model
class V1::Client < ActiveRecord::Base
has_many :users,
class_name: "V1::User"
has_many :administrators,
through: :users,
class_name: "V1::ClientAdministrator"
validates :administrators,
length: { minimum: 1}
ClientAdministrator model
class V1::ClientAdministrator < ActiveRecord::Base
belongs_to :v1_user
belongs_to :v1_client
end
Demo using rails c
u = V1::User.create!(name: 'test_user')
c = V1::Client.new(name: 'test_client')
c.administrators << u
ActiveRecord::AssociationTypeMismatch: V1::ClientAdministrator(#70347494104080) expected, got V1::User(#70347494299440)
Before switching to has_many :through I was successfully using has_and_belongs_to_many:
class V1::Client < ActiveRecord::Base
has_and_belongs_to_many :administrators,
-> { uniq },
join_table: :v1_client_administrators,
foreign_key: "v1_client_id",
association_foreign_key: "v1_user_id",
class_name: "V1::User"
The problem with this approach was I was not able to do any validation on the association such as before_destroy make sure there is still one administrator. Additionally, it's likely that I'll add metadata to that relationship in the future.
The ASK
How can I get / set the administrators of the client?
Is there any way that client.administrators would be an array of users instead of forcing me to client.administrators.each { |admin| admin.user} to get access to the user? (If I eventually add metadata this probably can't doesn't make sense)
Is there a way to restrict the use of client.users in favor of client.administrators?
Do model concerns help here?
Is this the right approach for ensuring there is always at least one administrator?
I believe here is what you're looking for, please namespace appropriately:
class User
has_many :clients_users
has_many :clients, through: :clients_users
end
class Client
has_many :clients_users
has_many :administrators, through: :clients_users, source: :user
validates :adminstrators, presence: true # I think this should ensure at least one admin
end
class ClientsUser
belongs_to :client
belongs_to :user
end
Client.first.administrators # fetch all adminstrators
Client.first.adminstrators << User.first # add an administrator

Inject attribute from association table into the associated record?

I have the following model in my Rails app:
class Course < ActiveRecord::Base
has_many :memberships
has_many :members, through: :memberships, class_name: 'User'
end
While course.members successfully returns the course's members, I don't get access to the Membership model which has a role attribute.
How do I find the user role without having to find the Membership given my Course and User? Can I inject the role attribute to User somehow in the context association?
User model:
class User < ActiveRecord::Base
has_many :memberships, dependent: :destroy
has_many :courses, through: :memberships
end
Here's a solution, not the most beautiful and idiomatic though
class Course < ActiveRecord::Base
has_many :memberships
def members
User.joins(:memberships).where(id: memberships.ids).select('users.*, memberships.role')
end
end
A better approach, suggested through the comments:
has_many :members, -> { select('users.*, memberships.role') }, class_name: 'User', through: :memberships, source: :user
I had the exact same problem, and couldn't find any way to fetch the intermediate association without explicitly finding it. It's not that ugly, though:
class User < ActiveRecord::Base
has_many :memberships, dependent: :destroy
has_many :courses, through: :memberships
def role_in(course)
memberships.find_by!(course: course).role
end
end
user.role_in(course)
# => "teacher"
I think dynamically storing the contextual role depending on what Course is used is not a good idea. It feels hacky to me because the attribute value becomes set implicitly rather than by explicit execution. A User would appear different depending on where and how it's instantiated, despite representing a row in the database.
I would instead implement something like below:
class User
def course_role(course)
memberships.find_by_course(course).role
end
end
class Course
def user_role(user)
members.find_by_user(user).role
end
end
This way, it's explicit that calling methods on either the User or Course to get the role depends on the respective memberships they are associated with.

Validation on a has_many relationship in rails 3.2

Given that I have the next models:
user.rb
has_many :favorites, dependent: :destroy
has_many :sports, through: :favorites
sport.rb
has_many :favorites, dependent: :destroy
has_many :users, through: :favorites
In the form for create a new user, there is a list of checkboxes of sports, and in the user validation I want to validate that at least one is selected.
I'm doing it like this:
In the user controller create action:
#user = User.new(params[:user])
#user.sports << Sport.find(params[:sports]) unless params[:sports].nil?
if #user.save ...
In the user model
validate :user_must_select_sport
def user_must_select_sport
if sports.empty?
errors.add(:Sport, "You have to select at least 1 sport")
end
end
And it's actually working, but I'm guessing that it has to be a better way of doing this. I'd appreciate any help.
You can use "validates_presence_of"
class User < ActiveRecord::Base
has_many :sports
validates_presence_of :sports
end
But there is a bug with it if you will use accepts_nested_attributes_for with :allow_destroy => true.
You can look into this : Nested models and parent validation

Rails make a model require another model

If I have a has_and_belongs_to_many relationship between two models, let's say Users and Accounts, can I require that a User have at least one Account, and how?
Also, using the has_and_belongs_to_many relationship, is it possible for an Account not to have a User?
What I need is a relationship where Accounts can live on their own, and belong to Billers, but they can also belong to Users if a User signed up with one. Is this possible, and how?
I personally would drop the the HABTM. Instead I would use has_many :though=>
You would need to create two new models, account_users, and account_billers. You likely already have join tables for the HABTM, but this will expose them as models so they will need ID fields.
So you would end up with something like the following:
class Account < ActiveRecord::Base
has_many :account_billers
has_many :account_users
has_many :billers, :through=> :account_billers
has_many :users, :through=> :account_users
end
class User < ActiveRecord::Base
has_many :account_users
has_many :accounts, :through=>:account_users
validates :accounts, :length => { :minimum => 1}
end
class Biller < ActiveRecord::Base
has_many :account_billers
has_many :accounts, :through=>:account_billers
validates :accounts, :length => { :minimum => 1}
end
class AccountUser < ActiveRecord::Base
belongs_to :user
belongs_to :account
end
class AccountBiller < ActiveRecord::Base
belongs_to :biller
belongs_to :account
end
To validate presence of at least one association, you might want to use a custom validation method, something like
class User < ActiveRecord::Base
has_and_belongs_to_many :accounts
validate :require_at_least_one_account
private
def require_at_least_one_account
errors.add(:accounts, "must amount to at least one") if accounts.size < 1
end
end
(Although this brings a question of how an account is shared between users)
For your second question, looks like polymorphic associations are what you're looking for, but you can't do this straight with a HABTM relationship, you'll have to change it to a has_many :through and introduce a join model.

Resources