Email Uniqueness and Updating data and information

Hi,

I have created a company model with the one validation for email defined like this:

validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }, uniqueness: true, unless: → { self.persisted? && self.email == email_was }

Then in the controller I have defined the update method something like this:

def update @company.skip_email_uniqueness_validation = true if @company.update(company_params) redirect_to @company, notice: ‘Company was successfully updated.’

else
    render :edit 
end 
@company.skip_email_uniqueness_validation = false

end

private def company_params params.require(:company).permit(:name, :owner, :address, :email, :avatar, :documents, :admin_id) end

Now when I try to update profile for the company without changing the email address, it throws an error that email already exists and does not allow me to update it, but when I remove the uniqueness requirement from the model the data gets updated.

Kindly please help me with the workaround for this issue.

Regards, Asim Khan

Try separating out your validators into two different ones, like this:

class Company < ApplicationRecord
  validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
  validates :email, uniqueness: true, unless: -> { self.persisted? && !email_changed? }
end

Note that !email_changed? is functionally the same thing as self.email == email_was.

(EDIT: :id not needed in the params hash.)

Thanks, I will try that out and update you on that.

P.S. For the company params, is passing in the id necessary?

Regards, Asim Khan

Hehe, you’re right about :id! Silly me. I had just quickly set up your example using The Brick in order to test it out, and then had copied over the params hash it auto-builds.

Based on this have put out a commit to exclude :id in the root part of the params hash.

Have fun with Rails!

1 Like

Hi, I tried your approach and made changes to the model and separated the 2 validators. The issue with email still exists, but when I comment out the uniqueness the changes did not get updated, rather it shows 2 records one with the old values and a new one with the newer values. It seems that it does not update the data but rather creates a new data instead.

Regards, Asim Khan

The model: class Company < ApplicationRecord belongs_to :admin

	has_many_attached :documents 
	has_one_attached :avatar
	validates :name, presence: true 
	validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
	#validates :email, uniqueness: true, unless: -> { self.persisted? && !email.changed? }
end

The controller: def update @company.errors.delete(:email) #@company.skip_email_uniqueness_validation = true if @company.update(company_params) redirect_to @company, notice: ‘Company was successfully updated.’

else
    render :edit 
end 
#@company.skip_email_uniqueness_validation = false

end

The params: def company_params params.require(:company).permit(:name, :owner, :address, :email, :avatar, :documents, :admin_id) end

Wow – over here I can’t get it to fail :smile:

Here’s a quick video showing building out the project from scratch.

If you’d like to try the same, in a new Rails project add this in your Gemfile:

gem 'brick'

and then bundle install, and after that here are the terminal commands that I had run:

bin/rails active_storage:install
bin/rails g migration CreateAdmins name
bin/rails g model Company name owner address email documents:attachments avatar:attachment admin:references

after this added your validators to company.rb:

  validates :name, presence: true
  validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
  validates :email, uniqueness: true, unless: -> { self.persisted? && !email_changed? }

And then ran a rails s and everything worked.

Here’s the controller that Brick had auto-generated:

class ::CompanyController < ActionController::Base
  def index
    @companies = Company.order("id")
    @companies._brick_querying(params, brick_col_names: true)
  end
  def show
    find_company
  end
  def new
    @company = Company.new
  end
  def create
    @company = Company.create(company_params)
  end
  def edit
    find_company
  end
  def update
    find_company.update(company_params)
  end
  def destroy
    find_company.destroy
  end
private
  def find_company
    id = params[:id]&.split(/[\/,_]/)
    @company = Company.find(id.is_a?(Array) && id.length == 1 ? id.first : id)
  end
  def company_params
    params.require(:company).permit(:name, :owner, :address, :email, :admin_id, :created_at, :updated_at, :avatar, documents: [])
  end
end

Hopefully this is helpful!

I finally found a fix for the issue and it was a very simple fix and had to take a lot of effort to find it, which was a silly mistake on my part. First, I modified my company model like this

class Company < ApplicationRecord
	belongs_to :admin

	has_many_attached :documents 
	has_one_attached :avatar

	validates :name, presence: true 
	validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }, unless: :skip_email_uniqueness_validation?
	validates :email, uniqueness: true, unless: :skip_email_uniqueness_validation?

	attr_accessor :skip_email_uniqueness_validation

	def update_without_email_validation(params)
		self.skip_email_uniqueness_validation = true
		result = update(params)
		self.skip_email_uniqueness_validation = false
		result
	end

	private 

	def skip_email_uniqueness_validation? 
		skip_email_uniqueness_validation
	end 

end

Then for the controller method I modified it as follows

def update @company = Company.find(params[:id]) if @company.update_without_email_validation(company_params) redirect_to @company, notice: ‘Company was successfully updated.’

else
    render :edit 
end

end

private

Only allow a list of trusted parameters through.

def company_params params.require(:company).permit(:id, :name, :owner, :address, :email, :avatar, :documents, :admin_id) end

In the form.html.erb I modified the form from this

<%= form_with(model: @company, url: companies_path, method: :post) do |form| %>

to this

<%= form_with(model: @company) do |form| %>

Now the updation and addition works with ease. Thank you all for the support and help.

Regards, Asim Khan