views:

648

answers:

3

I have a Character model and a Link model. The Link model represents a link from a character to another character. A link has a textual 'description' attribute. A link from character A to character B is distinct from the opposite link from B to A. A character has zero or one link to another character. A character may have various links to different characters. A character may be linked to by various different characters.

I used used Active Record relationships to partly implement relationships between Character and Link models:

class Character 
 has_many :links # the links from the character to other characters

class Link 
 belongs_to :character # the character from which starts the link to another character

which give me useful methods like character.links (array of all links starting from this character) or link.character (character from which starts the link)

The link model has also a to_character_id which contains the id of the character to who goes the link. Thus a link from character A to character B is an instance with the following attributes:

  • character_id = id of character A
  • to_character_id = id of character B
  • description = some text

I've written various extra methods like character.links_to (returning an array of all links which points to the character) or link.to_character (returning the character to which points the link), or character.characters_who_link_to (returning an array of the other characters having a link to this character). I've written also a callback to make sure that when a character is deleted, all links which go to this character are deleted (same for restoration).

Is possible to use additional AR relationships declarations which would provide me with this kind of extra methods, so that I do not have to write myself those methods and callbacks?

Agile Web Development with Rails presents a solution in the section "Using Models as Join Tables" but for a join table joining two different tables. In my case my join table Links join records of a single table, Characters.

+2  A: 

I think you want something like the following:

 class Character < ActiveRecord::Base
   has_many :outbound_links, :class_name => "Link", :foreign_key => "from_character_id"
   has_many :inbound_links, :class_name => "Link", :foreign_key => "to_character_id"
 end

 class Link < ActiveRecord::Base
   belongs_to :from_character, :class_name => "Character", :foreign_key => "from_character_id"
   belongs_to :to_character, :class_name => "Character", :foreign_key => "to_character_id"
 end

You can read about all your options on ActiveRecord associations at http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

Matt Gillooly
A: 

I think what you are really going for here is a self referential HABTM relationship using a join table

so if you had a join table

create_table :character_links do |t|
   t.integer :character_id
   t.integer :linked_character_id
   t.timestamps #if you want to know when the relationship was created
end

Then you would have

class Characters < ActiveRecord::Base
   has_and_belongs_to_many :linked_characters, 
      :class_name => "Characters", 
      :join_table => :character_links, 
      :foreign_key => "character_id", 
      :associated_foreign_key => "linked_character_id"

if you needed outgoing links and incoming links then you could just do

class Characters < ActiveRecord::Base
   has_and_belongs_to_many :outgoing_links, 
      :class_name => "Characters", 
      :join_table => :character_links, 
      :foreign_key => "character_id", 
      :associated_foreign_key => "linked_character_id"

   has_and_belongs_to_many :incoming_links, 
      :class_name => "Characters", 
      :join_table => :character_links, 
      :foreign_key => "linked_character_id", 
      :associated_foreign_key => "character_id"

Just switched the foreign_key and associated_foreign_key

This eliminates the need for you to have a seperate Links model

This is air code(not tested)

ErsatzRyan
+2  A: 

has_and_belongs_to_many is not really in use anymore; I'd use has_many :through instead.

class Character < ActiveRecord::Base
   has_many :links, :dependent => destroy
   has_many :characters, :through => :links 

   has_many :source_links, :class_name => "Link", 
     :foreign_key => "to_character_id", :dependent => :destroy
   has_many :source_characters, :class_name => "Character", 
     :through => :destination_links
 end

 class Link < ActiveRecord::Base
   belongs_to :character
   belongs_to :source_character, :class_name => "Character", 
    :foreign_key => "to_character_id"
 end

Note the :dependent => :destroy options - these will delete the links when the character is deleted. The naming is right - from the character's point of view, source_links are links to that character. So now you can do:

@character.characters # characters I link to
@character.links # links I have to other characters
@character.source_characters # characters that link to me
@character.source_links # links other characters have to me
Sarah Mei