Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Doesn't work uniqueness #28

Closed
rockandruby opened this issue Jul 22, 2016 · 3 comments
Closed

Doesn't work uniqueness #28

rockandruby opened this issue Jul 22, 2016 · 3 comments

Comments

@rockandruby
Copy link

Neither validate_uniqueness_of or unique: true for rails5

@andypike
Copy link
Owner

The validate_uniqueness_of and unique Rails validators are from ActiveRecord::Validations which we do not include in Rectify::Form as our form objects are not linked to the database and so these built-in validators can't work. We only include the ActiveModel::Validations as these do not rely on a database connection.

To do this validation, you could do something like the below, starting off with a form object to collect and validate the data:

class SignUpForm < Rectify::Form
  attribute :first_name, String
  attribute :last_name,  String
  attribute :email,      String

  validate :validate_email_uniqueness

  private

  def validate_email_uniqueness
    return if unique_email?

    errors.add(:email, "already in use")
  end

  def unique_email?
    User.where("lower(email) = lower(?)", email).empty?
  end
end

Here we add a check at the time of validation to see if the email address exists (case insensitive). If not, then it passes validation. If it does exists, then validation fails.

However, there is a potential race condition here in that between the time that you check for uniqueness and the time of saving, the email address might be taken by another request. To deal with this case, make sure you have a unique index on the database field to ensure that no duplicates can enter your database. Once that is in place we need a nice way to deal with the error that comes back from the database if you try to insert a duplicate.

To do that, you could use a Rectify::Command such as:

class SignUp < Rectify::Command
  def initialize(form)
    @form = form
  end

  def call
    return broadcast(:invalid) if form.invalid?

    User.create!(form.attributes)

    broadcast(:ok)

  rescue ActiveRecord::RecordNotUnique
    broadcast(:race_non_unique_email)
  end

  private

  attr_reader :form
end

Which you would call from your controller like this:

UsersController < ApplicationController
  def new
    @form = SignUpForm.new
  end

  def create
    @form = SignUpForm.from_params(params)

    SignUp.call(@form) do
      on(:ok)      { redirect_to dashboard_path }
      on(:invalid) { render :new }
      on(:race_non_unique_email) do 
        flash.now[:alert] = "The email address must be unique"
        render :new
      end
    end
  end
end

This way you guarantee that the email address is unique and you handle the race condition in a nice way for the user (better than the Rails default I would say, as that doesn't handle the race condition).

Anyway, hopefully that helps

Thanks for the your interest in Rectify!!

💖

@mhoncharov
Copy link

mhoncharov commented Aug 9, 2016

Hi!

What about to make some optional validation of uniqueness and use model name as an attribute for this validation? And make it as an extension of ActiveModel::Validations?

I think I can implement it if you want.

@andypike
Copy link
Owner

Thanks for the offer of help but I think in this case I would prefer to leave it to the developer to implement their uniqueness rules as this could get complex quite quickly.

Thanks again for the question! 💖

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants