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 (если это имеет значение для этой проблемы).
Ответы
Ответ 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