Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class CreateActsAsDagTables < ActiveRecord::Migration
end
```

If you have a multi-database setup, make sure to create the tables in the same database where your DAG ActiveRecord models are located.

### Usage

```ruby
Expand Down
55 changes: 26 additions & 29 deletions lib/acts_as_dag/acts_as_dag.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,37 @@ def acts_as_dag(options = {})

# Create Link and Descendant Classes
class_eval <<-RUBY
class ::#{options[:link_class]} < ActsAsDAG::AbstractLink
self.table_name = '#{options[:link_table]}'
belongs_to :parent, :class_name => '#{self.name}', :foreign_key => :parent_id, :inverse_of => :child_links, :optional => true
belongs_to :child, :class_name => '#{self.name}', :foreign_key => :child_id, :inverse_of => :parent_links, :optional => true
parent_class = ancestors.detect {it.respond_to?(:abstract_class) && it.abstract_class } || ActiveRecord::Base

after_save Proc.new {|link| HelperMethods.update_transitive_closure_for_new_link(link) }
after_destroy Proc.new {|link| HelperMethods.update_transitive_closure_for_destroyed_link(link) }
unless Object.const_defined?(options[:link_class])
class ::#{options[:link_class]} < parent_class
self.table_name = '#{options[:link_table]}'
belongs_to :parent, :class_name => '#{self.name}', :foreign_key => :parent_id, :inverse_of => :child_links, :optional => true
belongs_to :child, :class_name => '#{self.name}', :foreign_key => :child_id, :inverse_of => :parent_links, :optional => true

def node_class; #{self.name} end
after_save Proc.new {|link| HelperMethods.update_transitive_closure_for_new_link(link) }
after_destroy Proc.new {|link| HelperMethods.update_transitive_closure_for_destroyed_link(link) }

validate :not_self_referential

def not_self_referential
errors.add(:base, "Self referential links #{self.class} cannot be created.") if parent_id && parent_id == child_id
end

def node_class; #{self.name} end
end
end

class ::#{options[:descendant_class]} < ActsAsDAG::AbstractDescendant
self.table_name = '#{options[:descendant_table]}'
belongs_to :ancestor, :class_name => '#{self.name}', :foreign_key => :ancestor_id, :optional => true
belongs_to :descendant, :class_name => '#{self.name}', :foreign_key => :descendant_id, :optional => true
unless Object.const_defined?(options[:descendant_class])
class ::#{options[:descendant_class]} < parent_class
self.table_name = '#{options[:descendant_table]}'
belongs_to :ancestor, :class_name => '#{self.name}', :foreign_key => :ancestor_id, :optional => true
belongs_to :descendant, :class_name => '#{self.name}', :foreign_key => :descendant_id, :optional => true

def node_class; #{self.name} end
validates_presence_of :ancestor_id, :descendant_id

def node_class; #{self.name} end
end
end

def self.link_class
Expand Down Expand Up @@ -402,21 +416,4 @@ def self.rebuild_subtree_links(current, path = [])
end
end
end

# CLASSES (for providing hooks)
class AbstractLink < ActiveRecord::Base
self.abstract_class = true

validate :not_self_referential

def not_self_referential
errors.add(:base, "Self referential links #{self.class} cannot be created.") if parent_id && parent_id == child_id
end
end

class AbstractDescendant < ActiveRecord::Base
self.abstract_class = true

validates_presence_of :ancestor_id, :descendant_id
end
end
8 changes: 8 additions & 0 deletions spec/acts_as_dag_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1027,4 +1027,12 @@ def with_options(klass, options)
expect(record.subtree_links.first.category_type).to eq(klass.name)
end
end

if ActiveRecord::VERSION::MAJOR >= 6
describe "models connected to non-primary DBs" do
let(:klass) { AnotherDBModel }

it_should_behave_like "DAG Model"
end
end
end
62 changes: 61 additions & 1 deletion spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,35 @@

puts ActiveRecord.version

ENV['RAILS_ENV'] ||= 'test'

ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Base.logger.level = Logger::WARN
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")

# ActiveRecord 6.0 and above supports multiple database connections
if ActiveRecord::VERSION::MAJOR >= 6
ActiveRecord::Base.configurations = {
"test" => {
"primary" => {
"adapter" => "sqlite3",
"database" => "file:primary?mode=memory"
},
"other_database" => {
"adapter" => "sqlite3",
"database" => "file:other_db_memory?mode=memory"
}
}
}
else
ActiveRecord::Base.configurations = {
"test" => {
"adapter" => "sqlite3",
"database" => "file:primary?mode=memory"
}
}
end

ActiveRecord::Base.establish_connection

ActiveRecord::Schema.define(:version => 0) do

Expand Down Expand Up @@ -58,3 +84,37 @@ class SeparateLinkModel < ActiveRecord::Base
class UnifiedLinkModel < ActiveRecord::Base
acts_as_dag
end

if ActiveRecord::VERSION::MAJOR >= 6
class AnotherDBAbstractClass < ActiveRecord::Base
self.abstract_class = true

connects_to database: { writing: :other_database }
end

schema_class = ActiveRecord::Schema[ActiveRecord::Migration.current_version]
schema = schema_class.new
schema.instance_variable_set(:@connection, AnotherDBAbstractClass.connection)
schema.define(version: 0) do
create_table :another_db_models, force: true do |t|
t.string :name
end

create_table :acts_as_dag_links, :force => true do |t|
t.integer :parent_id
t.integer :child_id
t.string :category_type
end

create_table :acts_as_dag_descendants, :force => true do |t|
t.integer :ancestor_id
t.integer :descendant_id
t.integer :distance
t.string :category_type
end
end

class AnotherDBModel < AnotherDBAbstractClass
acts_as_dag
end
end