I would like to limit the number of items in an association. I want to ensure the User doesn't have more than X Things. This question was asked before and the solution had the logic in the child:
class User < ActiveRecord::Base
has_many :things, :dependent => :destroy
end
class Thing <ActiveRecord::Base
belongs_to :user
validate :thing_count_within_limit, :on => :create
def thing_count_within_limit
if self.user.things(:reload).count >= 5
errors.add(:base, "Exceeded thing limit")
end
end
end
The hard coded "5" is an issue. My limit changes based on the parent. The collection of Things knows its limit relative to a User. In our case, a Manager may adjust the limit (of Things) for each User, so the User must limit its collection of Things. We could have thing_count_within_limit request the limit from its user:
if self.user.things(:reload).count >= self.user.thing_limit
But, that's a lot of user introspection from Thing. Multiple calls to user and, especially, that (:reload)
are red flags to me.
I thought has_many :things, :before_add => :limit_things
would work, but we must raise an exception to stop the chain. That forces me to update the things_controller to handle exceptions instead of the rails convention of if valid?
or if save
.
class User
has_many :things, :before_add => limit_things
private
def limit_things
if things.size >= thing_limit
fail "Limited to #{thing_limit} things")
end
end
end
To do this, I have to update the parent model, the child's controller, AND I can't follow convention? Am I missing something? Am I misusing has_many, :before_add
? I looked for an example using :before_add, but couldn't find any.
I thought about moving the validation to User, but that only occurs on User save/update. I don't see a way to use it to stop the addition of a Thing.
I prefer a solution for Rails 3 (if that matters for this problem).
So if you want a different limit for each user you can add things_limit:integer into User and do
class User
has_many :things
validates_each :things do |user, attr, value|
user.errors.add attr, "too much things for user" if user.things.size > user.things_limit
end
end
class Thing
belongs_to :user
validates_associated :user, :message => "You have already too much things."
end
with this code you can't update the user.things_limit to a number lower than all the things he already got, and of course it restrict the user to create things by his user.things_limit.
Application example Rails 4 :