Search code examples
rubyruby-on-rails-4modelrails-migrations

Ruby on Rails Activerecord - ids are Exchanged in Model


I use Ruby in Rails 4.2.0 with Ruby 2.1.0 and I have a self join association. Here is my migration:

class CreateStateTemplates < ActiveRecord::Migration
  def change
    create_table :state_templates do |t|

      t.string :name

      t.references :next
      t.references :prev

      t.timestamps null: false
    end
  end
end

And here is my model:

class StateTemplate < ActiveRecord::Base

  has_one :prev, :class_name => "StateTemplate", :foreign_key => "prev_id"
  belongs_to :state_template, :class_name => "StateTemplate", :foreign_key => "prev_id"
  has_one :next, :class_name => "StateTemplate", :foreign_key => "next_id"
  belongs_to :state_template, :class_name => "StateTemplate", :foreign_key => "next_id"

end

Here I fill some states:

StateTemplate.find(1).update(:next => StateTemplate.find(2))
StateTemplate.find(2).update(:next => StateTemplate.find(3), :prev => StateTemplate.find(1))
StateTemplate.find(3).update(:next => StateTemplate.find(4), :prev => StateTemplate.find(2))
StateTemplate.find(4).update(:next => StateTemplate.find(5), :prev => StateTemplate.find(3))
StateTemplate.find(5).update(:prev => StateTemplate.find(4))

But something strange has been happened. When I take one record from StateTemplate and put it on a concole, I've got this:

2.1.0 :007 > StateTemplate.find(2)
  StateTemplate Load (0.1ms)  SELECT  "state_templates".* FROM "state_templates" WHERE "state_templates"."id" = ? LIMIT 1  [["id", 2]]
 => #<StateTemplate id: 2, name: "two", next_id: 1, prev_id: 3, created_at: "2017-07-22 11:51:30", updated_at: "2017-07-22 11:51:30"> 

Which doesn't right, because next_id must be 3, but when I refer to the next state, I've got this:

2.1.0 :008 > StateTemplate.find(2).next
  StateTemplate Load (0.1ms)  SELECT  "state_templates".* FROM "state_templates" WHERE "state_templates"."id" = ? LIMIT 1  [["id", 2]]
  StateTemplate Load (0.0ms)  SELECT  "state_templates".* FROM "state_templates" WHERE "state_templates"."next_id" = ? LIMIT 1  [["next_id", 2]]
 => #<StateTemplate id: 3, name: "three", next_id: 2, prev_id: 4, created_at: "2017-07-22 11:51:30", updated_at: "2017-07-22 11:51:30"> 

Which is right. And here is my question: why does ids in record is exchanged, but refered to a right record?


Solution

  • Both belongs_to associations have the same name (:state_template), but they need not have different names. Furthermore I think it is a bit confusing to have prev and next pointing to self instead of pointing to the linked templates.

    I would change the associations like this:

    belongs_to :next, class_name: 'StateTemplate'
    belongs_to :prev, class_name: 'StateTemplate'
    
    has_one :predecessor, foreign_key: 'next_id', class_name: 'StateTemplate'
    has_one :successor,   foreign_key: 'prev_id', class_name: 'StateTemplate'
    

    And this can even be simplified. Because if a template has a belongs_to :next than the reverse has_one is automatically the prev. Therefore remove the prev_id from that model and just use the following:

    belongs_to :next, class_name: 'StateTemplate'
    has_one :prev, foreign_key: 'next_id', class_name: 'StateTemplate'