Rails:如何限制has_many关联中的项目数(来自Parent)

时间:2011-11-16 14:14:30

标签: ruby-on-rails activerecord callback associations

我想限制关联中的项目数。我想确保用户没有超过X物品。这个问题was asked before和解决方案在孩子身上有逻辑:

提供的解决方案(针对类似问题):

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

硬编码“5”是一个问题。我的限制根据父级更改。物品集合知道相对于用户的限制。在我们的例子中,管理员可以调整每个用户的(物联网)限制,因此用户必须限制其收集的物品。我们可以让thing_count_within_limit向其用户请求限制:

if self.user.things(:reload).count >= self.user.thing_limit

但是,很多来自Thing的用户内省。对用户的多次调用,尤其是(:reload)对我来说是个红色标志。

对更合适的解决方案的想法:

我认为has_many :things, :before_add => :limit_things可行,但我们必须向stop the chain提出异常。这迫使我更新things_controller来处理异常,而不是if valid?if save的rails约定。

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

这是Rails。如果我必须努力工作,我可能做错了。

要做到这一点,我必须更新父模型,孩子的控制器,我不能遵循惯例?我错过了什么吗?我误导了has_many, :before_add吗?我找了一个例子:before_add,但找不到任何。

我考虑过将验证移到用户,但这只发生在用户保存/更新上。我没有看到使用它来阻止添加Thing的方法。

我更喜欢Rails 3的解决方案(如果这个问题很重要)。

8 个答案:

答案 0 :(得分:26)

因此,如果您想为每个用户设置不同的限制,可以将things_limit:integer添加到User中并执行

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

使用此代码,您无法将user.things_limit更新为低于他已经获得的所有内容的数字,当然它限制用户通过他的user.things_limit创建内容。

应用程序示例Rails 4:

https://github.com/senayar/user_things_limit

答案 1 :(得分:6)

验证当前计数会导致计数在保存完成后变得大于限制。我发现阻止创建发生的唯一方法是在创建之前验证事物的数量是否小于限制。

这并不是说对用户模型中的计数进行验证是没有用的,但这样做并不会阻止调用User.things.create,因为用户的计数集合是有效的直到保存新的Thing对象,然后在保存后变为无效。

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

答案 2 :(得分:2)

试试这个,就像字符串一样:

class User < ActiveRecord::Base
  has_many :things, :dependent => :destroy
  validates :things, length: {maximum: 4}
end

答案 3 :(得分:2)

我以为我会在这里发出声音。似乎大多数答案在竞争条件下都失败了。我试图限制在我们的应用中以特定价格点注册的用户数量。检查Rails中的限制意味着10个同时注册可以通过,即使它超过了我试图设置的限制。

例如,假设我想将注册限制为不超过10个。我们说我已经注册了5个用户。我们还要说有6个新用户同时尝试注册。在6个不同的线程中,Rails读取剩余的插槽数,并得到答案5。这通过了验证。然后Rails允许所有注册通过,我有11个注册。 :/

以下是我如何解决这个问题:

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

使用这种方法,我永远不会允许比max_enrollments更多的注册,即使应用程序处于负载状态。这是因为验证和增量是在单个原子数据库操作中完成的。另请注意,我总是在事务中调用此方法,因此它会在失败时回滚。

答案 4 :(得分:0)

您如何使用accepts_nested_attributes_for进行调查?

accepts_nested_attributes_for:things,:limit =&gt; 5

http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

那就是说,我认为accepts_nested_attributes_for似乎只适用于某些类型的情况。例如,如果您正在创建命令行API,我认为这是一个非常糟糕的解决方案。但是,如果你有一个嵌套的表单,它运行得很好(大部分时间)。

答案 5 :(得分:0)

您可以尝试validates_length_ofvalidates_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表明valid?方法按预期工作,但它不会阻止您添加新对象。

答案 6 :(得分:0)

Rails 4中,也许是早期版本,您只需验证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

请注意,我已使用:less_than因为:less_than_or_equal_to允许things_count6,因为在计数器缓存更新后验证它。{/ p >

如果您想为每个用户设置限制,可以创建一个things_limit列,以便与您设置的限制值进行动态比较。

validates :things_count, numericality: { less_than: :things_limit }

答案 7 :(得分:-5)

你应该试试这个。

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