Reputation: 1725
I'm trying to setup a self-referential association in Rails 5. I have a video model. A video may have a previous video (like in a tv series). Ideally it would act like this;
irb(main):001:0> first_video = Video.create(url: 'https://youtu.be/W0lhlPdo0mw')
irb(main):002:0> second_video = Video.create(url: 'https://youtu.be/gQhlw6F603o', previous_video: first_video)
irb(main):003:0> second_video.previous_video
=> #<Video id: 1, url: "https://youtu.be/W0lhlPdo0mw">
This is my current approach but it's failing with (PG::UndefinedColumn: ERROR: column videos.video_id does not exist)
so I'm having to pass around the id.
Model
class Video < ApplicationRecord
has_one :previous_video, class_name: 'Video'
end
Migration
class CreateVideo < ActiveRecord::Migration[5.2]
def change
create_table :videos do |t|
t.string :url, null: false
t.references :previous_video, class: 'Video'
t.timestamps
end
end
end
What is the best practice to achieve this in Rails 5? And why doesn't the above work as expected? Cheers Team!
Upvotes: 2
Views: 2695
Reputation: 163
t.references
is meant for belongs_to
associations on the model, and has_one
on the associated model. Below is how I would write the model and migration to accomplish what you want.
Update (2019-05-31):
Fixed the foreign key creation in the migration so it uses the correct to-table. Also added reference links for more information.
# model
class Video < ApplicationRecord
belongs_to(
# by default, the association name + "_id" will be used for the column name
:next_video,
optional: true,
class_name: 'Video', # will use the table of the specified model
inverse_of: :previous_video
)
has_one(
:previous_video,
class_name: 'Video', # will use the table of the specified model
foreign_key: 'next_video_id',
inverse_of: :next_video
)
end
# migration
class CreateVideo < ActiveRecord::Migration[5.2]
def change
create_table(:videos) do |t|
t.string(:url, null: false)
# This will create a `next_video_id` column. Add/remove any options as you
# see fit.
# - index: creates an index on the column
t.references(:next_video, index: true)
t.timestamps
end
# A foreign key could have been created when specifying the `references`
# column inside `create_table`, but it's self referential, and I'm not sure
# if it would work. So to be safe, the foreign key is created after the
# table is created.
add_foreign_key(:videos, :videos, column: :next_video_id)
end
end
t.references
)t.references
)Upvotes: 1
Reputation: 1758
You need to set the association like this:
has_one :prev_video, :class_name => 'Video', :foreign_key => 'previous_video'
Having set that you can call
@video.prev_video
Foreign key is the database column, the one i called prev_video can by named as you wish as long as you don't use the same name you gave to db column (previous_video).
Upvotes: 3