Rails 3: Model with multiple bi-directional relationships to itself
-
21-06-2021 - |
سؤال
I'm trying to build a text adventure game in Rails 3 (yes I know that's silly). Right now I have a model called Room. Each room needs to be associated with up to four other rooms. This relationship would be bi-directional, such that any two associated rooms would be exits for each other. So for example if I were to say:
@room1.north = @room2
@room2.south would automagically become @room1. Similarly, if I were to say:
@room1.east = nil
@room2.west also becomes nil. I would like to make this happen using only model associations, rather than doing it manually in the controller. Is this possible?
EDIT
The first example matzi gives doesn't quite work the way I want. Consider the following:
class Room < ActiveRecord::Base
attr_accessible :north, :south, :east, :west
has_one :north, :class_name => "Room", :foreign_key => "south_id"
has_one :east, :class_name => "Room", :foreign_key => "west_id"
belongs_to :south, :class_name => "Room", :foreign_key => "south_id"
belongs_to :west, :class_name => "Room", :foreign_key => "west_id"
end
@room1 = Room.new
@room2 = Room.new
@room1.save
@room2.save
This works fine:
@room1.north = @room2
@room1.north #Outputs @room2
@room2.south #Outputs @room1
@room1.north = nil
@room1.north #Outputs nil
@room2.south #Outputs nil
So far so good. But:
@room1.north = @room2
@room2.south = nil
@room1.north #Outputs @room2, but it should be nil
@room2.south #Outputs nil
Furthermore:
@room2.south = @room1
@room1.north #Outputs nil, but it should be @room2
@room2.south #Outputs @room1
See the problem here? This isn't truly bi-directional.
SOLVED
It turns out Matzi's first solution was correct after all. As he pointed out, the issue I was having with that solution was one of saving. The following works:
@room1 = Room.create
@room2 = Room.create
@room1.north = @room2
@room1.save
Room.find(1).north #Room 2
Room.find(2).south #Room 1
@room2.south = nil
@room2.save
Room.find(1).north #nil
Room.find(2).south #nil
المحلول
Of course it is possible. Select two direction (e.g north and east) which holds id (north_id and east_id), because there is no need for all four. Then set the relationship trough the :foreign_key
. Mind for the id's, as has_one marks the id name of the other side, while the belongs_to marks the id in the same object.
It should work:
has_one :south, :class_name => 'Room', :foreign_key => 'north_id'
belongs_to :north, :class_name => 'Room', :foreign_key => 'north_id'
However I would suggest you to use a connector model and a HABTM realtionship. This model can hold information about the directions, and can be much more flexible. E.g. now you can't create a room with two "North" entrance, nor can you define a one way connection between. With it you can even find specific connections and you can alter them as you like. If you ever want more complex structre like this simple grid, then consider this.