Rails - Parent/child relationships
I'm currently using a standard one-to-one relationship to handle parent/child relationships:
class Category < ActiveRecord::Base
has_one :category
belong开发者_StackOverflows_to :category
end
Is there a recommended way to do it or is this ok?
You will need to tweak the names you are using to get this working - you specify the name of the relationship, and then tell AR what the class is:
class Category < ActiveRecord::Base
has_one :child, :class_name => "Category"
belongs_to :parent, :class_name => "Category"
end
I found that I had to make a minor change to @equivalent8's solution to make it work for Rails 5 (5.1.4):
class Category < ActiveRecord::Base
has_many :children, :class_name => "Category", foreign_key: 'parent_id'
belongs_to :parent, :class_name => "Category", foreign_key: 'parent_id', :optional => true
end
Without the foreign_key
declaration, Rails tries to find the children by organization_id instead of parent_id and chokes.
Rails also chokes without the :optional => true
declaration on the belongs_to association since belongs_to requires an instance to be assigned by default in Rails 5. In this case, you would have to assign an infinite number of parents.
has_many version:
class Category < ActiveRecord::Base
has_many :children, :class_name => "Category"
belongs_to :parent, :class_name => "Category"
end
#migratio
class CreateCategories < ActiveRecord::Migration
def change
create_table :categories do |t|
t.integer :parent_id
t.string :title
t.timestamps null: false
end
end
end
# RSpec test
require 'rails_helper'
RSpec.describe Category do
describe '#parent & #children' do
it 'should be able to do parent tree' do
c1 = Category.new.save!
c2 = Category.new(parent: c1).save!
expect(c1.children).to include(c2)
expect(c2.parent).to eq c1
end
end
end
I think , should be foreign_key
class Product < ActiveRecord::Base
has_many :children, :foreign_key => "parent_id" , :class_name => "Product"
belongs_to :parent, :class_name => "Product"
end
Console :
2.4.3 :004 > product = Product.find_by_name "BOB"
=> #<Product id: 1, name: "BOB", parent_id: nil>
2.4.3 :005 > product.children
D, [2019-11-07T14:56:14.273606 #66632] DEBUG -- :
Product Load (0.7ms) SELECT "products".* FROM "products" WHERE "products"."parent_id" = $1 [["parent_id", 1]]
=> #<ActiveRecord::Associations::CollectionProxy
[#<Product id: 7, parent_id: 1>,
#<Product id: 8, parent_id: 1>,
#<Product id: 9, parent_id: 1>]>
If you already have a model Category
and a table categories
(in schema.rb) you could do this to the model:
class Category < ApplicationRecord
has_many :children, class_name: 'Category', foreign_key: :parent_id
belongs_to :parent, class_name: 'Category', optional: true
end
and this as a migration:
class AddParentIdToCategories < ActiveRecord::Migration[6.0]
def change
add_column :categories, :parent_id, :integer
end
end
NOTE: In newer versions of Ruby you don't need the rocket hashes =>
Since the relation is symmetric, I actually find that different than what Toby wrote, that I prefer the following:
class Category < ActiveRecord::Base
has_one :parent, :class_name => "Category"
belongs_to :children, :class_name => "Category"
end
For some reason "has one parent, many children" is the way my mind things, not "has many parents, only one child"
精彩评论