In my rails projects I have a lot of association tables. And I have some validations. Nothing really difficult, and it works almost every times.
But from time to time (like tonight), I have to switch from
validates_presence_of :project_id
validates_presence_of :tag_id
validates_uniqueness_of :project_id, :scope => [:tag_id]
to
validates_presence_of :project
validates_presence_of :tag
validates_uniqueness_of :project, :scope => [:tag]
Do you know the difference ? Do you if one is better than the other ?
From the Rails Guides: http://guides.rubyonrails.org/active_record_validations.html#presence
2.9 presence This helper validates that the specified attributes are not empty. It uses the blank? method to check if the value is either
nil or a blank string, that is, a string that is either empty or
consists of whitespace.
class Person < ActiveRecord::Base
validates :name, :login, :email, presence: true
end
If you want to be sure that an association is present, you'll need to
test whether the associated object itself is present, and not the
foreign key used to map the association.
class LineItem < ActiveRecord::Base
belongs_to :order
validates :order, presence: true
end
So, you should use the second example you gave, which tests if the associated object itself is present, and not the first example, which only tests if the foreign key used to map the association is present.
Related
I'm creating a polling system. I would like all options to be made unique, but only within their respective Poll. I'm using a proc to validate that they are not blank:
class Poll < ActiveRecord::Base
has_many :options
accepts_nested_attributes_for :options, reject_if: proc { |attributes| attributes['option'].blank? }
end
But I'm not sure how to validate their uniqueness. I tried doing it within the Option model but it's not rejecting duplicate options created through the Poll form's f.fields_for:
class Option < ActiveRecord::Base
belongs_to :poll
validates_uniqueness_of :option, scope: :poll_id
end
Is it possible to do it with proc?
Apply uniqueness validation upon option attributes like validates_uniqueness_of :title, :other, scope: :poll_id
You can pass any condition to that proc. So you could do query for that column and see whether any results are returned and reject if it they are.
I'm still pretty new to testing in Rails 3, and I use RSpec and Remarkable. I read through a lot of posts and some books already, but I'm still kind of stuck in uncertainty when to use the association's name, when its ID.
class Project < ActiveRecord::Base
has_many :tasks
end
class Task < ActiveRecord::Base
belongs_to :project
end
Because of good practice, I want to protect my attributes from mass assignments:
class Task < ActiveRecord::Base
attr_accessible :project # Or is it :project_id??
belongs_to :project
end
First of all, I want to make sure that a project never exists without a valid task:
class Task < ActiveRecord::Base
validates :project, :presence => true # Which one is the...
validates :project_id, :presence => true # ...right way to go??
end
I also want to make sure that the assigned project or project ID is always valid:
class Task < ActiveRecord::Base
validates :project, :associated => true # Again, which one is...
validates :project_id, :associated => true # ...the right way to go?
end
...and do I need the validation on :presence when I use :associated??
Thanks a lot for clarifying, it seems that after hours of reading and trying to test stuff using RSpec/Shoulda/Remarkable I don't see the forest because of all the trees anymore...
This seems to be the right way to do it:
attr_accessible :project_id
You don't have to put :project there, too! It's anyway possible to do task.project=(Project.first!)
Then check for the existence of the :project_id using the following (:project_id is also set when task.project=(...) is used):
validates :project_id, :presence => true
Now make sure than an associated Project is valid like this:
validates :project, :associated => true
So:
t = Task.new
t.project_id = 1 # Value is accepted, regardless whether there is a Project with ID 1
t.project = Project.first # Any existing valid project is accepted
t.project = Project.new(:name => 'valid value') # A new valid project is accepted
t.project = Project.new(:name => 'invalid value') # A new invalid (or an existing invalid) project is NOT accepted!
It's a bit a pity that when assigning an ID through t.project_id = it's not checked whether this specific ID really exists. You have to check this using a custom validation or using the Validates Existence GEM.
To test these associations using RSpec with Remarkable matchers, do something like:
describe Task do
it { should validate_presence_of :project_id }
it { should validate_associated :project }
end
validates :project, :associated => true
validates :project_id, :presence => true
If you want to be sure that an association is present, you’ll need to
test whether the foreign key used to map the association is present,
and not the associated object itself.
http://guides.rubyonrails.org/active_record_validations_callbacks.html
attr_accessible :project_id
EDIT: assuming that the association is not optional...
The only way I can get it to thoroughly validate is this:
validates_associated :project
validates_presence_of :project_id,
:unless => Proc.new {|o| o.project.try(:new_record?)}
validates_presence_of :project, :if => Proc.new {|o| o.project_id}
The first line validates whether the associated Project is valid, if there is one. The second line insists on the project_id being present, unless the associated Project exists and is new (if it's a new record, it won't have an ID yet). The third line ensures that the Project is present if there is an ID present, i.e., if the associated Project has already been saved.
ActiveRecord will assign a project_id to the task if you assign a saved Project to project. If you assign an unsaved/new Project to project, it will leave project_id blank. Thus, we want to ensure that project_id is present, but only when dealing with a saved Project; this is accomplished in line two above.
Conversely, if you assign a number to project_id that represents a real Project, ActiveRecord will fill in project with the corresponding Project object. But if the ID you assigned is bogus, it will leave project as nil. Thus line three above, which ensures that we have a project if project_id is filled in -- if you supply a bogus ID, this will fail.
See RSpec examples that test these validations: https://gist.github.com/kianw/5085085
Joshua Muheim's solution works, but I hate being not be able to simply link a project to a task with an id like this:
t = Task.new
t.project_id = 123 # Won't verify if it's valid or not.
So I came up with this instead:
class Task < ActiveRecord:Base
belongs_to :project
validates :project_id, :presence => true
validate :project_exists
private
def project_exists
# Validation will pass if the project exists
valid = Project.exists?(self.project_id)
self.errors.add(:project, "doesn't exist.") unless valid
end
end
I have the following Models:
Language
Itemtype
Item
belongs_to :itemtype
LocalisedItem
belongs_to :item
belongs_to :language
The LocalisedItem model has an attribute called "title".
I want to validate the uniqueness of said "title" attribute. My problem is the scope: It´s supposed to be unique per language (easy) and itemtype, which I could not figure out how to do until now.
My best try...
validates :title, :uniqueness => { :scope => [:language_id, 'item.itemtype_id'] }
...fails with "NoMethodError: undefined method `item.itemtype_id'".
Is there any way to check for uniqueness in the way described?
You can use this format for validate uniqueness with a scope:
validates_uniqueness_of :title, :scope => :language_id
Can anyone figure out what's going on here? I was able to get my code to work the way I want it to, but I can't figure out why validates_associated isn't working as I expect. Here's a snippet of my code:
class Flag < ActiveRecord::Base
belongs_to :user
belongs_to :post
# allow only one flag per post per user
validates_uniqueness_of :user_id, :scope => :post_id
validates :user_id, :post_id, :presence => true
validates_associated :user, :post
attr_accessible :user_id, :post_id
end
With this code I can't save a flag with user_id == nil. I can save a flag with user_id == 12345 (i.e. some user_id not in the database). This is what the validates_associated API specification says:
validates_associated(*attr_names)
Validates whether the associated object or objects are all valid themselves. Works with any kind of association.
...
NOTE: This validation will not fail if the association hasn’t been assigned. If you want to ensure that the association is both present and guaranteed to be valid, you also need to use validates_presence_of.
I was able to get the desired behavior by using this, instead:
validates :user, :post, :presence => true
My understanding of the API specification is that validates_associated checks the associated table to see if a row exists with an id matching the foreign key of Flag provided the foreign key is non-nil. Can anyone offer any insight on this? Am I misunderstanding how validates_associated is supposed to work?
validates_associated simply runs the validations that are specified within the associated object's class, it does nothing in regard to foreign keys.
validates :user_id, :presence=>true ensures the presence of a user_id in your flag record, but that's all.
validates :user, :presence=>true is used on the association itself and ensures that foreign keys are properly set up.
Man... all I got was that validates_presence_of is needed for this to work as you got from the API. Seem's overkill to be checking for association validness, but I'm a noob.
Is it posible to validate the uniqueness of a child model's attribute scoped against a polymorphic relationship?
For example I have a model called field that belongs to fieldable:
class Field < ActiveRecord::Base
belongs_to :fieldable, :polymorphic => :true
validates_uniqueness_of :name, :scope => :fieldable_id
end
I have several other models (Pages, Items) which have many Fields. So what I want is to validate the uniqueness of the field name against the parent model, but the problem is that occasionally a Page and an Item share the same ID number, causing the validations to fail when they shouldn't.
Am I just doing this wrong or is there a better way to do this?
Just widen the scope to include the fieldable type:
class Field < ActiveRecord::Base
belongs_to :fieldable, :polymorphic => :true
validates_uniqueness_of :name, :scope => [:fieldable_id, :fieldable_type]
end
You can also add a message to override the default message, or use scope to add the validation:
class Field < ActiveRecord::Base
belongs_to :fieldable, :polymorphic => :true
validates_uniqueness_of :fieldable_id, :scope => [:fieldable_id, :fieldable_type], :message => 'cannot be duplicated'
end
As a bonus if you go to your en.yml, and enter:
activerecord:
attributes:
field:
fieldable_id: 'Field'
You are going to replace the default 'subject' that rails add to the errors with the one you specify here. So instead of saying: Fieldable Id has been already taken or so, it would say:
Field cannot be duplicated