Validates_presence_of with your own associations, the right way

I am learning how validates_presence_of 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 the role to always belong to an existing project, but I just learned from this example that this can lead to invalid (lost) roles stored in db. So the right way to do this is to insert validates_presence_of :project_id in my role model and it seems to work even if I think it semantically makes sense to check for a project instead of a project identifier.

In addition, I thought that I could put an invalid id (for a non-existing project) if I just check for the presence of project_id, since by default AR does not add integrity checks to the migration, and even if I add them manually some databases do not support them (t .e. MySQL with MyISAM or sqlite). This example proves 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 will not write such code, but I want to prevent such incorrect data in the database.

I wonder how to ensure that the role ALWAYS has a (real and saved) project.

I found a validates_existence gem, but I prefer not to add a gem to my project if it is strictly necessary.

Any thought on this?

Update

validates_presence_of :project and adding :null => false for the project_id column in the wrap seems like a cleaner solution.

+6
source share
2 answers

I tried many combinations of validators, but the purest solution is to use validates_existence gem. With this, 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 last 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 project and validates_presence_of: project in the model) Role#valid? will return true, and Role#save will throw a database-level exception when project_id is null.

+2
source

Rails will try to find the identifier and add a validation error if the object with the identifier 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 that your problem also wants to deal with a situation where the author object is provided but is new, not db. In case the presence check does not work. It will be decided.

 Role.create!(:name => "admin", :project => Project.new) # Validation passes when it shouldn't. 

Update: To some extent, you can mitigate the effect of transferring a fictitious new object by performing validation on the associated project :.

 class Role < ActiveRecord::Base belongs_to :project validates_presence_of :project validates_associated :project end 

If Project.new.valid? - false, then Role.create!(:name => "admin", :project => Project.new) will also cause an error. If, however, Project.new.valid? true, then the above will create a project object when saved.

Does using validates_associated :project help you?

+6
source

Source: https://habr.com/ru/post/889222/


All Articles