Rails: Как ограничить количество элементов в ассоциации has_many (из Parent) - PullRequest
17 голосов
/ 16 ноября 2011

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

Это Рельсы.Если мне нужно усердно работать, я, вероятно, что-то делаю не так.

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

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

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

Ответы [ 8 ]

26 голосов
/ 15 марта 2013

Так что, если вы хотите различный лимит для каждого пользователя, вы можете добавить в файл things_limit: integer и сделать

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

6 голосов
/ 31 октября 2013

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

Это не означает, что это бесполезно, еслипроверка счетчика в модели User, но это не препятствует вызову 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 голосов
/ 06 июля 2016

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

2 голосов
/ 14 апреля 2015

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

class User < ActiveRecord::Base
  has_many :things, :dependent => :destroy
  validates :things, length: {maximum: 4}
end
0 голосов
/ 25 марта 2015

В 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 }
0 голосов
/ 29 сентября 2012

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

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 голосов
/ 17 ноября 2011

Вы можете попробовать 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? работает, как и ожидалось, но он не останавливает вас от добавления новых объектов.

0 голосов
/ 17 ноября 2011

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

accept_nested_attributes_for: вещи,: предел => 5

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

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

...