Подтвердить что ты не робот

Rails: как ограничить количество элементов в ассоциации has_many (от родителя)

Я хотел бы ограничить количество элементов в ассоциации. Я хочу, чтобы у Пользователя не было больше X Вещей. Этот вопрос был задан до, и у решения была логика у ребенка:

Предлагаемое решение (для аналогичной проблемы):

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 будет работать, но мы должны сделать исключение для остановить цепочку. Это заставляет меня обновлять thing_controller для обработки исключений вместо соглашения rails if valid? или 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

Это Rails. Если мне придется тяжело работать, я, вероятно, что-то сделаю неправильно.

Чтобы сделать это, мне нужно обновить родительскую модель, дочерний контроллер, и я не могу следовать конвенции? Я что-то упускаю? Я злоупотребляю has_many, :before_add? Я искал пример, используя: before_add, но не смог найти.

Я думал о переносе проверки на пользователя, но это происходит только при сохранении/обновлении пользователя. Я не вижу способа использовать его, чтобы остановить добавление Thing.

Я предпочитаю решение для Rails 3 (если это имеет значение для этой проблемы).

4b9b3361

Ответ 1

Итак, если вам нужен другой предел для каждого пользователя, вы можете добавить thing_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

Ответ 2

Проверка по текущему счету приводит к тому, что счетчик станет больше предела после завершения сохранения. Единственный способ, которым я нашел способ предотвратить создание из-за того, чтобы проверить, что до создания, количество вещей меньше предела.

Это не означает, что не полезно иметь проверку на счет в модели пользователя, но это не предотвращает вызов 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

Ответ 3

попробуйте это, как строка:

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

Ответ 4

Мне показалось, что я звоню здесь. Похоже, что большинство ответов здесь терпят неудачу в условиях гонки. Я пытаюсь ограничить количество пользователей, которые могут зарегистрироваться с определенной ценой в нашем приложении. Проверка предела в 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, даже если приложение находится под нагрузкой. Это связано с тем, что валидация и приращение выполняются в одной операции с атомной базой данных. Также обратите внимание, что я всегда вызываю этот метод из транзакции, поэтому он откатывается при ошибке.

Ответ 5

Как вы исследовали использование accepts_nested_attributes_for?

accepts_nested_attributes_for: things,: limit = > 5

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

Это говорит о том, что accepts_nested_attributes_for кажется приемлемым только для определенных типов ситуаций. Например, если вы создаете API командной строки, я считаю это довольно ужасным решением. Однако, если у вас есть вложенная форма, она работает достаточно хорошо (большую часть времени).

Ответ 6

Вы можете попробовать validates_length_of и 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

Быстрый тест показывает, что метод valid? работает так, как ожидалось, но не мешает вам добавлять новые объекты.

Ответ 7

В 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_count быть 6, поскольку он проверяется после обновления кэша счетчика.

Если вы хотите установить лимит для каждого пользователя, вы можете создать столбец things_limit для динамического сравнения с установленным предельным значением.

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

Ответ 8

Вы должны попробовать это.

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