on an ActiveModel Object, how do I check uniqueness?

前端 未结 2 970
离开以前
离开以前 2020-12-30 06:38

In Bryan Helmkamp\'s excellent blog post called \"7 Patterns to Refactor Fat ActiveRecord Models\", he mentions using Form Objects to abstract away multi-layer

相关标签:
2条回答
  • 2020-12-30 07:05

    Bryan was kind enough to comment on my question to his blog post. With his help, I've come up with the following custom validator:

    class UniquenessValidator < ActiveRecord::Validations::UniquenessValidator
      def setup(klass)
        super
        @klass = options[:model] if options[:model]
      end
    
      def validate_each(record, attribute, value)
        # UniquenessValidator can't be used outside of ActiveRecord instances, here
        # we return the exact same error, unless the 'model' option is given.
        #
        if ! options[:model] && ! record.class.ancestors.include?(ActiveRecord::Base)
          raise ArgumentError, "Unknown validator: 'UniquenessValidator'"
    
        # If we're inside an ActiveRecord class, and `model` isn't set, use the
        # default behaviour of the validator.
        #
        elsif ! options[:model]
          super
    
        # Custom validator options. The validator can be called in any class, as
        # long as it includes `ActiveModel::Validations`. You can tell the validator
        # which ActiveRecord based class to check against, using the `model`
        # option. Also, if you are using a different attribute name, you can set the
        # correct one for the ActiveRecord class using the `attribute` option.
        #
        else
          record_org, attribute_org = record, attribute
    
          attribute = options[:attribute].to_sym if options[:attribute]
          record = options[:model].new(attribute => value)
    
          super
    
          if record.errors.any?
            record_org.errors.add(attribute_org, :taken,
              options.except(:case_sensitive, :scope).merge(value: value))
          end
        end
      end
    end
    

    You can use it in your ActiveModel classes like so:

      validates :account_name,
        uniqueness: { case_sensitive: false, model: Account, attribute: 'name' }
    

    The only problem you'll have with this, is if your custom model class has validations as well. Those validations aren't run when you call Signup.new.save, so you will have to check those some other way. You can always use save(validate: false) inside the above persist! method, but then you have to make sure all validations are in the Signup class, and keep that class up to date, when you change any validations in Account or User.

    0 讨论(0)
  • 2020-12-30 07:28

    Creating a custom validator may be overkill if this just happens to be a one-off requirement.

    A simplified approach...

    class Signup
    
      (...)
    
      validates :email, presence: true
      validates :account_name, length: {within: 3..40}, format: { with: /^([a-z0-9\-]+)$/i }
    
      # Call a private method to verify uniqueness
    
      validate :account_name_is_unique
    
    
      def persisted?
        false
      end
    
      def save
        if valid?
          persist!
          true
        else
          false
        end
      end
    
      private
    
      # Refactor as needed
    
      def account_name_is_unique
        if Account.where(name: account_name).exists?
          errors.add(:account_name, 'Account name is taken')
        end
      end
    
      def persist!
        @account = Account.create!(name: account_name)
        @user = @account.users.create!(name: name, email: email)
      end
    end
    
    0 讨论(0)
提交回复
热议问题