ActiveRecord validate :uniqueness on association - ruby-on-rails

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

Related

update nested_attributes_for errors with uniqueness constraint

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.

Different types of validations in rails

I have a User model
is there a difference between
class User < ActiveRecord::Base
validates :name, :presence => true
end
and
class User < ActiveRecord::Base
def validate
errors.add_to_base "name should not be nil" if name.nil?
end
end
The validates macro is more flexible, as it also allows you to do things like:
validates :name, :format => { :with => /\A[a-zA-Z]+\z/,
:message => "Only letters allowed" }, :length => { :in => 6..20 }
The validate method is really a quick and easy way to do custom validations when existing ones do not exist. (When custom validations get too complex, then you should usually move them into custom validators and use the validates macro).
See more at http://guides.rubyonrails.org/active_record_validations_callbacks.html
Yes -- the first will fail to save an empty string, whereas the second will allow it.

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.

ActiveRecord validation by parent attribute

I have model Article, for example. Article has_one ArticleContent. ArticleContent has validation of all it's attributes by default. But I need additional functionality - to save draft Article, without any validation.
So I pass :draft => false as one of a parameter in Article.new(), next I do #article.build_article_content(). There is some not working code in ArticleContent:
def draft?
raise self.article.draft
end
validates_presence_of :header, :message => "We have no fuckin' header!", :unless => :draft?
Of course it's not work. At the moment of draft? execution there is no any suitable Article object anywhere, so self.article returns nil. Nice try, codemonkey...
Anyone have some sweet ideas? I think to make #content.save! is not a very good idea
UPDATE
I tried so:
def draft
self[:draft]
end
def draft=(value)
self[:draft] = value
end
def draft?
self[:draft]
end
validates_presence_of :field1, :message => "msg1", :unless => :draft?
validates_presence_of :field2, :message => "msg2", :unless => :draft?
validates_presence_of :field3, :message => "msg3", :unless => :draft?
It works, but how can I group this?
unless self.draft?
validates_presence_of :field1, :message => "msg1"
validates_presence_of :field2, :message => "msg2"
validates_presence_of :field3, :message => "msg3"
end
Says that draft? method is not found. Also i should do
#article.content.draft = #article.draft
And it looks like dirty-dirty hack too
This is a common use case for a state machine. There are several rails plugins that provide for those.
http://ruby-toolbox.com/categories/state_machines.html
If you don't need a full state machine implementation it could still be instructive to have a state column in your ArticleContent model. Its values would be "new", "draft", "published" and so on. Your validations would look at that column's value when deciding what to do, like:
validates :content, :presence => true, :unless => Proc.new { |a| a.state == "Draft" }
(I'm pretty sure that's not the correct syntax but you should get what I'm aiming at.)
To answer your UPDATE
Try with_options.
with_options :unless => :draft? do |o|
o.validates_presence_of :field1, :message => "msg1"
o.validates_presence_of :field2, :message => "msg2"
o.validates_presence_of :field3, :message => "msg3"
end
Looking at your code there's a couple of smells. In order to flunk a validation the thing to do is errors.add(blah), not raise an exception. Also, your methods defined for accessing the draft column look a little redundant. They're just doing what AR would do anyway.

Rails - Why is my custom validation being triggered for only a build command

I have a sentence and correction model with a has_one and belongs_to relationship respectively.
For some reason when I do
def create
#sentence = Sentence.find(params[:sentence_id])
#correction = #sentence.build_correction(params[:correction])
a custom validation I wrote for Correction is being called at the build_correction point. the validation is below
class Correction < ActiveRecord::Base
attr_accessible :text, :sentence_id, :user_id
belongs_to :sentence
belongs_to :user
validate :correction_is_different_than_sentence
def correction_is_different_than_sentence
errors.add(:text, "can't be the same as the original sentence.") if (text == self.sentence.text)
end
the problem is for some reason on validation the correction object doesn't have the sentence id set (despite I used the build_correction method) and so it complains
"you have nil object .... while executing nil.text" in the if clause in the validation above.
So my question is why is the validation occuring for a build command, i thought it only triggers on a create or update. And why isnt the sentence_id getting set?
Some error was creating a lot of headaches for me. Don't know why but moving custom validator call to the end of other validator calls fixed this for me.
Before
validates :name, :short_description, presence: true
validate :uniq_name
validates :price, :numericality => {:greater_than_or_equal_to => 0}
validates_attachment_content_type :image, :content_type => /image/
After
validates :name, :short_description, presence: true
validates :price, :numericality => {:greater_than_or_equal_to => 0}
validates_attachment_content_type :image, :content_type => /image/
validate :uniq_name
here is my custom validator
private
def uniq_name
return if clone?
user_product = self.user.products.unlocked.where(:name => self.name).first
errors[:name] << "has already been taken" if user_product && !user_product.id.eql?(self.id)
end
Try this, may be it will do the trick for you too.
As ever it was not rails fault but my own - its trivial, long to explain, no use to anyone else so wont explain.

Resources