web-dev-qa-db-fra.com

Rails ne valide l'unicité que si conditionnel

J'ai une classe de questions:

class Question < ActiveRecord::Base
  attr_accessible :user_id, :created_on

  validates_uniqueness_of :created_on, :scope => :user_id
end

Un utilisateur donné ne peut créer qu'une seule question par jour, donc je veux forcer l'unicité dans la base de données via un index unique et la classe Question via validates_uniqueness_of.

Le problème que je rencontre est que je ne veux cette contrainte que pour les utilisateurs non administrateurs. Les administrateurs peuvent donc créer autant de questions par jour qu'ils le souhaitent. Des idées sur la façon de réaliser cela avec élégance?

31
kid_drew

Vous pouvez rendre une validation conditionnelle en passant soit une simple chaîne de Ruby à exécuter, un Proc ou un nom de méthode comme symbole comme valeur à :if Ou :unless Dans les options de validation. Voici quelques exemples:

Avant Rails version 5.2, vous pouviez passer une chaîne:

# using a string:
validates :name, uniqueness: true, if: 'name.present?'

À partir de la version 5.2, les chaînes ne sont plus prises en charge, vous laissant les options suivantes:

# using a Proc:
validates :email, presence: true, if: Proc.new { |user| user.approved? }

# using a Lambda (a type of proc ... and a good replacement for deprecated strings):
validates :email, presence: true, if: -> { name.present? }

# using a symbol to call a method:
validates :address, presence: true, if: :some_complex_condition

def some_complex_condition
  true # do your checking and return true or false
end

Dans votre cas, vous pouvez faire quelque chose comme ceci:

class Question < ActiveRecord::Base
  attr_accessible :user_id, :created_on

  validates_uniqueness_of :created_on, :scope => :user_id, unless: Proc.new { |question| question.user.is_admin? }
end

Jetez un œil à la section validation conditionnelle des guides Rails pour plus de détails: http://edgeguides.rubyonrails.org/active_record_validations.html#conditional-validation

77
Jon

La seule façon que je sache de garantir l'unicité est par le biais de la base de données (par exemple un index unique). Toutes les approches basées sur Rails uniquement impliquent des conditions de course. Compte tenu de vos contraintes, je pense que la chose la plus simple serait d'établir une colonne distincte et uniquement indexée contenant une combinaison du jour et de l'ID utilisateur que vous laisseriez null aux administrateurs.

Pour ce qui est de validates_uniqueness_of, vous pouvez restreindre la validation aux non-administrateurs en utilisant une option if ou unless, comme indiqué dans http://apidock.com/Rails/ActiveRecord/Validations/ClassMethods/validates_uniqueness_of

4
Peter Alfvin

Ajoutez simplement une condition au validates_uniqueness_of appel.

validates_uniqueness_of :created_on, scope: :user_id, unless: :has_posted?
def has_posted
  exists.where(user_id: user_id).where("created_at >= ?", Time.zone.now.beginning_of_day)
end

Mais encore mieux, créez simplement une validation personnalisée:

validate :has_not_posted
def has_not_posted
  posted = exists.where(user: user).where("DATE(created_at) = DATE(?)", Time.now)
  errors.add(:base, "Error message") if posted
end
1
Mohamad