Active Record Relations and Nesting

Hello,   I am looking to create a rails app that will model and display related nodes. I am just beginning to dive into active record and I was wondering if the following pesudo code would work?

class NodeLink < ActiveRecord::Base   belongs_to :node # foreign key - node_id (recorded as primary_node_id)   belongs_to :node # foreign key - node_id (recorded as secondary_node_id) end

class Node < ActiveRecord::Base   has_many :nodes, :through => :node_links end

In essence this would create something that looks like the following.       [Node One]       > >       > > [Node Two] [Node Three]

[Node Four]

Where the node links are represented by the lines and the nodes themselves are enclosed in brackets. And this pattern could be continued indefinite through different variations of nodes and links.

Any help or advice you could give would be great. Thanks, Devin Morin

Hello, I am looking to create a rails app that will model and display related nodes. I am just beginning to dive into active record and I was wondering if the following pesudo code would work?

class NodeLink < ActiveRecord::Base belongs_to :node # foreign key - node_id (recorded as primary_node_id) belongs_to :node # foreign key - node_id (recorded as secondary_node_id) end

You'll need to specify different names for these two associations; a working declaration might look like:

class NodeLink < ActiveRecord::Base   belongs_to :primary_node, :class_name => 'Node'   belongs_to :secondary_node, :class_name => 'Node' end

class Node < ActiveRecord::Base has_many :nodes, :through => :node_links end

This gets messier:

class Node < ActiveRecord::Base   has_many :primary_links, :foreign_key => 'primary_node_id', :class_name => 'NodeLink'   has_many :secondary_links, :foreign_key => 'secondary_node_id', :class_name => 'NodeLink'   has_many :primary_nodes, :through => :primary_links, :source => :secondary_node   has_many :secondary_nodes, :through => :secondary_links, :source => :primary_node end

This offers some additional detail:

Note that it's going to be fairly complicated to deal with "all nodes that are connected to this one" unless you denormalize somewhat and make *two* NodeLinks for each edge. You may want to consider if you *really* need a completely undirected graph, or if there's additional structure (tree-like behavior, for instance) that you can simplify things with.

If this is a central concern to your app, you may want to look into the specialized "graph databases" that are now available.

--Matt Jones

Note that it's going to be fairly complicated to deal with "all nodes that are connected to this one" unless you denormalize somewhat and make *two* NodeLinks for each edge. You may want to consider if you *really* need a completely undirected graph, or if there's additional structure (tree-like behavior, for instance) that you can simplify things with.

If this is a central concern to your app, you may want to look into the specialized "graph databases" that are now available.

--Matt Jones

This will be a central concern of my app, is there any resources I can use to study undirected graphs? And I feel that the denormalization would be an accepted way of dealing with the issue. From what I understand this would entail having a table with

Whoops, message got cut off. From what I can guess this denormalization just basically means you store the relations between the nodes in a table to save on compute time when performing a lookup on the Db? Regards, Devin Morin

Devin:

Unsolicited, but here's a pair of classes I've used for representing "vertices" (nodes) and "edges" (node links). You could conceptually "Vertex".gsub("NodeLink") and "Edge".gsub("Node") and get 80% of where you want to get. The useful bits:

- the belongs_to clauses in Edge implement the kind of thing you're describing - the before_destroy clause in Vertex takes the place of dependent => destroy - the edges method in Vertex finds both "left" and "right" links.

Also, this is a self-contained sample useful for testing, since it includes the up() and down() methods to create and destroy the tables while you're trying things out. I use this pattern a lot.

The example does NOT check for circular dependencies and such (since edges and vertices may describe circular graphs), but you might find it helpful.

class Edge < ActiveRecord::Base   belongs_to :vertex_a, :class_name => 'Vertex', :foreign_key => 'vertex_a_id'   belongs_to :vertex_b, :class_name => 'Vertex', :foreign_key => 'vertex_b_id'

  def self.up()     ActiveRecord::Schema.define do       create_table "edges", :force => true do |t|         t.integer "vertex_a_id"         t.integer "vertex_b_id"       end     end   end

  def self.down()     ActiveRecord::Schema.define do       drop_table :edges     end   end

end

class Vertex < ActiveRecord::Base   before_destroy do |record|     Edge.destroy_all "vertex_a_id = #{record.id}"     Edge.destroy_all "vertex_b_id = #{record.id}"   end

  # has_many :vertices will not work in this case, so here's   # how we find all edges asociated with this vertex...   def edges     Edge.find_all_by_vertex_a_id(self.id) + Edge.find_all_by_vertex_b_id(self.id)   end

  has_many :vertices

  def self.up()     ActiveRecord::Schema.define do       create_table "vertices", :force => true do |t|         t.float "x"         t.float "y"         t.float "z"       end     end   end

  def self.down()     ActiveRecord::Schema.define do       drop_table :vertices     end   end

end

# Testing...

def setup   Edge.up   Vertex.up   v0 = Vertex.create(:x => 0.0, :y => 0.0, :z => 0.0)   v1 = Vertex.create(:x => 1.0, :y => 1.0, :z => 1.0)   e0 = Edge.create(:vertex_a => v0, :vertex_b => v1)   [v0, v1, e0] end

def teardown   Edge.down   Vertex.down end