Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parent-child relationship in User model (self join)

In rails, I have this User model:

class User < ActiveRecord::Base
  enum role: [:adult, :child, :admin]
  after_initialize :set_default_role, :if => :new_record?

  # belongs_to :spouse, :foreign_key => :spouse_id, :class_name => 'User', :inverse_of => :spouse 

  def set_default_role
    self.role ||= :adult
  end

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable, :confirmable,
         :recoverable, :rememberable, :trackable, :validatable


  def marry(user)
    self.spouse = user
    user.spouse = self
  end
end

I added spouses through this migration:

class AddFieldsToUser < ActiveRecord::Migration
  def change
    # for marriages
    add_column :users, :spouse_id, :integer, index: true
  end
end

and that works decently (though my inverse of function never worked # belongs_to :spouse, :foreign_key => :spouse_id, :class_name => 'User', :inverse_of => :spouse ). I am now trying to do another self join with a "child-parent" relationship. A parent can have many children and a child can have (many / two) parents. This is the migration I came up with:

class CreateParentalRelationships < ActiveRecord::Migration
  def change
    create_table :parental_relationships do |t|
      t.references :parent, index: true
      t.references :child
    end
  end
end

and I added this to the model:

  has_many :children, :through => :parental_relationships, class_name: "User"
  has_many :parents, :through => :parental_relationships, class_name: "User"

but the relationship did not work with the following error:

[7] pry(main)> u = User.find(3)
  User Load (0.0ms)  SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 3]]
=> #<User id: 3, email: ...>

[8] pry(main)> u.children
ActiveRecord::HasManyThroughAssociationNotFoundError: Could not find the association :parental_relationships in model Us
er

[9] pry(main)> u.parent
NoMethodError: undefined method `parent' for #<User:0x5e52eb0>
from C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/activemodel-4.1.8/lib/active_model/attribute_methods.rb:435:in
 `method_missing'

[10] pry(main)> u.parents
ActiveRecord::HasManyThroughAssociationNotFoundError: Could not find the association :parental_relationships in model Us
er
from C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/activerecord-4.1.8/lib/active_record/reflection.rb:690:in `che
ck_validity!'

[11] pry(main)> u2.children
ActiveRecord::HasManyThroughAssociationNotFoundError: Could not find the association :parental_relationships in model Us
er
from C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/activerecord-4.1.8/lib/active_record/reflection.rb:690:in `che
ck_validity!'

What am I missing?

like image 711
Jeff Avatar asked Mar 16 '23 07:03

Jeff


1 Answers

Setting up the parent child relationship takes some finagling.

class ParentalRelationship < ActiveRecord::Base
  belongs_to :parent, class_name: 'User'
  belongs_to :child, class_name: 'User'
end

class User < ActiveRecord::Base
  has_many :parent_relationships,
          foreign_key: 'child_id',
          class_name: 'ParentalRelationship'

  has_many :child_relationships,
          foreign_key: 'parent_id',
          class_name: 'ParentalRelationship'

  has_many :parents,
          through: :parent_relationships,
          class_name: 'User'

  has_many :children,
          through: :child_relationships,
          class_name: 'User'

  has_and_belongs_to_many :marriages
  belongs_to :current_marriage, class_name: 'Marriage'

  def marry(spouse)
    marriage = Marriage.create(users: [self, spouse])
    marriage.users.each { |u| u.update(current_marriage: marriage ) }
    marriage
  end

  def spouse
    return nil unless current_marriage
    current_marriage
      .users.where.not('marriages_users.user_id' => id).first
  end

  def birth(child)
    child.parents << self
    child.parents << spouse if spouse
  end
end

Note that we need to setup the relation to ParentalRelationship twice since we need to tell rails which foreign key it should look at for each type of relation (user is parent or child).


Since in these modern times people can actually have be married several times we need a Marriage model and a join table for users_marriages.

class Marriage < ActiveRecord::Base
  has_and_belongs_to_many :users
  validates :users, length: { minimum: 2, maximum: 2 }
end

rails g migration CreateUsersMarriagesJoinTable users marriages

Example app with specs: https://github.com/maxcal/sandbox/tree/31614819

like image 59
max Avatar answered Mar 23 '23 16:03

max