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

后端 未结 8 1083
情深已故
情深已故 2021-01-30 11:45

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 th

相关标签:
8条回答
  • 2021-01-30 12:04

    In Rails 4, perhaps earlier versions you can simply validate on the value of the counter_cache.

    class User
      has_many :things
      validates :things_count, numericality: { less_than: 5 }
    end
    
    class Thing
      belongs_to :user, counter_cache: true
      validates_associated :user
    end
    

    note that I've used :less_than because :less_than_or_equal_to would allow the things_count to be 6 since it is validated after the counter cache update.

    If you want to set a limit on a per user basis, you can create a things_limit column to dynamically compare with the limit value you've set.

    validates :things_count, numericality: { less_than: :things_limit }
    
    0 讨论(0)
  • 2021-01-30 12:08

    I thought I'd chime in here. It seems like most of the answers here fail under race-conditions. I'm trying to limit the number of users who can sign up at a certain price point in our app. Checking the limit in Rails means that 10 simultaneous registrations could get through, even if it exceeds the limit I'm trying to set.

    For example, say I want to restrict registrations to no more than 10. Let's say that I already have 5 users registered. Let's also say that 6 new users attempt to register at the same time. In 6 different threads, Rails reads the number of slots remaining, and it gets the answer 5. This passes validation. Rails then allows all registrations to go through, and I have 11 registrations. :/

    Here's how I solved this problem:

    def reserve_slot(price_point)
      num_updated = PricePoint.where(id: price_point.id)
        .where('num_remaining <= max_enrollments')
        .update_all('num_remaining = num_remaining + 1')
    
      if num_updated == 0
        raise ActiveRecord::Rollback
      end
    end
    

    Using this approach, I never allow more registrations than max_enrollments, even when the app is under load. This is because the validation and increment is done in a single, atomic database operation. Note, too that I always call this method from within a transaction, so it rolls back under failure.

    0 讨论(0)
  • 2021-01-30 12:12

    try this, just like string:

    class User < ActiveRecord::Base
      has_many :things, :dependent => :destroy
      validates :things, length: {maximum: 4}
    end
    
    0 讨论(0)
  • 2021-01-30 12:13

    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

    0 讨论(0)
  • 2021-01-30 12:26

    You should try this.

    class Thing <ActiveRecord::Base
      belongs_to :user
      validate :thing_count, :on => :create
    
      def thing_count
          user = User.find(id)
          errors.add(:base, "Exceeded thing limit") if user.things.count >= 5
      end
    end
    
    0 讨论(0)
  • 2021-01-30 12:27

    You could try validates_length_of and validates_associated:

    class Client < ActiveRecord::Base
    
      has_many :orders
      validates :orders, :length => { :maximum => 3 }
    
    end
    
    class Order < ActiveRecord::Base
    
      belongs_to :client
      validates_associated :client
    
    end
    

    A quick test shows that the valid? method works as expected, but it does not stop you from adding new objects.

    0 讨论(0)
提交回复
热议问题