update nested_attributes_for errors with uniqueness constraint - ruby-on-rails

So I'm working on build a user model in rails and this user model will have an associated email address model. The email address model has a uniqueness constraint on the email. Right now I have it set up so that the user accepts_nested_attributes_for :email_address. This works great on create but on update I get this error:
ActiveRecord::JDBCError: org.postgresql.util.PSQLException:
ERROR: duplicate key value violates unique constraint
"index_email_addresses_on_email"
I can recreate this bug by doing this in the rails console:
u = User.create(:name => "foo", :new_password => "Passw0rd",
:email_address_attributes => {:email => "foo#bar.com"})
u.update({:name => "new name",
:email_address_attributes => {:email => "foo#bar.com"}})
How do I get it to update the name while not caring about the email_address. Which hasn't changed?
Some other notes and code:
I do index my email_address on email and I'm using rails 4.
class User < ActiveRecord::Base
belongs_to :email_address
validates :email_address, :presence => true
accepts_nested_attributes_for :email_address
end
class EmailAddress < ActiveRecord::Base
validates_format_of :email, :with => RFC822::EmailAddress
validates :email, :presence => true
has_one :user
end

When you update your email_address_attributes in this way, you're actually adding a new email_address object for your user. You need to pass the email address's id as an attribute, i.e.:
u.update({:name => "new name",
:email_address_attributes => {:id => u.email_address.id, :email => "foo#bar.com"}})
Or alternatively, you can update the user's email address in a different update statement
u.update({:name => "new name"})
u.email_address.update({:email => "foo#bar.com"})
As for your controller, all you need to do is add the email addresses's :id field as a permitted parameter.
def user_params
params.require(:user).permit(:name, email_address_attributes: [:id, :email])
end
There is more information about Strong Parameters in the Strong Parameters Rails Guide. Check out the More Example section for a setup similar to yours.

If you don't want to validate the e-mail address except on create, you can add that to the validation:
validates :email_address, presence: true, on: :create

Use ":update_only" option in "accepts_nested_attributes_for" like this:
accepts_nested_attributes_for :email_address, :update_only => true
This way active record will update the child record if it already exists, instead of creating a new one. This should take care of the unique constraint.

Related

why doesn't attribute in a model work in Rails SIMPLE

