ruby on rails - has_and_belongs_to_many - Rails: Cómo limitar el número de elementos en la asociación has_many(de Parent)
rails callbacks (8)
Me gustaría limitar el número de elementos en una asociación. Quiero asegurarme de que el usuario no tenga más de X cosas. Esta pregunta se hizo antes y la solución tenía la lógica en el niño:
La solución ofrecida (para problema similar):
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
El codificado "5" es un problema. Mi límite cambia según el padre. La colección de las cosas conoce su límite en relación con un usuario. En nuestro caso, un Administrador puede ajustar el límite (de Cosas) para cada Usuario, por lo que el Usuario debe limitar su colección de Cosas. Podríamos tener thing_count_within_limit solicitar el límite a su usuario:
if self.user.things(:reload).count >= self.user.thing_limit
Pero, eso es una gran cantidad de introspección de usuario de Thing. Varias llamadas al usuario y, especialmente, que (:reload)
son señales de advertencia para mí.
Pensamientos hacia una solución más apropiada:
Pensé que has_many :things, :before_add => :limit_things
funcionaría, pero debemos has_many :things, :before_add => :limit_things
una excepción para detener la cadena . ¿Eso me obliga a actualizar el control things_controller para manejar excepciones en lugar de la convención de rieles de if valid?
o 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
Esto es Rails. Si tengo que trabajar tan duro, probablemente estoy haciendo algo mal.
Para hacer esto, tengo que actualizar el modelo principal, el controlador del niño, ¿Y no puedo seguir la convención? ¿Me estoy perdiendo de algo? ¿Estoy abusando de has_many, :before_add
? Busqué un ejemplo usando: before_add, pero no pude encontrar ninguno.
Pensé en mover la validación a Usuario, pero eso solo ocurre en Guardar / actualizar usuario. No veo una forma de usarlo para detener la adición de una Cosa.
Prefiero una solución para Rails 3 (si eso importa para este problema).
¿Cómo investigaste usando accept_nested_attributes_for?
accept_nested_attributes_for: things,: limit => 5
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
Dicho esto, creo que accept_nested_attributes_for parece ser apropiado solo para ciertos tipos de situaciones. Por ejemplo, si estaba creando una API de línea de comandos, creo que es una solución bastante horrible. Sin embargo, si tiene una forma anidada, funciona lo suficientemente bien (la mayoría de las veces).
Deberías intentar esto.
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
En Rails 4
, tal vez en versiones anteriores, simplemente puede validar el valor de 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
tenga en cuenta que he usado :less_than
porque :less_than_or_equal_to
permitiría que things_count
sea 6
ya que se valida después de la actualización de la caché del contador.
Si desea establecer un límite por usuario, puede crear una columna de things_limit
para comparar dinámicamente con el valor límite que ha establecido.
validates :things_count, numericality: { less_than: :things_limit }
Entonces, si desea un límite diferente para cada usuario, puede agregar things_limit: integer en User y hacer
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
con este código no puede actualizar user.things_limit a un número inferior al de todas las cosas que ya obtuvo, y, por supuesto, restringe al usuario para crear cosas por su user.things_limit.
Ejemplo de aplicación de Rails 4:
La validación en el recuento actual hace que el recuento sea mayor que el límite una vez que se haya completado el guardado. La única forma en que he encontrado la forma de evitar que se produzca la creación es validando que antes de la creación, el número de cosas sea menor que el límite.
Esto no quiere decir que no sea útil tener una validación en el recuento en el modelo de Usuario, pero hacerlo no impide que se User.things.create
a User.things.create
porque la colección de recuento del usuario es válida hasta que el nuevo objeto Thing sea guardado, y luego deja de ser válido después de guardar.
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
Pensé que iba a sonar aquí. Parece que la mayoría de las respuestas aquí fallan en condiciones de carrera. Estoy tratando de limitar el número de usuarios que pueden registrarse a un precio determinado en nuestra aplicación. Comprobar el límite en Rails significa que se podrían pasar 10 registros simultáneos, incluso si supera el límite que estoy tratando de establecer.
Por ejemplo, digamos que quiero restringir los registros a no más de 10. Digamos que ya tengo 5 usuarios registrados. Digamos también que 6 usuarios nuevos intentan registrarse al mismo tiempo. En 6 subprocesos diferentes, Rails lee el número de ranuras restantes y obtiene la respuesta 5
. Esto pasa la validación. Rails luego permite que todas las inscripciones pasen, y tengo 11 inscripciones. : /
Así es como resolví este problema:
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
Usando este enfoque, nunca permito más registros que max_enrollments
, incluso cuando la aplicación está bajo carga. Esto se debe a que la validación y el incremento se realizan en una sola operación de base de datos atómica. Tenga en cuenta, también, que siempre llamo a este método desde una transacción, por lo que se deshace en caso de error.
Puedes probar validates_length_of
y 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
Una prueba rápida muestra que el valid?
El método funciona como se esperaba, pero no le impide agregar nuevos objetos.
prueba esto, como una cuerda
class User < ActiveRecord::Base
has_many :things, :dependent => :destroy
validates :things, length: {maximum: 4}
end