I'm investigating on how validates_presence_of actually works. Suppose I have two models
class Project < ActiveRecord::Base
[...]
has_many :roles
end
and
class Role < ActiveRecord::Base
validates_presence_of :name, :project
belongs_to :project
end
I want it so that role always belongs to an existing project but I just found out from this example that this could lead to invalid (orphaned) roles saved into the db. So the right way to do that is to insert the validates_presence_of :project_id
in my Role model and it seems to work, even if I think that semantically has more sense to validate the presence of a project instead of a project id.
Besides that I was thinking that I could put an invalid id (for a non existing project) if I just validate the presence of project_id, since by default AR doesn't add integrity checks to migrations, and even if I add them manually some DB does not support them (i.e. MySQL with MyISAM or sqlite). This example prove that
# with validates_presence_of :name, :project, :project_id in the role class
Role.create!(:name => 'foo', :project_id => 1334, :project => Project.new)
AREL (0.4ms) INSERT INTO "roles" ("name", "project_id") VALUES ('foo', NULL)
+----+------+------------+
| id | name | project_id |
+----+------+------------+
| 7 | foo | |
+----+------+------------+
Of course I won't write code like this, but I want to prevent this kind of wrong data in DB.
I'm wondering how to ensure that a role ALWAYS has a (real and saved) project associated.
I found the validates_existence gem, but I prefer to not add a gem into my project unless is strictly necessary.
Any thought on this?
Update
validates_presence_of :project
and adding :null => false
for the project_id column in the migration seems to be a cleaner solution.
Rails will try a find on the id and add validation error if an object with an id is not found.
class Role < AR::Base
belongs_to :project
validates_presence_of :project, :name
end
Role.create!(:name => "admin", :project_id => 1334)# Project 1334 does not exist
# => validation error raised
I see your problem also wants to deal with the situation where the author object is provided but is new and not in db. In the case the presence check doesnt work. Will solve.
Role.create!(:name => "admin", :project => Project.new) # Validation passes when it shouldn't.
Update: To some extent you can mitigate the effect of passing a dummy new object by doing a validation on the associated :project.
class Role < ActiveRecord::Base
belongs_to :project
validates_presence_of :project
validates_associated :project
end
If Project.new.valid?
is false then Role.create!(:name => "admin", :project => Project.new)
will also raise an error. If however, Project.new.valid?
is true then the above will create a project object when saving.
Does using validates_associated :project
help you?
I tried a lot of combinations of validators, but the cleanest solution is to use the validates_existence gem. With that I can write code like this
r = Role.new(:name => 'foo', :project => Project.new) # => #<Role id: nil, name: "foo", project_id: nil, created_at: nil, updated_at: nil>
r.valid? # => false
r.errors # => {:project=>["does not exist"], :project_id=>["does not exist"]}
So my final model is as simple as
class Role < ActiveRecord::Base
belongs_to :project
validates_existence_of :project
# or with alternate syntax
validates :project, :existence => true
[...]
end
With db validation plus Aditya solution (i.e. :null => false in the migration and validates_presence_of :project in the model) Role#valid?
will return true and Role#save
will raise an exception at database level when project_id is null.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With