Let’s say you have a many-to-many setup, with same parent for both left and right side. You might want to create an account, with a user and a project in one go.
# app/models/account.rb class Account < ActiveRecord::Base has_many :users has_many :projects accepts_nested_attributes_for :users end
# app/models/user.rb class User < ActiveRecord::Base belongs_to :account has_many :user_projects, :class_name => "User::Project", dependent: :destroy, inverse_of: :user has_many :projects, through: :user_projects validates_presence_of :account, on: :create # We want to accept nested attributes for projects accepts_nested_attributes_for :project end
# app/models/user/project.rb class User::Project < ActiveRecord::Base belongs_to :user, class_name: "User", inverse_of: :user_projects belongs_to :project, class_name: "Project", inverse_of: :project_users validates_presence_of :user, :project # No need for duplicates validates_uniqueness_of :project, scope: :user_id end
# app/models/project.rb class Project < ActiveRecord::Base belongs_to :account has_many :project_users, :class_name => "User::Project", dependent: :destroy, inverse_of: :project has_many :users, through: :project_users end validates_presence_of :account, on: :create
When you try to create an account with a default user and project you will encounter an issue. The parent account is not set for the project, but it is set for the user. In the above setup it will actually throw a validation error.
It is because the inverse_of value isn’t kept throughout the has_many: through relationship.
The fix was to add the following before_validation rule to the User model (assuming that you create the user first, and then the project as nested attribute to the user).
before_validation :set_account_for_new_projects def set_account_for_new_projects self.user_projects.each do |user_project| # If id is not set, it means this is a new project user_project.project.account = self.account if !user_project.project.id end end