What you are describing is called a self-referential assocation or a self join.
To setup a self referential assocation you just create a nullable foreign key column that points to the same table:
class CreateObservations < ActiveRecord::Migration[6.0]
def change
add_reference :comments, :parent,
null: true,
foreign_key: { to_table: :comments }
end
end
And an assocation that points back to the same class:
class Comment < ApplicationRecord
belongs_to :parent,
class_name: 'Comment',
optional: true,
inverse_of: :children
has_many :children,
class_name: 'Comment',
foreign_key: 'parent_id',
inverse_of: :parent
end
If you don’t make the column nullable and the belongs_to
assocation optional you end up in a chicken vs egg scenario where you can’t actually insert the first record in the table as it has nothing to refer to.
If you want to create a record and children at the same you use accepts_nested_attributes
.
class Comment < ApplicationRecord
belongs_to :parent,
class_name: 'Comment',
optional: true,
inverse_of: :children
has_many :children,
class_name: 'Comment',
foreign_key: 'parent_id',
inverse_of: :parent
accepts_nested_attributes_for :children
end
This lets you create a reddit style thread of comments with:
Comment.create!(
text: "You suck!!!!!",
user_id: 99,
children_attributes: [
{
text: "Be respectful, or I'll block you.",
user_id: 1,
children_attributes: [
{
text: "Typical *******, doesn't belive in free speech.",
user_id: 99
}
]
}
]
])
As it automatically handles recursion. See the guides for how to to create forms for nested attributes and how to whitelist them in your controller. If the nested attributes contain an id the nested records will be updated instead of creating new records.
Dealing with the whole post_id issue can be done in different ways:
- Use a callback to set the post from the parent comment. This is my least favorite.
- Make the column nullable and get the parent by traversing up the tree.
- Use a polymorphic assocation instead so that a comment can belong to either a comment or a post. Get the original post by traversing up the tree.
CLICK HERE to find out more related problems solutions.