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.
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 have a situation where User has_one :address and Address belongs_to :user.
I need to be able to validate the address object in these cases:
After a user has signed up, he has an option to partly fill in the address form. In this state I would like to validate for example validates :phone_number, :postal_code, numericality: true but the user can leave the field blank if he wants to.
When user is making a purchase he has to complete the address form. And all the fields have to be validated by validates presence: true + previous validations.
I understand that one approach would be to attach another parameter to the form (i.e.full_validation) and then add a custom validation method that would check for this parameter and then fully validate all attributes.
I was just wondering is there a more code efficient and easier way to do this.
So far I have only found ways to validate some attributes (seethis blog post) but I have not yet found suggestions on how to invoke part of the validation process for certain attributes.
Any help/suggestions will be appreciated :)
#app/models/user.rb
class User < ActiveRecord::Base
has_one :address, inverse_of: :user
end
#app/models/address.rb
class Address < ActiveRecord::Base
belongs_to :user, inverse_of: :address
validates :phone_number, :postal_code, numericality: true, if: ["phone_number.present?", "postal_code.present?"]
validates :x, :y, :z, presence: true, unless: "user.new_record?"
end
--
After a user has signed up
Use if to determine if the phone_number or postal_code are present.
This will only validate their numericality if they exist in the submitted data. Whether the User is new doesn't matter.
--
When user is making a purchase
To make a purchase, I presume a User has to have been created (otherwise he cannot purchase). I used the user.new_record? method to determine whether the user is a new record or not.
Ultimately, both my & #odaata's answers allude to the use of conditional evaluation (if / unless) to determine whether certain attributes / credentials warrant validation.
The docs cover the issue in depth; I included inverse_of because it gives you access to the associative objects (allowing you to call user.x in Address).
If you give more context on how you're managing the purchase flow, I'll be able to provide better conditional logic for it.
For your first use case, you can use the :allow_blank option on validates to allow the field to be blank, i.e. only validate the field if it is not blank?.
http://guides.rubyonrails.org/active_record_validations.html#allow-blank
For both use cases, you can tell Rails exactly when to fire the validations using the :if/:unless options. This is known as Conditional Validation:
http://guides.rubyonrails.org/active_record_validations.html#conditional-validation
For Address, you might try something like this:
class Address
belongs_to :user
validates :phone_number, :postal_code, numericality: true, allow_blank: true, if: new_user?
def new_user?
user && user.new_record?
end
end
This gives you an example for your first use case. As for the second, you'll want to use conditional validation on User to make sure an address is present when the person makes a purchase. How this is handled depends on your situation: You could set a flag on User or have that flag check some aspect of User, e.g. the presence of any purchases for a given user.
class User
has_one :address
has_many :purchases
validates :address, presence: true, if: has_purchases?
def has_purchases?
purchases.exists?
end
end
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.
Suppose I have an ActiveRecord association like:
class City < ActiveRecord::Base
belongs_to :state
end
A city without a state should be invalid. It seems that both of these are possible validations:
validates :state, presence: true
# OR
validates :state_id, presence: true
I would guess that they are identical, since:
belongs_to creates methods state and state=
state= sets the state_id
However, I've just fixed a failing spec by changing it to check for the id instead of the object.
Are these two ways of validating both acceptable? If so, when would you use one or the other?
validates :state will use the relationship from city to state (the belongs_to) along with the foreign key whereas validates :state_id alone will just use the column state_id and see if it has any value at all.
My preferred method is to validate state (the relationship) as this requires both the key and the relationship to be present.
Validating state_id will work, in that it will make sure that a state id exists, however it won't check for the validity of the code, i.e. that a state actually 'exists' for any given state key in City.
Basically if the foreign keys (for state_id) used in City all exist as actual records in State, the effect is the same. The difference would show if you had an invalid state code in state.
What if you did something like
s = State.new
c = City.new
c.state = s
c.valid?
I haven't tried this but I'm guessing that, if you're checking for the presence of c.state_id, it will be missing, even though c does have a state (because the ID hasn't been generated yet, because the state hasn't been saved yet).
That is to say, if what you care about is the presence of the state, you should validate the presence of the state.
Personally, I prefer to allow the model to be more robust and accept either or. So in your particular situation, City could accept either a State object or a state_id, but is required to send one of them.
class City < ActiveRecord::Base
attr_accessible :state, :state_id
validates :state, presence: true, if: proc{|c| c.state_id.blank? }
validates :state_id, presence: true, if: proc{|c| c.state.blank? }
belongs_to :state
end
Edit: Removed the double negative in the validate statement. Originally had unless: proc{|c| !c.state_id.blank? }
According to Rails 4 Way by Obie Fernandez:
When you're trying to ensure that an association is present, pass its
foreign key attribute, not the association variable itself
validates :region_id, :presence => true
validate :region_exists
def region_exists
errors.add(:region_id, "does not exist") unless Region.exists?(region_id)
end
The book does not explain why you should use this as opposed to
validates :region, :presence => true
But I know that these guys know their stuff.
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