I have a Project model and a User model. A project must have a client (User class) and so the Project model has a client_id foreign key.
The User model has a type attribute and will contain 3 if the user is a client.
I want to validate that when a project is assigned to a client, that #user.type is 3.
Project.rb
validates :client_id, presence: true, ##user.type must be 3
belongs_to :client, :class_name => User, :foreign_key => :client_id
User.rb
#constants
TYPES = {
:manager => 1,
:contractor => 2,
:client => 3
}
Not to sure how to go about the validation. I read through the rails guide on validations but still can't seem to get a solution. Any ideas?
Use the inclusion validation helper. Docs here
Here's a quick example from the docs
class Coffee < ActiveRecord::Base
validates :size, :inclusion => { :in => %w(small medium large),
:message => "%{value} is not a valid size" }
end
EDIT:
Ok, I see what you mean. Don't use validation helpers for this, do it manually.
# somewhere in your model (don't be tempted to put this in your controller)
def assigning_client
if #user.type == 3
# do the assignment
else
errors.add(:base, :message => "User must be a client")
end
end
The error will prevent the info from being saved as long as you use the bang version save! which forces validation.
Just a pointer here. Don't use an attribute named type in your activerecord models. It conflicts with the way rails uses STI(Single Table Inheritance) as it uses the type attribute to determine the type of the class when its subclassing another
Related
I'm struggling since some hours in order to make validations of nested attributes work in my rails app. A small caveat is that I have to validate nested attributes dynamically based off of their parent's attributes, as the amount of info required changes over time according to where in the process the parent is.
So here's my setup: I have a parent with many different associated models and I want to validate subsequently nested attributes of those every time I save the parent. Given the fact that validations change dynamically, I had to write a custom validation method in the model:
class Parent < ActiveRecord::Base
attr_accessible :children_attributes, :status
has_many :children
accepts_nested_attributes_for :children
validate :validate_nested_attributes
def validate_nested_attributes
children.each do |child|
child.descriptions.each do |description|
errors.add(:base, "Child description value cant be blank") if description.value.blank? && parent.status == 'validate_children'
end
end
end
end
class Child < ActiveRecord::Base
attr_accessible :descriptions_attributes, :status
has_many :descriptions
belongs_to :parent
accepts_nested_attributes_for :descriptions
end
In my controller I call update_attributes on the parent when I want to save. Now the problem is that, apparently, rails runs the validations against the database and not against the object that was modified by the user or the controller. So what might happen is that a child's value is erased by a user and the validations will pass, while later validations will not pass because the item in the database is not valid.
Here's a quick example of this scenario:
parent = Parent.create({:status => 'validate_children', :children_attributes => {0 => {:descriptions_attributes => { 0 => {:value => 'Not blank!'}}}})
#true
parent.update_attributes({:children_attributes => {0 => {:descriptions_attributes => { 0 => {:value => nil}}}})
#true!! / since child.value.blank? reads the database and returns false
parent.update_attributes({:children_attributes => {0 => {:descriptions_attributes => { 0 => {:value => 'Not blank!'}}}})
#false, same reason as above
The validation works for first-level associations, e.g. if a Child has a 'value' attribute, I could run a validation the way I do. The problem is with deep associations that apparently cannot be validated before saving.
Could anyone point me in the right direction of how to solve this? The only way I currently see is by saving records, validating them afterwards and deleting / reverting them if the validation fails, but I am honestly hoping for something more clean.
Thank you all in advance!
SOLUTION
So it turns out I was running validations on deep nested models by referencing those directly in the custom validation, this way:
class Parent < ActiveRecord::Base
[...]
has_many :descriptions, :through => :children
[...]
def validate_nested_attributes
descriptions.each do |description|
[...]
end
end
end
Which for some reason leads to the problems I was having above. Thanks Santosh for testing my example code and reporting it was working, this pointed me in the right direction to figure this out.
For future reference, the code in the original question works for this sort of dynamic, deeply-nested validations.
I think you should use validates_associated for this
with the following validation in Child
validates :value, :presence => true, :if => "self.parent.status == 'validate_children'"
I am building a simple Ruby on Rails app for problem management. I have a problem model as follows:
class Problem < ActiveRecord::Base
attr_accessible :active, :impact, :incident_number, :issue_description, :root_cause, :user_id, :problem_summary, :incident_priority, :timeline_enabled
attr_accessor :enable_timeline
validates :problem_summary, :length => { :in => 10..100 }
belongs_to :user
has_one :timeline
has_many :actionitems
end
which has a has_many belongs_to association with the model for actionitems:
class Actionitem < ActiveRecord::Base
attr_accessible :completion_date, :description, :initial_due_date, :notes, :problem_id, :revised_due_date, :status, :user_id
belongs_to :problem
end
I would like to be able to update the problem record and save it with some set of limited validations (I still need to add those). However, I would like to have a "Complete problem investigation" button that would trigger a method on the problem controller to set the :active attribute on the problem record to false. I would like to be able to run a different, more complete set of validations on the problem record prior to performing this action and also to validate that all actionitems (if any) that were associated with this problem record are in :status "completed".
The two questions that I have:
How do I perform a specific set of validations only on a given action?
How can I validate that related instances of Actionitem are in status "complete" prior to performing an action on Problem?
This task seems very complex to me. If you could please point me to what I need to utilize in order to be able to achieve this that would be greatly appreciated! (I read on validates :on => :save etc and accepts_nested_attributes_for but I am not sure how to put all of this together to achieve the behavior that I want).
Many thanks for all your help!
try this
validates_length_of :problem_summary, :in => 10..100, :if => :status_active?
def status_active?
self.active == true
end
see in details - validations & validates_length_of
U need to apply checking conditions on validations like
validate :xyz , length => {:in => 1..12}, :if => , :if => lambda {self.active == true }
this validation will only run when aCTIVE IS TRUE. similarly you can add more validation with checking
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 a Meal model that has_many :foods, :through => :servings. Meal also:
accepts_nested_attributes_for :servings, :allow_destroy => true
validates_associated :servings
The Serving model has a field called serving_amount and a field called amount_recorded. In the Serving model, I currently use this validation:
validates :serving_size, :numericality => {:greater_than => 0}, :if => :amount_recorded?
The message returned if the validation fails is terrible. This is compounded by the fact that each meal may have multiple servings.
How do I create a custom validation message that refers to the name of the food for which the serving size is invalid? For example, I would like it to say, "You entered an invalid serving size for Watermelon," if amount_recorded is true for a serving of a Food with name watermelon and the validation fails.
I would try the validates_each method. When calling the method, you pass it a block that will be passed the record instance (from the docs):
validates_each :first_name, :last_name do |record, attr, value|
record.errors.add attr, 'starts with z.' if value.to_s[0] == zz
end
That means you can access any of the records attributes, including the name, and easily construct the error message.
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