new to rails/ruby, so this (i think) is a very straightforward question. Why doesn't this work in my model
class Subscription < ActiveRecord::Base
attribute :email, :validate => /\A([\w\.%\+\-]+)#([\w\-]+\.)+([\w]{2,})\z/i
end
I get the error
undefined method `attribute' for #<Class:0x00000101218ed0>
The model does exist, as does the column (or attribute?) 'email', so surely I must be able to validate its submission like so.
Rails format helper validates the attributes' values by testing whether they match a given regular expression, which is specified using the :with option
Try:
class Subscription < ActiveRecord::Base
validates :email, format: { with: /\A([\w\.%\+\-]+)#([\w\-]+\.)+([\w]{2,})\z/i,message: "your validation message" }
end
If you are using rails 3.x then you need validates_format_of
validates_format_of :email, :with => /\A([\w\.%\+\-]+)#([\w\-]+\.)+([\w]{2,})\z/i

has_one, reject_if, and accept_nested_attributes_for still triggers validation

I'm trying to provide a place to set a single service login for an account, yet not require that the account owner enter the service login credentials every time the rest of the record is updated.
My understanding is that the :reject_if option on accepts_nested_attributes_for is the way to have the nested hash values ignored. Yet, in Rails 4.1, I'm getting a "password can't be blank".
I've traced through the nested_attributes code and it seems to properly ignore the values, yet nothing I do to avoid the update works. I've even deleted the web_service_user_attributes hash from the params passed to update, so I'm wondering if there is something else going on.
Am I understanding :reject_if correctly for a has_one association?
Parent model code:
class Account
has_one :web_service_user
accepts_nested_attributes_for :web_service_user, :allow_destroy => true, :reject_if => :password_not_specified, :update_only => true
def password_not_specified(attributes)
attributes[:password].blank?
end
end
Child model code:
class WebServiceUser
devise :database_authenticatable
belongs_to :account
validates_uniqueness_of :username
validates_presence_of :password, if: Proc.new{|wsu| !username.blank? }
end
Controller code:
def update
respond_to do |format|
if #licensee.update(account_params)
#etc...
end
private
def account_params
params.require(:account).permit(:name, :area_of_business, :address1, :address2, :city, :state_code, :zip, :website_url, :web_service_user_attributes => [:id, :username, :password, :_destroy])
end
Ok, it appears that my primary goof was trying to validate the presence of :password. I really wanted to validate the length of the password if it existed.
class WebServiceUser
devise :database_authenticatable
belongs_to :account
validates_uniqueness_of :username
validates_length_of :password, :minimum => 14, if: Proc.new { |u| !u.password.nil? }
end

ActiveRecord validate :uniqueness on association

I need to perform the validation to make sure that only one user within a company can exist within a given category.
validates :user_id, :uniqueness => {:scope => [:category, :company_id], :message => "already exists"}
This works except the error message is set on :user_id key.
How can I do the same but set the error on the :user key (validates :user gives an error)?
Here's a simple way to check uniqueness and force the error to be assigned to the :user attribute:
class User < ActiveRecord::Base
validate :user_unique_per_company_per_category
private
def user_unique_per_company_per_category
if self.class.exists?(:user_id => user_id, :company_id => company_id, :category => category)
errors.add :user, 'already exists'
end
end
end
It would be preferable, I think, if you could figure out a way to use the default validation on :user_id, but maybe you have a special use case.
Also, if you're not using this in a form, you might consider assigning the error to :base, since you might confuse future developers who expect the error to appear on :user_id:
errors.add :base, 'already exists'
I don't think this is possible as the validates method of ActiveRecord sends the errors to the method being validated.
So validates :user trys to send to the attr_accessor of :user which doesn't exist in your model.
Though, if you're just trying to make the error message pretty you can:
alias user user_id
And then use :user in your validation.
validates :user, :uniqueness => {:scope => [:category, :company_id], :message => "already exists"}
On a side note, I wouldn't use user in the alias rather something like:
alias the_supplied_user user_id
And then in your validation:
validates :the_supplied_user, :uniqueness => {:scope => [:category, :company_id], :message => "already exists"}

validates_uniqueness_of can't check on unsaved data?

I have a model called Science Subject Choice
class ScienceSubjectChoice < SubjectChoice
belongs_to :subject
belongs_to :subject_preference
validates_associated :subject
validates_associated :subject_preference
#TODO: validation
validates :priority, :presence => true, :numericality => true, :inclusion => {:in => 1..SubjectPreference::MAX_SCIENCE_SUBJECT_CHOICE}
validates_uniqueness_of :subject_id, :scope => :subject_preference_id
validates_uniqueness_of :priority, :scope => :subject_preference_id
end
the uniqueness validator don't work on unsaved data?
How can I solve it?
Solution:
Instead of validating in itself, the parent object should do the validation:
def validate_science_subject_choices_uniqueness
if science_subject_choices.map(&:priority) != science_subject_choices.map(&:priority).uniq
errors[:base] << "Duplicated priority in science subject"
end
end
Validations do not work like that. They are dynamic by nature. If you want database constraints, you have to specify it in your migrations. For instance, a :uniq => true would make sure that a value is unique in your model.

Rails Validation for users email - only want it to validate when a user signs up or updates email address

I have a User model with the usual attributes such as email and hashed_password etc. I want to write a validation that checks for the presence of an email address but only when
1) there isn't one stored in the database for this object (i.e. this is a new user signing up)
2) the user is trying to update their email address.
My current validations
validates_presence_of :email
validates_presence_of :email_confirmation
validates_confirmation_of :email
are obviously preventing me from updating any attributes. I thought of using
validates_presence_of :email , :if :email_validation_required?
def email_validation_required?
self.email.blank?
end
But that wont help with scenario 2 as it will return true because the user does have an password email address in the db.
I cant work out how i can limit it to just those 2 scenarios above.
Can anyone help?
I think EmFi is on to something. But I don't think the validates_presence_of :email should be holding you up. The email should always be present - if it is left blank in the form the parameter will not mess with your save of the user. If it is entered in the form, even for update, it should have an email_confirmation along for the ride.
Give this a try:
validates_presence_of :email
validates_presence_of :email_confirmation, :if => :email_changed?
validates_confirmation_of :email
you have two options. one is to use :on. by default, they are set to :on => :save, but you can do something like this this
validates_presence_of :email, :on => :create
or
validates_presence_of :email, :on => :update
the other options is to use :if, and then pass in a method name or a proc. so something like
validates_presence_of :email, :if => :should_validate
or
validates_presence_of :email, :if => Proc.new { |user| user.signup_stage > 2 }
Hope that helps :)
You want to use an :if clause on the validation that uses the ActiveRecord::Dirty methods:
validates_presence_of :email, :if => Proc.new { |user| user.email_changed?}
N.B. Only works in Rails 2.1 or later.

Resources