Существует определенный класс задач, когда необходимо реализовать отношение "многие-ко-многим". Довольно интересным являеться случай когда узел может иметь больше одного родителя. Проще говоря, математический граф построенный на одной модели когда его элементы пренадлежат одной сущности.
Ruby CMF Web
\ | /
\_|_/
Rails
/ | \
/ | \
AR AV AC
Создадим миграцию для модели категорий
class CreateCategories < ActiveRecord::Migration
def self.up
create_table :categories do |t|
t.column :name, :string
t.timestamps
end
end
def self.down
drop_table :categories
end
end
Создадим миграцию для таблицы со связями
class Relations < ActiveRecord::Migration
def self.up
create_table :relations, :id => false do |t|
t.column :parent_id, :integer
t.column :child_id, :integer
end
end
def self.down
drop table :relations
end
end
Основной интерес составляет модель категорий и декларация связей has_and_belongs_to_many через вышеупомянутую таблицу.
class Category < ActiveRecord::Base
has_and_belongs_to_many :parents,
:join_table => 'relations',
:foreign_key => 'parent_id',
:association_foreign_key => 'child_id',
:class_name =>'Category'
has_and_belongs_to_many :children,
:join_table => 'relations',
:foreign_key => 'child_id',
:association_foreign_key => 'parent_id',
:name => 'Category'
end
Миграция заполняющая тестовые данные моделирующиее граф указанный на картинке
class TestDataGenerate < ActiveRecord::Migration
def self.up
c_ruby = Category.create :name => 'Ruby'
c_cmf = Category.create :name => 'CMF'
c_web = Category.create :name => 'WEB'
c_rails = Category.create :name => 'Rails'
c_ar = Category.create :name => 'AR'
c_av = Category.create :name => 'AV'
c_ac = Category.create :name => 'AC'
c_rails.parents << [c_ruby, c_cmf, c_web]
c_rails.children << [c_ar, c_av, c_ac]
end
def self.down
end
end
Переходим в консоль и проверяем правильность выполнения поставленной задачи. Находим категорию Rails и запрашиваем у нее имена(для краткости вывода) дочерних и родительских категорий.
>> rs = Category.find_by_name('Rails')
=> #
>> rs.children.find :all, :select => :name
=> [#, #, #]
>> rs.parents.find :all, :select => :name
=> [#, #, #]
Хочу отметить, что такое решение являеться независимым от синтаксиса SQL большенства реляционных БД отвечающим стандарту SQL92.
Другие варианты решения и нюансы описаны в статьях:
* http://szeryf.wordpress.com/2007/06/27/self-referential-many-to-many-relations-in-ruby-on-rails/
* http://wiki.rubyonrails.org/rails/pages/HowToCreateASelfReferentialManyToManyRelationship
Существует решение в виде плагина: http://tammersaleh.com/posts/acts_as_graph
Но как пишет его автор, плагин давно не тестировался.
P.S. acts_as_graph -реализует рекурсивный обход графа.