Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails: How to limit number of items in has_many association (from Parent)

Tags:

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:

The offered solution (for similar issue):

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.

Thoughts toward a more appropriate solution:

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 

This is Rails. If I have to work this hard, I'm probably doing something wrong.

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).

like image 658
Matt Scilipoti Avatar asked Nov 16 '11 14:11

Matt Scilipoti


2 Answers

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 :

https://github.com/senayar/user_things_limit

like image 82
rbinsztock Avatar answered Sep 19 '22 13:09

rbinsztock


Validating on the current count causes the count to become greater than the limit once the save has completed. The only way I've found way to prevent the create from occurring is to validate that before the create, the number of things is less than the limit.

This is not to say that it isn't useful having a validation on the count in the User model, but doing so doesn't prevent User.things.create from being called because the user's count collection is valid until the new Thing object is saved, and then becomes invalid after the save.

class User   has_many :things end  class Thing   belongs_to :user   validate :on => :create do     if user && user.things.length >= thing_limit       errors.add(:user, :too_many_things)     end   end end 
like image 35
Matt Connolly Avatar answered Sep 18 '22 13:09

Matt Connolly