rails has_and_belongs_to_many callbacks association active ruby-on-rails activerecord callback associations

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:

https://github.com/senayar/user_things_limit


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