Search code examples
ruby-on-railsrubyruby-on-rails-4activerecordself-reference

Rails 4 has_one self reference column


I have a Totem model and Totems table.

There will be many totems and I need to store the order of the totems in the database table. I added a previous_totem_id and next_totem_id to the Totems table to store the order information. I did it via this Rails Migration:

class AddPreviousNextTotemColumnsToTotems < ActiveRecord::Migration
  def change
    add_column :totems, :previous_totem_id, :integer
    add_column :totems, :next_totem_id, :integer
  end
end

Now in the Model I have defined the relationships:

class Totem < ActiveRecord::Base
  validates :name, :presence => true

  has_one :previous_totem, :class_name => 'Totem'
  has_one :next_totem, :class_name => 'Totem'
end

I created a couple of these totems through ActiveRecord and tried to use the previous_totem_id column like so:

totem = Totem.create! name: 'a1'
Totem.create! name: '1a'
totem.previous_totem_id = Totem.find_by(name: '1a').id
puts totem.previous_totem #This is NIL

However, the previous_totem comes back as nil, and I do not see a select statement in the mysql log when calling this line

totem.previous_totem

Is this relationship recommended? What is the best way to implement a self referencing column?


Solution

  • Changing direction of the association from has_one to belongs_to and specifying foreign keys, should make your code work as you expected:

    class Totem < ActiveRecord::Base
      validates :name, :presence => true
    
      belongs_to :previous_totem, :class_name => 'Totem', foreign_key: :previous_totem_id
      belongs_to :next_totem, :class_name => 'Totem', foreign_key: :next_totem_id
    end
    

    However, good association should be properly named and declared on both sides - with matching has_one association; in this case it's impossible without naming conflicts :) Self join might be sometimes useful, but i'm not sure if it's the best solution here. I didn't use the gem moveson recommends, but an integer column to store position is something I use and IMHO makes reordering records easier :)