Nested Model Validation Problem and Solution

ror31.jpg

Nested Attribute is one of the helpful feature for Ruby on Rails developers. It gives the privilege to save attributes of a record through its associated parent. You can find details about nested model and nested attribute here.

Model Validation

Validation of models is an important part of development as it ensures that only valid data are saved into model. For instace, it may be necessary for any application to ensure that every user provide valid email or mailing address. It is a good practice because it prohibits error and use of unpermitted data. But validation in nested model cause a problem. I am explaining it with an example.

Suppose we have two model User and Project where a user can have many project. So relationship between these two model is one to many. So our model description are

app/models/user.rb

class User < ActiveRecord::Base
     has_many :projects

Attributes: email:sring, name:string

 end

app/models/project.rb

 class Project < ActiveRecord::Base
     belongs_to :user

Attributes: user:reference, name:string

 end

As there is a has_many relation in user model with project, we can enable nested attribute updating by

app/models/user.rb

class User < ActiveRecord::Base
     has_many :projects

Attributes: email:sring, name:string

     accepts_nested_attributes_for :projects
 end

Now as a good and sincere developer, we will add validation to our models

app/models/user.rb

class User < ActiveRecord::Base
     has_many :projects

     validates_presence_of :email
     validates_presence_of :name

Attributes: email:sring, name:string

     accepts_nested_attributes_for :projects
 end

app/models/project.rb

 class Project < ActiveRecord::Base
     belongs_to :user

     validates_presence_of :user
     validates_presence_of :name

Attributes: user:reference, name:string

 end

Ok now model programming is done. We will now move to controller. We will create an user with some projects at the same time. So our user controller will be like

app/controllers/users_controller.rb

 class UsersController < ApplicationController

     def create
        user = User.new(user_params)
        if user.save
          render json: { success: "Created Successfully"),
        else
          render json: user.errors
        end
      end

      def user_params
        params.require(:user).permit :email, :name, projects_attributes: [:name]
      end
 end

Our controller and models are ready and so we are ready to roll. Now we are going to create an user with this JSON request:

curl -X POST -H "Content-Type: application/json" http://localhost:3000/users -d '{"user":{"name": "Ananta Jalil", "email": "[email protected]", "projects_attributes": [{"name": "acting"}, {"name": "Business"}]}}

But BANG!! It throws an error, right?? and the error is

{projects.user_id: [projects.attribute.user_id.blank]} 😦

Whats The Problem

Actually Rails Validate the presence of associated records in the join record before the parent record exist in the database. So in this case validation of projects run before that existing record is assigned to a saved user. So the user_id is nil when validation of projects is run. And give us the error that user id is blank. So what's the solution?? Can't we use validation in nested models??

Solution

The answer of the above question is Yes, we can. You have to use inverse_of to get rid of this problem. Documentation of invers_of can be found here. Its a good practise to always add this functionality in has_many, belongs_to methods in model. So adding this, our model looks

app/models/user.rb

class User < ActiveRecord::Base
     has_many :projects, inverse_of: :user

     validates_presence_of :email
     validates_presence_of :name

Attributes: email:sring, name:string

     accepts_nested_attributes_for :projects
 end

app/models/project.rb

 class Project < ActiveRecord::Base
     belongs_to :user, inverse_of: :projects

     validates_presence_of :user
     validates_presence_of :name

Attributes: user:reference, name:string

 end

So now we can create our user object with associated project. So after running the above JSON request for creating user we get

{ "success": "Created Successfully")}

Problem solved na??

ha5