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 
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