diff --git a/Gemfile b/Gemfile index fa75df1..b4e2a20 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,3 @@ -source 'https://rubygems.org' +source "https://rubygems.org" gemspec diff --git a/Rakefile b/Rakefile index b0db3ce..089546b 100644 --- a/Rakefile +++ b/Rakefile @@ -1,23 +1,22 @@ -require 'rake/testtask' -require 'rdoc/task' -require 'bundler/gem_tasks' +require "rake/testtask" +require "rdoc/task" +require "bundler/gem_tasks" -desc 'Default: run unit tests.' -task :default => :test +desc "Default: run unit tests." +task default: :test -desc 'Test the acts_as_dag plugin.' +desc "Test the acts_as_dag plugin." Rake::TestTask.new(:test) do |t| - t.libs << 'lib' - t.pattern = 'test/**/*_test.rb' + t.libs << "lib" + t.pattern = "test/**/*_test.rb" t.verbose = true end -desc 'Generate documentation for the acts_as_dag plugin.' +desc "Generate documentation for the acts_as_dag plugin." Rake::RDocTask.new(:rdoc) do |rdoc| - rdoc.rdoc_dir = 'rdoc' - rdoc.title = 'ActsAsDag' - rdoc.options << '--line-numbers' << '--inline-source' - rdoc.rdoc_files.include('README.rdoc') - rdoc.rdoc_files.include('lib/dag/dag.rb') + rdoc.rdoc_dir = "rdoc" + rdoc.title = "ActsAsDag" + rdoc.options << "--line-numbers" << "--inline-source" + rdoc.rdoc_files.include("README.rdoc") + rdoc.rdoc_files.include("lib/dag/dag.rb") end - diff --git a/acts-as-dag.gemspec b/acts-as-dag.gemspec index 2313c25..131c6f6 100644 --- a/acts-as-dag.gemspec +++ b/acts-as-dag.gemspec @@ -1,26 +1,25 @@ -# -*- encoding: utf-8 -*- -$:.push File.expand_path("../lib", __FILE__) +$LOAD_PATH.push File.expand_path("lib", __dir__) require "acts-as-dag/version" Gem::Specification.new do |s| s.name = "acts-as-dag" s.version = Acts::As::Dag::VERSION - s.authors = ['Matthew Leventi', 'Robert Schmitt'] + s.authors = ["Matthew Leventi", "Robert Schmitt"] s.email = ["resgraph@cox.net"] - s.homepage = 'https://github.com/resgraph/acts-as-dag' - s.summary = %q{Directed Acyclic Graph hierarchy for Rail's ActiveRecord} - s.description = %q{Directed Acyclic Graph hierarchy for Rail's ActiveRecord, supporting Rails 4.x} + s.homepage = "https://github.com/resgraph/acts-as-dag" + s.summary = "Directed Acyclic Graph hierarchy for Rail's ActiveRecord" + s.description = "Directed Acyclic Graph hierarchy for Rail's ActiveRecord, supporting Rails 4.x" s.rubyforge_project = "acts-as-dag" s.files = `git ls-files`.split("\n") - s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") - s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } + s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) } s.require_paths = ["lib"] # As specified in test/dag_test.rb - s.add_development_dependency 'rake' - s.add_development_dependency 'sqlite3' - s.add_runtime_dependency 'activemodel' - s.add_runtime_dependency 'activerecord', '>= 4.0.0' + s.add_development_dependency "rake" + s.add_development_dependency "sqlite3" + s.add_dependency "activemodel" + s.add_dependency "activerecord", ">= 4.0.0" + s.metadata["rubygems_mfa_required"] = "true" end diff --git a/lib/acts-as-dag.rb b/lib/acts-as-dag.rb index dd1a8c5..51c472b 100644 --- a/lib/acts-as-dag.rb +++ b/lib/acts-as-dag.rb @@ -13,6 +13,4 @@ $LOAD_PATH.shift -if defined?(ActiveRecord::Base) - ActiveRecord::Base.extend Dag -end +ActiveRecord::Base.extend Dag if defined?(ActiveRecord::Base) diff --git a/lib/acts-as-dag/version.rb b/lib/acts-as-dag/version.rb index c2f20ad..6f46fe9 100644 --- a/lib/acts-as-dag/version.rb +++ b/lib/acts-as-dag/version.rb @@ -1,7 +1,7 @@ module Acts module As module Dag - VERSION = "4.0.0" + VERSION = "5.0.0" end end end diff --git a/lib/dag/columns.rb b/lib/dag/columns.rb index 0927f6a..1ae24e0 100644 --- a/lib/dag/columns.rb +++ b/lib/dag/columns.rb @@ -1,26 +1,34 @@ module Dag - - #Methods that show columns + # Methods that show columns module Columns - def ancestor_id_column_name - acts_as_dag_options[:ancestor_id_column] - end + def ancestor_id_column_name + acts_as_dag_options[:ancestor_id_column] + end - def descendant_id_column_name - acts_as_dag_options[:descendant_id_column] - end + def descendant_id_column_name + acts_as_dag_options[:descendant_id_column] + end - def direct_column_name - acts_as_dag_options[:direct_column] - end + def scoped_record_id_column_name + acts_as_dag_options[:scoped_record_id_column] + end - def count_column_name - acts_as_dag_options[:count_column] - end + def direct_column_name + acts_as_dag_options[:direct_column] + end - def acts_as_dag_polymorphic? - acts_as_dag_options[:polymorphic] - end + def count_column_name + acts_as_dag_options[:count_column] end -end \ No newline at end of file + def acts_as_dag_polymorphic? + acts_as_dag_options[:polymorphic] + end + + def scoped_record_id + return unless scoped_record_id_column_name && respond_to?(scoped_record_id_column_name) + + public_send(scoped_record_id_column_name) + end + end +end diff --git a/lib/dag/dag.rb b/lib/dag/dag.rb index 0adfd43..f1f2c46 100644 --- a/lib/dag/dag.rb +++ b/lib/dag/dag.rb @@ -1,60 +1,60 @@ module Dag - - #Sets up a model to act as dag links for models specified under the :for option + # Sets up a model to act as dag links for models specified under the :for option def acts_as_dag_links(options = {}) - conf = { - :ancestor_id_column => 'ancestor_id', - :ancestor_type_column => 'ancestor_type', - :descendant_id_column => 'descendant_id', - :descendant_type_column => 'descendant_type', - :direct_column => 'direct', - :count_column => 'count', - :polymorphic => false, - :node_class_name => nil} - conf.update(options) - - unless conf[:polymorphic] - if conf[:node_class_name].nil? - raise ActiveRecord::ActiveRecordError, 'ERROR: Non-polymorphic graphs need to specify :node_class_name with the receiving class like belong_to' - end + options = { + ancestor_id_column: "ancestor_id", + ancestor_type_column: "ancestor_type", + descendant_id_column: "descendant_id", + descendant_type_column: "descendant_type", + direct_column: "direct", + count_column: "count", + polymorphic: false, + node_class_name: nil, + scoped_record_id_column: nil + }.merge(options) + + if options[:polymorphic].blank? && options[:node_class_name].blank? + raise ActiveRecord::ActiveRecordError, + "ERROR: Non-polymorphic graphs need to specify :node_class_name with the receiving class like belong_to" end - class_attribute :acts_as_dag_options, :instance_writer => false - self.acts_as_dag_options = conf + class_attribute :acts_as_dag_options, instance_writer: false + self.acts_as_dag_options = options extend Columns include Columns - #access to _changed? and _was for (edge,count) if not default - unless direct_column_name == 'direct' - module_eval <<-"end_eval", __FILE__, __LINE__ - def direct_changed? - self.#{direct_column_name}_changed? - end - def direct_was - self.#{direct_column_name}_was - end - end_eval + # access to _changed? and _was for (edge,count) if not default + unless direct_column_name == "direct" + module_eval <<-"END_EVAL", __FILE__, __LINE__ + 1 + def direct_changed? + self.#{direct_column_name}_changed? + end + + def direct_was + self.#{direct_column_name}_was + end + END_EVAL end - unless count_column_name == 'count' - module_eval <<-"end_eval", __FILE__, __LINE__ - def count_changed? - self.#{count_column_name}_changed? - end - def count_was - self.#{count_column_name}_was - end - end_eval + unless count_column_name == "count" + module_eval <<-"END_EVAL", __FILE__, __LINE__ + 1 + def count_changed? + self.#{count_column_name}_changed? + end + + def count_was + self.#{count_column_name}_was + end + END_EVAL end internal_columns = [ancestor_id_column_name, descendant_id_column_name] - edge_class_name = self.to_s direct_column_name.intern count_column_name.intern - #links to ancestor and descendant + # links to ancestor and descendant if acts_as_dag_polymorphic? extend PolyColumns include PolyColumns @@ -62,239 +62,279 @@ def count_was internal_columns << ancestor_type_column_name internal_columns << descendant_type_column_name - belongs_to :ancestor, :polymorphic => true - belongs_to :descendant, :polymorphic => true - - validates ancestor_type_column_name.to_sym, :presence => true - validates descendant_type_column_name.to_sym, :presence => true - validates ancestor_id_column_name.to_sym, :uniqueness => {:scope => [ancestor_type_column_name, descendant_type_column_name, descendant_id_column_name]} - - scope :with_ancestor, lambda { |ancestor| where(ancestor_id_column_name => ancestor.id, ancestor_type_column_name => ancestor.class.to_s) } - scope :with_descendant, lambda { |descendant| where(descendant_id_column_name => descendant.id, descendant_type_column_name => descendant.class.to_s) } - - scope :with_ancestor_point, lambda { |point| where(ancestor_id_column_name => point.id, ancestor_type_column_name => point.type) } - scope :with_descendant_point, lambda { |point| where(descendant_id_column_name => point.id, descendant_type_column_name => point.type) } + belongs_to :ancestor, polymorphic: true + belongs_to :descendant, polymorphic: true + + validates ancestor_type_column_name.to_sym, presence: true + validates descendant_type_column_name.to_sym, presence: true + uniqueness_scope = [ancestor_type_column_name, descendant_type_column_name, descendant_id_column_name] + uniqueness_scope << scoped_record_id_column_name.to_sym if scoped_record_id_column_name.present? + validates ancestor_id_column_name.to_sym, uniqueness: { scope: uniqueness_scope } + + scope :with_ancestor, lambda { |ancestor, scoped_record_id = nil| + scope = where(ancestor_id_column_name => ancestor.id, ancestor_type_column_name => ancestor.class.to_s) + scope = scope.where(scoped_record_id_column_name => scoped_record_id) if scoped_record_id_column_name.present? + scope + } + + scope :with_descendant, lambda { |descendant, scoped_record_id = nil| + scope = where(descendant_id_column_name => descendant.id, descendant_type_column_name => descendant.class.to_s) + scope = scope.where(scoped_record_id_column_name => scoped_record_id) if scoped_record_id_column_name.present? + scope + } + + scope :with_ancestor_point, lambda { |point, scoped_record_id = nil| + scope = where(ancestor_id_column_name => point.id, ancestor_type_column_name => point.type) + scope = scope.where(scoped_record_id_column_name => scoped_record_id) if scoped_record_id_column_name.present? + scope + } + + scope :with_descendant_point, lambda { |point, scoped_record_id = nil| + scope = where(descendant_id_column_name => point.id, descendant_type_column_name => point.type) + scope = scope.where(scoped_record_id_column_name => scoped_record_id) if scoped_record_id_column_name.present? + scope + } extend Polymorphic include Polymorphic else - belongs_to :ancestor, :foreign_key => ancestor_id_column_name, :class_name => acts_as_dag_options[:node_class_name] - belongs_to :descendant, :foreign_key => descendant_id_column_name, :class_name => acts_as_dag_options[:node_class_name] - - validates ancestor_id_column_name.to_sym, :uniqueness => {:scope => [descendant_id_column_name]} - - scope :with_ancestor, lambda { |ancestor| where(ancestor_id_column_name => ancestor.id) } - scope :with_descendant, lambda { |descendant| where(descendant_id_column_name => descendant.id) } - - scope :with_ancestor_point, lambda { |point| where(ancestor_id_column_name => point.id) } - scope :with_descendant_point, lambda { |point| where(descendant_id_column_name => point.id) } + belongs_to :ancestor, foreign_key: ancestor_id_column_name, class_name: acts_as_dag_options[:node_class_name] + belongs_to :descendant, foreign_key: descendant_id_column_name, class_name: acts_as_dag_options[:node_class_name] + + uniqueness_scope = [descendant_id_column_name] + uniqueness_scope << scoped_record_id_column_name.to_sym if scoped_record_id_column_name.present? + validates ancestor_id_column_name.to_sym, uniqueness: { scope: uniqueness_scope } + + scope :with_ancestor, lambda { |ancestor, scoped_record_id = nil| + scope = where(ancestor_id_column_name => ancestor.id) + scope = scope.where(scoped_record_id_column_name => scoped_record_id) if scoped_record_id_column_name.present? + scope + } + + scope :with_descendant, lambda { |descendant, scoped_record_id = nil| + scope = where(descendant_id_column_name => descendant.id) + scope = scope.where(scoped_record_id_column_name => scoped_record_id) if scoped_record_id_column_name.present? + scope + } + + scope :with_ancestor_point, lambda { |point, scoped_record_id = nil| + scope = where(ancestor_id_column_name => point.id) + scope = scope.where(scoped_record_id_column_name => scoped_record_id) if scoped_record_id_column_name.present? + scope + } + scope :with_descendant_point, lambda { |point, scoped_record_id = nil| + scope = where(descendant_id_column_name => point.id) + scope = scope.where(scoped_record_id_column_name => scoped_record_id) if scoped_record_id_column_name.present? + scope + } extend Standard include Standard end - scope :direct, lambda { where(:direct => true) } - scope :indirect, lambda { where(:direct => false) } + scope :direct, -> { where(direct: true) } + + scope :indirect, -> { where(direct: false) } - scope :ancestor_nodes, lambda { joins(:ancestor) } - scope :descendant_nodes, lambda { joins(:descendant) } + scope :ancestor_nodes, -> { joins(:ancestor) } - validates :ancestor, :presence => true - validates :descendant, :presence => true + scope :descendant_nodes, -> { joins(:descendant) } + + validates :ancestor, presence: true + validates :descendant, presence: true extend Edges include Edges before_destroy :destroyable!, :perpetuate before_save :perpetuate - before_validation :field_check, :fill_defaults, :on => :update - before_validation :fill_defaults, :on => :create + before_validation :field_check, :fill_defaults, on: :update + before_validation :fill_defaults, on: :create include ActiveModel::Validations - validates_with CreateCorrectnessValidator, :on => :create - validates_with UpdateCorrectnessValidator, :on => :update - + validates_with CreateCorrectnessValidator, on: :create + validates_with UpdateCorrectnessValidator, on: :update - #internal fields - code = 'def field_check ' + "\n" + # internal fields + code = ["def field_check \n"] internal_columns.each do |column| - code += "if " + column + "_changed? \n" + ' raise ActiveRecord::ActiveRecordError, "Column: '+column+' cannot be changed for an existing record it is immutable"' + "\n end \n" + code << "if #{column}_changed? \n raise ActiveRecord::ActiveRecordError, \"Column: #{column} cannot be changed for an existing record it is immutable\"\n end \n" end - code += 'end' - module_eval code + code << "end" + module_eval(code.join) [count_column_name].each do |column| - module_eval <<-"end_eval", __FILE__, __LINE__ - def #{column}=(x) - raise ActiveRecord::ActiveRecordError, "ERROR: Unauthorized assignment to #{column}: it's an internal field handled by acts_as_dag code." - end - end_eval + module_eval <<-"END_EVAL", __FILE__, __LINE__ + 1 + def #{column}=(x) + raise ActiveRecord::ActiveRecordError, + "ERROR: Unauthorized assignment to #{column}: it's an internal field handled by acts_as_dag code." + end + END_EVAL end end def has_dag_links(options = {}) - conf = { - :class_name => nil, - :prefix => '', - :ancestor_class_names => [], - :descendant_class_names => [] - } - conf.update(options) - - #check that class_name is filled - if conf[:link_class_name].nil? + options = { + class_name: nil, + prefix: "", + ancestor_class_names: [], + descendant_class_names: [], + scoped_record_id_column: nil + }.merge(options) + + # check that class_name is filled + if options[:link_class_name].nil? raise ActiveRecord::ActiveRecordError, "has_dag_links must be provided with :link_class_name option" end - #add trailing '_' to prefix - unless conf[:prefix] == '' - conf[:prefix] += '_' - end - - prefix = conf[:prefix] - dag_link_class_name = conf[:link_class_name] - dag_link_class = conf[:link_class_name].constantize + # add trailing '_' to prefix + options[:prefix] = "#{options[:prefix]}_" unless options[:prefix] == "" - if dag_link_class.acts_as_dag_polymorphic? - self.class_eval <<-EOL - has_many :#{prefix}links_as_ancestor, :as => :ancestor, :class_name => '#{dag_link_class_name}' - has_many :#{prefix}links_as_descendant, :as => :descendant, :class_name => '#{dag_link_class_name}' + prefix = options[:prefix] + dag_link_class_name = options[:link_class_name] + dag_link_class = options[:link_class_name].constantize + if options[:scoped_record_id_column].present? + scoped_record_condition = ",#{options[:scoped_record_id_column]}: record.#{options[:scoped_record_id_column]}" + lambda_scoped_record_condition = "lambda { |record| where(#{options[:scoped_record_id_column]}: + record.#{options[:scoped_record_id_column]}) },".squish - has_many :#{prefix}links_as_parent, lambda { where('#{dag_link_class.direct_column_name}' => true) }, :as => :ancestor, :class_name => '#{dag_link_class_name}' - has_many :#{prefix}links_as_child, lambda { where('#{dag_link_class.direct_column_name}' => true) }, :as => :descendant, :class_name => '#{dag_link_class_name}' + class_eval <<-EOL0, __FILE__, __LINE__ + 1 + attr_accessor :#{options[:scoped_record_id_column]} + EOL0 + else + class_eval <<-EOL0, __FILE__, __LINE__ + 1 + attr_accessor :scoped_record_id + EOL0 + end - EOL + if dag_link_class.acts_as_dag_polymorphic? + class_eval <<-EOL1, __FILE__, __LINE__ + 1 + has_many :#{prefix}links_as_ancestor,#{lambda_scoped_record_condition} as: :ancestor, class_name: "#{dag_link_class_name}" + has_many :#{prefix}links_as_descendant,#{lambda_scoped_record_condition} as: :descendant, class_name: "#{dag_link_class_name}" + has_many :#{prefix}links_as_parent, lambda { |record| where(#{dag_link_class.direct_column_name}: true#{scoped_record_condition}) }, as: :ancestor, class_name: "#{dag_link_class_name}" + has_many :#{prefix}links_as_child, lambda { |record| where(#{dag_link_class.direct_column_name}: true#{scoped_record_condition}) }, as: :descendant, class_name: "#{dag_link_class_name}" + EOL1 ancestor_table_names = [] parent_table_names = [] - conf[:ancestor_class_names].each do |class_name| + options[:ancestor_class_names].each do |class_name| table_name = class_name.tableize - self.class_eval <<-EOL2 - has_many :#{prefix}links_as_descendant_for_#{table_name}, lambda { where('#{dag_link_class.ancestor_type_column_name}' => '#{class_name}') }, :as => :descendant, :class_name => '#{dag_link_class_name}' - has_many :#{prefix}ancestor_#{table_name}, :through => :#{prefix}links_as_descendant_for_#{table_name}, :source => :ancestor, :source_type => '#{class_name}' - has_many :#{prefix}links_as_child_for_#{table_name}, lambda { where('#{dag_link_class.ancestor_type_column_name}' => '#{class_name}','#{dag_link_class.direct_column_name}' => true) }, :as => :descendant, :class_name => '#{dag_link_class_name}' - has_many :#{prefix}parent_#{table_name}, :through => :#{prefix}links_as_child_for_#{table_name}, :source => :ancestor, :source_type => '#{class_name}' - - def #{prefix}root_for_#{table_name}? - self.links_as_descendant_for_#{table_name}.empty? - end + class_eval <<-EOL2, __FILE__, __LINE__ + 1 + has_many :#{prefix}links_as_descendant_for_#{table_name}, lambda { |record| where(#{dag_link_class.ancestor_type_column_name}: "#{class_name}"#{scoped_record_condition}) }, as: :descendant, class_name: "#{dag_link_class_name}" + has_many :#{prefix}ancestor_#{table_name}, through: :#{prefix}links_as_descendant_for_#{table_name}, source: :ancestor, source_type: "#{class_name}" + has_many :#{prefix}links_as_child_for_#{table_name}, lambda { |record| where(#{dag_link_class.ancestor_type_column_name}: "#{class_name}", "#{dag_link_class.direct_column_name}": true#{scoped_record_condition}) }, as: :descendant, class_name: "#{dag_link_class_name}" + has_many :#{prefix}parent_#{table_name}, through: :#{prefix}links_as_child_for_#{table_name}, source: :ancestor, source_type: "#{class_name}" + + def #{prefix}root_for_#{table_name}? + self.links_as_descendant_for_#{table_name}.empty? + end EOL2 - ancestor_table_names << (prefix+'ancestor_'+table_name) - parent_table_names << (prefix+'parent_'+table_name) - unless conf[:descendant_class_names].include?(class_name) - #this apparently is only one way is we can create some aliases making things easier - self.class_eval "has_many :#{prefix}#{table_name}, :through => :#{prefix}links_as_descendant_for_#{table_name}, :source => :ancestor, :source_type => '#{class_name}'" - end + ancestor_table_names << ("#{prefix}ancestor_#{table_name}") + parent_table_names << ("#{prefix}parent_#{table_name}") + next if options[:descendant_class_names].include?(class_name) + + # this apparently is only one way is we can create some aliases making things easier + class_eval "has_many :#{prefix}#{table_name}, through: :#{prefix}links_as_descendant_for_#{table_name}, source: :ancestor, source_type: \"#{class_name}\"", + __FILE__, __LINE__ - 1 end - unless conf[:ancestor_class_names].empty? - self.class_eval <<-EOL25 - def #{prefix}ancestors - #{ancestor_table_names.join(' + ')} - end - def #{prefix}parents - #{parent_table_names.join(' + ')} - end - EOL25 - else - self.class_eval <<-EOL26 - def #{prefix}ancestors - a = [] - #{prefix}links_as_descendant.each do |link| - a << link.ancestor - end - a - end - def #{prefix}parents - a = [] - #{prefix}links_as_child.each do |link| - a << link.ancestor - end - a - end + if options[:ancestor_class_names].empty? + class_eval <<-EOL26, __FILE__, __LINE__ + 1 + def #{prefix}ancestors + #{prefix}links_as_descendant.map(&:ancestor) + end + + def #{prefix}parents + #{prefix}links_as_child.map(&:ancestor) + end EOL26 + else + class_eval <<-EOL25, __FILE__, __LINE__ + 1 + def #{prefix}ancestors + #{ancestor_table_names.join(' + ')} + end + + def #{prefix}parents + #{parent_table_names.join(' + ')} + end + EOL25 end descendant_table_names = [] child_table_names = [] - conf[:descendant_class_names].each do |class_name| + options[:descendant_class_names].each do |class_name| table_name = class_name.tableize - self.class_eval <<-EOL3 - has_many :#{prefix}links_as_ancestor_for_#{table_name}, lambda { where('#{dag_link_class.descendant_type_column_name}' => '#{class_name}') }, :as => :ancestor, :class_name => '#{dag_link_class_name}' - has_many :#{prefix}descendant_#{table_name}, :through => :#{prefix}links_as_ancestor_for_#{table_name}, :source => :descendant, :source_type => '#{class_name}' + class_eval <<-EOL3, __FILE__, __LINE__ + 1 + has_many :#{prefix}links_as_ancestor_for_#{table_name}, lambda { |record| where(#{dag_link_class.descendant_type_column_name}: "#{class_name}"#{scoped_record_condition}) }, as: :ancestor, class_name: "#{dag_link_class_name}" + has_many :#{prefix}descendant_#{table_name}, through: :#{prefix}links_as_ancestor_for_#{table_name}, source: :descendant, source_type: "#{class_name}" - has_many :#{prefix}links_as_parent_for_#{table_name}, lambda { where('#{dag_link_class.descendant_type_column_name}' => '#{class_name}','#{dag_link_class.direct_column_name}' => true) }, :as => :ancestor, :class_name => '#{dag_link_class_name}' - has_many :#{prefix}child_#{table_name}, :through => :#{prefix}links_as_parent_for_#{table_name}, :source => :descendant, :source_type => '#{class_name}' + has_many :#{prefix}links_as_parent_for_#{table_name}, lambda { |record| where(#{dag_link_class.descendant_type_column_name}: "#{class_name}", #{dag_link_class.direct_column_name}: true#{scoped_record_condition}) }, as: :ancestor, class_name: "#{dag_link_class_name}" + has_many :#{prefix}child_#{table_name}, through: :#{prefix}links_as_parent_for_#{table_name}, source: :descendant, source_type: "#{class_name}" - def #{prefix}leaf_for_#{table_name}? - self.links_as_ancestor_for_#{table_name}.empty? - end + def #{prefix}leaf_for_#{table_name}? + self.links_as_ancestor_for_#{table_name}.empty? + end EOL3 - descendant_table_names << (prefix+'descendant_'+table_name) - child_table_names << (prefix+'child_'+table_name) - unless conf[:ancestor_class_names].include?(class_name) - self.class_eval "has_many :#{prefix}#{table_name}, :through => :#{prefix}links_as_ancestor_for_#{table_name}, :source => :descendant, :source_type => '#{class_name}'" + descendant_table_names << ("#{prefix}descendant_#{table_name}") + child_table_names << ("#{prefix}child_#{table_name}") + unless options[:ancestor_class_names].include?(class_name) + class_eval "has_many :#{prefix}#{table_name}, through: :#{prefix}links_as_ancestor_for_#{table_name}, source: :descendant, source_type: \"#{class_name}\"", + __FILE__, __LINE__ - 1 end end - unless conf[:descendant_class_names].empty? - self.class_eval <<-EOL35 - def #{prefix}descendants - #{descendant_table_names.join(' + ')} - end - def #{prefix}children - #{child_table_names.join(' + ')} - end - EOL35 - else - self.class_eval <<-EOL36 - def #{prefix}descendants - d = [] - #{prefix}links_as_ancestor.each do |link| - d << link.descendant - end - d - end - def #{prefix}children - d = [] - #{prefix}links_as_parent.each do |link| - d << link.descendant - end - d - end + if options[:descendant_class_names].empty? + class_eval <<-EOL36, __FILE__, __LINE__ + 1 + def #{prefix}descendants + #{prefix}links_as_ancestor.map(&:descendant) + end + + def #{prefix}children + #{prefix}links_as_parent.map(&:descendant) + end EOL36 + else + class_eval <<-EOL35, __FILE__, __LINE__ + 1 + def #{prefix}descendants + #{descendant_table_names.join(' + ')} + end + + def #{prefix}children + #{child_table_names.join(' + ')} + end + EOL35 end else - self.class_eval <<-EOL4 - has_many :#{prefix}links_as_ancestor, :foreign_key => '#{dag_link_class.ancestor_id_column_name}', :class_name => '#{dag_link_class_name}' - has_many :#{prefix}links_as_descendant, :foreign_key => '#{dag_link_class.descendant_id_column_name}', :class_name => '#{dag_link_class_name}' - - has_many :#{prefix}ancestors, :through => :#{prefix}links_as_descendant, :source => :ancestor - has_many :#{prefix}descendants, :through => :#{prefix}links_as_ancestor, :source => :descendant + class_eval <<-EOL4, __FILE__, __LINE__ + 1 + has_many :#{prefix}links_as_ancestor,#{lambda_scoped_record_condition} foreign_key: "#{dag_link_class.ancestor_id_column_name}", class_name: "#{dag_link_class_name}" + has_many :#{prefix}links_as_descendant,#{lambda_scoped_record_condition} foreign_key: "#{dag_link_class.descendant_id_column_name}", class_name: "#{dag_link_class_name}" - has_many :#{prefix}links_as_parent, lambda { where('#{dag_link_class.direct_column_name}' => true) }, :foreign_key => '#{dag_link_class.ancestor_id_column_name}', :class_name => '#{dag_link_class_name}', :inverse_of => :ancestor - has_many :#{prefix}links_as_child, lambda { where('#{dag_link_class.direct_column_name}' => true) }, :foreign_key => '#{dag_link_class.descendant_id_column_name}', :class_name => '#{dag_link_class_name}', :inverse_of => :descendant + has_many :#{prefix}ancestors, through: :#{prefix}links_as_descendant, source: :ancestor + has_many :#{prefix}descendants, through: :#{prefix}links_as_ancestor, source: :descendant - has_many :#{prefix}parents, :through => :#{prefix}links_as_child, :source => :ancestor - has_many :#{prefix}children, :through => :#{prefix}links_as_parent, :source => :descendant + has_many :#{prefix}links_as_parent, lambda { |record| where(#{dag_link_class.direct_column_name}: true#{scoped_record_condition}) }, foreign_key: "#{dag_link_class.ancestor_id_column_name}", class_name: "#{dag_link_class_name}", inverse_of: :ancestor + has_many :#{prefix}links_as_child, lambda { |record| where(#{dag_link_class.direct_column_name}: true#{scoped_record_condition}) }, foreign_key: "#{dag_link_class.descendant_id_column_name}", class_name: "#{dag_link_class_name}", inverse_of: :descendant + has_many :#{prefix}parents, through: :#{prefix}links_as_child, source: :ancestor + has_many :#{prefix}children, through: :#{prefix}links_as_parent, source: :descendant EOL4 end - self.class_eval <<-EOL5 - def #{prefix}self_and_ancestors - [self] + #{prefix}ancestors - end - def #{prefix}self_and_descendants - [self] + #{prefix}descendants - end - - def #{prefix}leaf? - self.#{prefix}links_as_ancestor.empty? - end - def #{prefix}root? - self.#{prefix}links_as_descendant.empty? - end + class_eval <<-EOL5, __FILE__, __LINE__ + 1 + def #{prefix}self_and_ancestors + [self] + #{prefix}ancestors + end + + def #{prefix}self_and_descendants + [self] + #{prefix}descendants + end + + def #{prefix}leaf? + self.#{prefix}links_as_ancestor.empty? + end + + def #{prefix}root? + self.#{prefix}links_as_descendant.empty? + end EOL5 end - end diff --git a/lib/dag/edges.rb b/lib/dag/edges.rb index c423e3e..188ae4d 100644 --- a/lib/dag/edges.rb +++ b/lib/dag/edges.rb @@ -1,59 +1,69 @@ module Dag module Edges - def self.included(base) - base.send :include, EdgeInstanceMethods + base.send(:include, EdgeInstanceMethods) + end + + def ancestor_scoped_record_id(ancestor) + if ancestor.respond_to?(:scoped_record_id) + ancestor.scoped_record_id + elsif scoped_record_id_column_name + ancestor.public_send(scoped_record_id_column_name) + end end - #Class methods that extend the link model for both polymorphic and non-polymorphic graphs - #Returns a new edge between two points + # Class methods that extend the link model for both polymorphic and non-polymorphic graphs + # Returns a new edge between two points def build_edge(ancestor, descendant) - source = self::EndPoint.from(ancestor) - sink = self::EndPoint.from(descendant) - conditions = self.conditions_for(source, sink) - path = self.new(conditions) + scoped_record_id = ancestor_scoped_record_id(ancestor) + source = self::EndPoint.from(ancestor, scoped_record_id) + sink = self::EndPoint.from(descendant, scoped_record_id) + path = new(conditions_for(source, sink, scoped_record_id)) path.make_direct path end - #Finds an edge between two points, Must be direct + # Finds an edge between two points, Must be direct def find_edge(ancestor, descendant) - source = self::EndPoint.from(ancestor) - sink = self::EndPoint.from(descendant) - self.where(self.conditions_for(source, sink).merge!({direct_column_name => true})).first + scoped_record_id = ancestor_scoped_record_id(ancestor) + source = self::EndPoint.from(ancestor, scoped_record_id) + sink = self::EndPoint.from(descendant, scoped_record_id) + where(conditions_for(source, sink, scoped_record_id).merge!(direct_column_name => true)).first end - #Finds a link between two points + # Finds a link between two points def find_link(ancestor, descendant) - source = self::EndPoint.from(ancestor) - sink = self::EndPoint.from(descendant) - self.where(self.conditions_for(source, sink)).first + scoped_record_id = ancestor_scoped_record_id(ancestor) + source = self::EndPoint.from(ancestor, scoped_record_id) + sink = self::EndPoint.from(descendant, scoped_record_id) + where(conditions_for(source, sink, scoped_record_id)).first end - #Finds or builds an edge between two points + # Finds or builds an edge between two points def find_or_build_edge(ancestor, descendant) - edge = self.find_edge(ancestor, descendant) + edge = find_edge(ancestor, descendant) return edge unless edge.nil? - return build_edge(ancestor, descendant) + + build_edge(ancestor, descendant) end - #Creates an edge between two points using save + # Creates an edge between two points using save def create_edge(ancestor, descendant) - link = self.find_link(ancestor, descendant) + link = find_link(ancestor, descendant) if link.nil? - edge = self.build_edge(ancestor, descendant) - return edge.save + edge = build_edge(ancestor, descendant) + edge.save else link.make_direct - return link.save + link.save end end - #Creates an edge between two points using save! Returns created edge + # Creates an edge between two points using save! Returns created edge def create_edge!(ancestor, descendant) - link = self.find_link(ancestor, descendant) + link = find_link(ancestor, descendant) if link.nil? - edge = self.build_edge(ancestor, descendant) + edge = build_edge(ancestor, descendant) edge.save! edge else @@ -63,204 +73,197 @@ def create_edge!(ancestor, descendant) end end - #Finds the longest path between ancestor and descendant returning as an array - def longest_path_between(ancestor, descendant, path=[]) + # Finds the longest path between ancestor and descendant returning as an array + def longest_path_between(ancestor, descendant, path = []) longest = [] ancestor.children.each do |child| if child == descendant temp = path.clone temp << child - if temp.length > longest.length - longest = temp - end - elsif self.find_link(child, descendant) + longest = temp if temp.length > longest.length + elsif find_link(child, descendant) temp = path.clone temp << child - temp = self.longest_path_between(child, descendant, temp) - if temp.length > longest.length - longest = temp - end + temp = longest_path_between(child, descendant, temp) + longest = temp if temp.length > longest.length end end longest end - #Finds the shortest path between ancestor and descendant returning as an array - def shortest_path_between(ancestor, descendant, path=[]) + # Finds the shortest path between ancestor and descendant returning as an array + def shortest_path_between(ancestor, descendant, path = []) shortest = [] ancestor.children.each do |child| if child == descendant temp = path.clone temp << child - if shortest.blank? || temp.length < shortest.length - shortest = temp - end - elsif self.find_link(child, descendant) + shortest = temp if shortest.blank? || temp.length < shortest.length + elsif find_link(child, descendant) temp = path.clone temp << child - temp = self.shortest_path_between(child, descendant, temp) - if shortest.blank? || temp.length < shortest.length - shortest = temp - end + temp = shortest_path_between(child, descendant, temp) + shortest = temp if shortest.blank? || temp.length < shortest.length end end - return shortest + shortest end - #Determines if an edge exists between two points + # Determines if an edge exists between two points def edge?(ancestor, descendant) - !self.find_edge(ancestor, descendant).nil? + !find_edge(ancestor, descendant).nil? end - #Alias for edge + # Alias for edge def direct?(ancestor, descendant) - self.edge?(ancestor, descendant) + edge?(ancestor, descendant) end - #Instance methods included into the link model for polymorphic and non-polymorphic DAGs + # Instance methods included into the link model for polymorphic and non-polymorphic DAGs module EdgeInstanceMethods - attr_accessor :do_not_perpetuate - #Fill default direct and count values if necessary. In place of after_initialize method + # Fill default direct and count values if necessary. In place of after_initialize method def fill_defaults self[direct_column_name] = true if self[direct_column_name].nil? self[count_column_name] = 0 if self[count_column_name].nil? end - #Whether the edge can be destroyed + # Whether the edge can be destroyed def destroyable? - (self.count == 0) || (self.direct? && self.count == 1) + count.zero? || (direct? && count == 1) end - #Raises an exception if the edge is not destroyable. Otherwise makes the edge indirect before destruction to cleanup graph. + # Raises an exception if the edge is not destroyable. Otherwise makes the edge indirect before destruction to cleanup graph. def destroyable! - raise ActiveRecord::ActiveRecordError, 'ERROR: cannot destroy this edge' unless destroyable? - #this triggers rewiring on destruction via perpetuate - if self.direct? - self[direct_column_name] = false - end + raise ActiveRecord::ActiveRecordError, "ERROR: cannot destroy this edge" unless destroyable? + + # this triggers rewiring on destruction via perpetuate + self[direct_column_name] = false if direct? true end - #Analyzes the changes in a model instance and rewires as necessary. + # Analyzes the changes in a model instance and rewires as necessary. def perpetuate - #flag set by links that were modified in association - return true if self.do_not_perpetuate - - #if edge changed this was manually altered - if direct_changed? - if self.direct? - self[count_column_name] += 1 - else - self[count_column_name] -= 1 - end - self.wiring + # flag set by links that were modified in association + return true if do_not_perpetuate + + # if edge changed this was manually altered + return unless direct_changed? + + if direct? + self[count_column_name] += 1 + else + self[count_column_name] -= 1 end + wiring end - #Id of the ancestor + # Id of the ancestor def ancestor_id self[ancestor_id_column_name] end - #Id of the descendant + # Id of the descendant def descendant_id self[descendant_id_column_name] end - #Count of the edge, ie the edge exists in X ways + # Count of the edge, ie the edge exists in X ways def count self[count_column_name] end - #Changes the count of the edge. DO NOT CALL THIS OUTSIDE THE PLUGIN + # Changes the count of the edge. DO NOT CALL THIS OUTSIDE THE PLUGIN def internal_count=(val) self[count_column_name] = val end - #Whether the link is direct, ie manually created + # Whether the link is direct, ie manually created def direct? self[direct_column_name] end - #Whether the link is an edge? + # Whether the link is an edge? def edge? self[direct_column_name] end - #Makes the link direct, ie an edge + # Makes the link direct, ie an edge def make_direct self[direct_column_name] = true end - #Makes an edge indirect, ie a link. + # Makes an edge indirect, ie a link. def make_indirect self[direct_column_name] = false end - #Source of the edge, creates if necessary + # Source of the edge, creates if necessary def source @source = self.class::Source.from_edge(self) if @source.nil? @source end - #Sink (destination) of the edge, creates if necessary + # Sink (destination) of the edge, creates if necessary def sink @sink = self.class::Sink.from_edge(self) if @sink.nil? @sink end - #All links that end at the source + # All links that end at the source def links_to_source - self.class.with_descendant_point(self.source) + self.class.with_descendant_point(source, scoped_record_id) end - #all links that start from the sink + # all links that start from the sink def links_from_sink - self.class.with_ancestor_point(self.sink) + self.class.with_ancestor_point(sink, scoped_record_id) end protected # Changes on a wire based on the count (destroy or save!) (should not be called outside this plugin) def push_associated_modification!(edge) - raise ActiveRecord::ActiveRecordError, 'ERROR: cannot modify our self in this way' if edge == self + raise ActiveRecord::ActiveRecordError, "ERROR: cannot modify our self in this way" if edge == self + edge.do_not_perpetuate = true - if edge.count == 0 + if edge.count.zero? edge.destroy else edge.save! end end - #Updates the wiring of edges that dependent on the current one + # Updates the wiring of edges that dependent on the current one def rewire_crossing(above_leg, below_leg) if above_leg.count_changed? was = above_leg.count_was was = 0 if was.nil? above_leg_count = above_leg.count - was if below_leg.count_changed? - raise ActiveRecord::ActiveRecordError, 'ERROR: both legs cannot 0 normal count change' - else - below_leg_count = below_leg.count + raise ActiveRecord::ActiveRecordError, "ERROR: both legs cannot 0 normal count change" end + + below_leg_count = below_leg.count + else above_leg_count = above_leg.count - if below_leg.count_changed? - was = below_leg.count_was - was = 0 if was.nil? - below_leg_count = below_leg.count - was - else - raise ActiveRecord::ActiveRecordError, 'ERROR: both legs cannot have count changes' + unless below_leg.count_changed? + raise ActiveRecord::ActiveRecordError, "ERROR: both legs cannot have count changes" end + + was = below_leg.count_was + was = 0 if was.nil? + below_leg_count = below_leg.count - was + end count = above_leg_count * below_leg_count source = above_leg.source sink = below_leg.sink bridging_leg = self.class.find_link(source, sink) if bridging_leg.nil? - bridging_leg = self.class.new(self.class.conditions_for(source, sink)) + bridging_leg = self.class.new(self.class.conditions_for(source, sink, scoped_record_id)) bridging_leg.make_indirect bridging_leg.internal_count = 0 end @@ -268,41 +271,34 @@ def rewire_crossing(above_leg, below_leg) bridging_leg end - #Find the edges that need to be updated + # Find the edges that need to be updated def wiring source = self.source sink = self.sink - above_sources = [] - self.links_to_source.each do |edge| - above_sources << edge.source - end - below_sinks = [] - self.links_from_sink.each do |edge| - below_sinks << edge.sink - end + above_sources = links_to_source.map(&:source) + below_sinks = links_from_sink.map(&:sink) above_bridging_legs = [] - #everything above me tied to my sink + # everything above me tied to my sink above_sources.each do |above_source| above_leg = self.class.find_link(above_source, source) - above_bridging_leg = self.rewire_crossing(above_leg, self) + above_bridging_leg = rewire_crossing(above_leg, self) above_bridging_legs << above_bridging_leg unless above_bridging_leg.nil? end - #everything beneath me tied to my source + # everything beneath me tied to my source below_sinks.each do |below_sink| below_leg = self.class.find_link(sink, below_sink) - below_bridging_leg = self.rewire_crossing(self, below_leg) - self.push_associated_modification!(below_bridging_leg) + below_bridging_leg = rewire_crossing(self, below_leg) + push_associated_modification!(below_bridging_leg) above_bridging_legs.each do |above_bridging_leg| - long_leg = self.rewire_crossing(above_bridging_leg, below_leg) - self.push_associated_modification!(long_leg) + long_leg = rewire_crossing(above_bridging_leg, below_leg) + push_associated_modification!(long_leg) end end above_bridging_legs.each do |above_bridging_leg| - self.push_associated_modification!(above_bridging_leg) + push_associated_modification!(above_bridging_leg) end end end - end end diff --git a/lib/dag/poly_columns.rb b/lib/dag/poly_columns.rb index 1597e75..12b4fb9 100644 --- a/lib/dag/poly_columns.rb +++ b/lib/dag/poly_columns.rb @@ -1,5 +1,5 @@ module Dag - #Methods that show the columns for polymorphic DAGs + # Methods that show the columns for polymorphic DAGs module PolyColumns def ancestor_type_column_name acts_as_dag_options[:ancestor_type_column] @@ -9,4 +9,4 @@ def descendant_type_column_name acts_as_dag_options[:descendant_type_column] end end -end \ No newline at end of file +end diff --git a/lib/dag/polymorphic.rb b/lib/dag/polymorphic.rb index e100d51..6e8fadd 100644 --- a/lib/dag/polymorphic.rb +++ b/lib/dag/polymorphic.rb @@ -1,67 +1,74 @@ module Dag module Polymorphic - def self.included(base) base.send :include, PolyEdgeInstanceMethods end - #Contains nested classes in the link model for polymorphic DAGs - #Encapsulates the necessary information about a graph node + # Contains nested classes in the link model for polymorphic DAGs + # Encapsulates the necessary information about a graph node class EndPoint - #Does the endpoint match a model or another endpoint + # Does the endpoint match a model or another endpoint def matches?(other) - return (self.id == other.id) && (self.type == other.type) if other.is_a?(EndPoint) - (self.id == other.id) && (self.type == other.class.to_s) + if other.is_a?(EndPoint) + id == other.id && type == other.type && scoped_record_id == other.scoped_record_id + else + id == other.id && type == other.class.to_s && scoped_record_id == other.scoped_record_id + end end - #Factory Construction method that creates an EndPoint instance from a model - def self.from_resource(resource) - self.new(resource.id, resource.class.to_s) + # Factory Construction method that creates an EndPoint instance from a model + def self.from_resource(resource, scoped_record_id) + new(resource.id, resource.class.to_s, scoped_record_id) end - #Factory Construction method that creates an EndPoint instance from a model if necessary - def self.from(obj) - return obj if obj.kind_of?(EndPoint) - self.from_resource(obj) + # Factory Construction method that creates an EndPoint instance from a model if necessary + def self.from(obj, scoped_record_id) + obj.is_a?(EndPoint) ? obj : from_resource(obj, scoped_record_id) end - #Initializes the EndPoint instance with an id and type - def initialize(id, type) + # Initializes the EndPoint instance with an id and type + def initialize(id, type, scoped_record_id) @id = id @type = type + @scoped_record_id = scoped_record_id end - attr_reader :id, :type + attr_reader :id + attr_reader :type + attr_reader :scoped_record_id end - #Encapsulates information about the source of a link + # Encapsulates information about the source of a link class Source < EndPoint - #Factory Construction method that generates a source from a link + # Factory Construction method that generates a source from a link def self.from_edge(edge) - self.new(edge.ancestor_id, edge.ancestor_type) + scoped_record_id = edge.scoped_record_id_column_name ? edge.public_send(edge.scoped_record_id_column_name) : nil + new(edge.ancestor_id, edge.ancestor_type, scoped_record_id) end end - #Encapsulates information about the sink (destination) of a link + # Encapsulates information about the sink (destination) of a link class Sink < EndPoint - #Factory Construction method that generates a sink from a link + # Factory Construction method that generates a sink from a link def self.from_edge(edge) - self.new(edge.descendant_id, edge.descendant_type) + scoped_record_id = edge.scoped_record_id_column_name ? edge.public_send(edge.scoped_record_id_column_name) : nil + new(edge.descendant_id, edge.descendant_type, scoped_record_id) end end - #Contains class methods that extend the link model for polymorphic DAGs - #Builds a hash that describes a link from a source and a sink - def conditions_for(source, sink) + # Contains class methods that extend the link model for polymorphic DAGs + # Builds a hash that describes a link from a source and a sink + def conditions_for(source, sink, scoped_record_id = nil) { - ancestor_id_column_name => source.id, - ancestor_type_column_name => source.type, - descendant_id_column_name => sink.id, - descendant_type_column_name => sink.type - } + ancestor_id_column_name => source.id, + ancestor_type_column_name => source.type, + descendant_id_column_name => sink.id, + descendant_type_column_name => sink.type, + scoped_record_id_column_name => scoped_record_id + }.compact end - #Instance methods included into link model for a polymorphic DAG + # Instance methods included into link model for a polymorphic DAG module PolyEdgeInstanceMethods def ancestor_type self[ancestor_type_column_name] @@ -71,6 +78,5 @@ def descendant_type self[descendant_type_column_name] end end - end end diff --git a/lib/dag/standard.rb b/lib/dag/standard.rb index 259f5b8..b2dcce4 100644 --- a/lib/dag/standard.rb +++ b/lib/dag/standard.rb @@ -1,62 +1,65 @@ module Dag module Standard - def self.included(base) - base.send :include, NonPolyEdgeInstanceMethods + base.send(:include, NonPolyEdgeInstanceMethods) end - #Encapsulates the necessary information about a graph node + # Encapsulates the necessary information about a graph node class EndPoint - #Does an endpoint match another endpoint or model instance + # Does an endpoint match another endpoint or model instance def matches?(other) - self.id == other.id + id == other.id && scoped_record_id == other.scoped_record_id end - #Factory Construction method that creates an endpoint from a model - def self.from_resource(resource) - self.new(resource.id) + # Factory Construction method that creates an endpoint from a model + def self.from_resource(resource, scoped_record_id) + new(resource.id, scoped_record_id) end - #Factory Construction method that creates an endpoint from a model if necessary - def self.from(obj) - return obj if obj.kind_of?(EndPoint) - self.from_resource(obj) + # Factory Construction method that creates an endpoint from a model if necessary + def self.from(obj, scoped_record_id) + obj.is_a?(EndPoint) ? obj : from_resource(obj, scoped_record_id) end - #Initializes an endpoint based on an Id - def initialize(id) + # Initializes an endpoint based on an Id + def initialize(id, scoped_record_id) @id = id + @scoped_record_id = scoped_record_id end attr_reader :id + attr_reader :scoped_record_id end - #Encapsulates information about the source of a link + # Encapsulates information about the source of a link class Source < EndPoint - #Factory Construction method creates a source instance from a link + # Factory Construction method creates a source instance from a link def self.from_edge(edge) - self.new(edge.ancestor_id) + scoped_record_id = edge.scoped_record_id_column_name ? edge.public_send(edge.scoped_record_id_column_name) : nil + new(edge.ancestor_id, scoped_record_id) end end - #Encapsulates information about the sink of a link + + # Encapsulates information about the sink of a link class Sink < EndPoint - #Factory Construction method creates a sink instance from a link + # Factory Construction method creates a sink instance from a link def self.from_edge(edge) - self.new(edge.descendant_id) + scoped_record_id = edge.scoped_record_id_column_name ? edge.public_send(edge.scoped_record_id_column_name) : nil + new(edge.descendant_id, scoped_record_id) end end - #Builds a hash that describes a link from a source and a sink - def conditions_for(source, sink) + # Builds a hash that describes a link from a source and a sink + def conditions_for(source, sink, scoped_record_id = nil) { - ancestor_id_column_name => source.id, - descendant_id_column_name => sink.id - } + ancestor_id_column_name => source.id, + descendant_id_column_name => sink.id, + scoped_record_id_column_name => scoped_record_id + }.compact end - #Instance methods included into the link model for a non-polymorphic DAG + # Instance methods included into the link model for a non-polymorphic DAG module NonPolyEdgeInstanceMethods end - end -end \ No newline at end of file +end diff --git a/lib/dag/validators.rb b/lib/dag/validators.rb index 2b55b4e..4c0e7c2 100644 --- a/lib/dag/validators.rb +++ b/lib/dag/validators.rb @@ -1,47 +1,48 @@ module Dag - - #Validations on model instance creation. Ensures no duplicate links, no cycles, and correct count and direct attributes + # Validations on model instance creation. Ensures no duplicate links, no cycles, and correct count and direct attributes class CreateCorrectnessValidator < ActiveModel::Validator - def validate(record) - record.errors[:base] << 'Link already exists between these points' if has_duplicates(record) - record.errors[:base] << 'Link already exists in the opposite direction' if has_long_cycles(record) - record.errors[:base] << 'Link must start and end in different places' if has_short_cycles(record) - cnt = check_possible(record) - record.errors[:base] << 'Cannot create a direct link with a count other than 0' if cnt == 1 - record.errors[:base] << 'Cannot create an indirect link with a count less than 1' if cnt == 2 + record.errors.add(:base, "Link already exists between these points") if has_duplicates(record) + record.errors.add(:base, "Link already exists in the opposite direction") if has_long_cycles(record) + record.errors.add(:base, "Link must start and end in different places") if has_short_cycles(record) + count = check_possible(record) + record.errors.add(:base, "Cannot create a direct link with a count other than 0") if count == 1 + record.errors.add(:base, "Cannot create an indirect link with a count less than 1") if count == 2 end private - #check for duplicates + # check for duplicates def has_duplicates(record) record.class.find_link(record.source, record.sink) end - #check for long cycles + # check for long cycles def has_long_cycles(record) record.class.find_link(record.sink, record.source) end - #check for short cycles + # check for short cycles def has_short_cycles(record) record.sink.matches?(record.source) end - #check not impossible + # check not impossible def check_possible(record) - record.direct? ? (record.count != 0 ? 1 : 0) : (record.count < 1 ? 2 : 0) + if record.direct? + record.count.zero? ? 0 : 1 + else + (record.count < 1 ? 2 : 0) + end end end - #Validations on update. Makes sure that something changed, that not making a lonely link indirect, and count is correct. + # Validations on update. Makes sure that something changed, that not making a lonely link indirect, and count is correct. class UpdateCorrectnessValidator < ActiveModel::Validator - def validate(record) - record.errors[:base] << "No changes" unless record.changed? - record.errors[:base] << "Do not manually change the count value" if manual_change(record) - record.errors[:base] << "Cannot make a direct link with count 1 indirect" if direct_indirect(record) + record.errors.add(:base, "No changes") unless record.changed? + record.errors.add(:base, "Do not manually change the count value") if manual_change(record) + record.errors.add(:base, "Cannot make a direct link with count 1 indirect") if direct_indirect(record) end private @@ -54,5 +55,4 @@ def direct_indirect(record) record.direct_changed? && !record.direct? && record.count == 1 end end - -end \ No newline at end of file +end diff --git a/test/dag_test.rb b/test/dag_test.rb index 14038a5..f0541ac 100644 --- a/test/dag_test.rb +++ b/test/dag_test.rb @@ -1,76 +1,74 @@ -require 'minitest/autorun' -require 'active_record' +require "minitest/autorun" +require "active_record" require "./init" I18n.enforce_available_locales = true -ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => "#{File.dirname(__FILE__)}/database.test") +ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: "#{File.dirname(__FILE__)}/database.test") -#Used for basic graph link testing +# Used for basic graph link testing class Default < ActiveRecord::Base - acts_as_dag_links :node_class_name => 'Node' - self.table_name = 'edges' + acts_as_dag_links node_class_name: "Node" + self.table_name = "edges" end -#Used for polymorphic graph link testing +# Used for polymorphic graph link testing class Poly < ActiveRecord::Base - acts_as_dag_links :polymorphic => true - self.table_name = 'poly_edges' + acts_as_dag_links polymorphic: true + self.table_name = "poly_edges" end -#Used for redefinition testing +# Used for redefinition testing class Redefiner < ActiveRecord::Base - acts_as_dag_links :node_class_name => 'Redefiner', - :direct_column => 'd', - :count_column => 'c', - :ancestor_id_column => 'foo_id', - :descendant_id_column => 'bar_id' - self.table_name = 'edges2' + acts_as_dag_links node_class_name: "Redefiner", + direct_column: "d", + count_column: "c", + ancestor_id_column: "foo_id", + descendant_id_column: "bar_id" + self.table_name = "edges2" end class Node < ActiveRecord::Base - has_dag_links :link_class_name => 'Default' - self.table_name = 'nodes' + has_dag_links link_class_name: "Default" + self.table_name = "nodes" end class RedefNode < ActiveRecord::Base - has_dag_links :link_class_name => 'Redefiner' - self.table_name = 'redef_nodes' + has_dag_links link_class_name: "Redefiner" + self.table_name = "redef_nodes" end class AlphaNode < ActiveRecord::Base - has_dag_links :link_class_name => 'Poly', - :descendant_class_names => ['BetaNode', 'GammaNode', 'ZetaNode'] - self.table_name = 'alpha_nodes' + has_dag_links link_class_name: "Poly", + descendant_class_names: %w[BetaNode GammaNode ZetaNode] + self.table_name = "alpha_nodes" end class BetaNode < ActiveRecord::Base - has_dag_links :link_class_name => 'Poly', - :ancestor_class_names => ['AlphaNode', 'BetaNode'], - :descendant_class_names => ['BetaNode', 'GammaNode', 'ZetaNode'] - self.table_name = 'beta_nodes' + has_dag_links link_class_name: "Poly", + ancestor_class_names: %w[AlphaNode BetaNode], + descendant_class_names: %w[BetaNode GammaNode ZetaNode] + self.table_name = "beta_nodes" end class GammaNode < ActiveRecord::Base - has_dag_links :link_class_name => 'Poly', - :ancestor_class_names => ['AlphaNode', 'BetaNode', 'GammaNode'], - :descendant_class_names => ['GammaNode', 'ZetaNode'] - self.table_name = 'gamma_nodes' + has_dag_links link_class_name: "Poly", + ancestor_class_names: %w[AlphaNode BetaNode GammaNode], + descendant_class_names: %w[GammaNode ZetaNode] + self.table_name = "gamma_nodes" end class ZetaNode < ActiveRecord::Base - has_dag_links :link_class_name => 'Poly', - :ancestor_class_names => ['AlphaNode', 'BetaNode', 'GammaNode'] - self.table_name = 'zeta_nodes' + has_dag_links link_class_name: "Poly", + ancestor_class_names: %w[AlphaNode BetaNode GammaNode] + self.table_name = "zeta_nodes" end - -#Unit Tests for the DAG plugin +# Unit Tests for the DAG plugin class DagTest < Minitest::Test - - #Setups up database in memory + # Setups up database in memory def setup ActiveRecord::Migration.verbose = false - ActiveRecord::Schema.define(:version => 1) do + ActiveRecord::Schema.define(version: 1) do create_table :edges do |t| t.column :ancestor_id, :integer t.column :descendant_id, :integer @@ -120,102 +118,104 @@ def setup end end - #Brings down database + # Brings down database def teardown ActiveRecord::Base.connection.tables.each do |table| ActiveRecord::Base.connection.drop_table(table) end end - #Test ancestor id column default value + # Test ancestor id column default value def test_ancestor_id_column_default - assert_equal 'ancestor_id', Default.acts_as_dag_options[:ancestor_id_column] + assert_equal "ancestor_id", Default.acts_as_dag_options[:ancestor_id_column] end - #Test descendant id column default value + # Test descendant id column default value def test_descendant_id_column_default - assert_equal 'descendant_id', Default.acts_as_dag_options[:descendant_id_column] + assert_equal "descendant_id", Default.acts_as_dag_options[:descendant_id_column] end - #Test direct column default value + # Test direct column default value def test_direct_column_default - assert_equal 'direct', Default.acts_as_dag_options[:direct_column] + assert_equal "direct", Default.acts_as_dag_options[:direct_column] end - #Test count column default value + # Test count column default value def test_count_column_default - assert_equal 'count', Default.acts_as_dag_options[:count_column] + assert_equal "count", Default.acts_as_dag_options[:count_column] end - #Test ancestor type column default value + # Test ancestor type column default value def test_ancestor_type_column_default - assert_equal 'ancestor_type', Poly.acts_as_dag_options[:ancestor_type_column] + assert_equal "ancestor_type", Poly.acts_as_dag_options[:ancestor_type_column] end - #Test descendant type column default value + # Test descendant type column default value def test_descendant_type_column_default - assert_equal 'descendant_type', Poly.acts_as_dag_options[:descendant_type_column] + assert_equal "descendant_type", Poly.acts_as_dag_options[:descendant_type_column] end - #Test polymorphic option default value + # Test polymorphic option default value def test_polymorphic_default assert Poly.acts_as_dag_options[:polymorphic] assert !Default.acts_as_dag_options[:polymorphic] end - #more defaults here + # more defaults here - #Tests ancestor_id_column_name instance and class method + # Tests ancestor_id_column_name instance and class method def test_ancestor_id_column_name - assert_equal 'ancestor_id', Default.ancestor_id_column_name - assert_equal 'ancestor_id', Default.new.ancestor_id_column_name + assert_equal "ancestor_id", Default.ancestor_id_column_name + assert_equal "ancestor_id", Default.new.ancestor_id_column_name end - #Tests descendant_id_column_name instance and class method + # Tests descendant_id_column_name instance and class method def test_descendant_id_column_name - assert_equal 'descendant_id', Default.descendant_id_column_name - assert_equal 'descendant_id', Default.new.descendant_id_column_name + assert_equal "descendant_id", Default.descendant_id_column_name + assert_equal "descendant_id", Default.new.descendant_id_column_name end - #Tests direct_column_name instance and class method + # Tests direct_column_name instance and class method def test_direct_column_name - assert_equal 'direct', Default.direct_column_name - assert_equal 'direct', Default.new.direct_column_name + assert_equal "direct", Default.direct_column_name + assert_equal "direct", Default.new.direct_column_name end - #Tests count_column_name instance and class method + # Tests count_column_name instance and class method def test_count_column_name - assert_equal 'count', Default.count_column_name - assert_equal 'count', Default.new.count_column_name + assert_equal "count", Default.count_column_name + assert_equal "count", Default.new.count_column_name end - #Tests ancestor_type_column_name polymorphic instance and class method + # Tests ancestor_type_column_name polymorphic instance and class method def test_ancestor_type_column_name - assert_equal 'ancestor_type', Poly.ancestor_type_column_name - assert_equal 'ancestor_type', Poly.new.ancestor_type_column_name + assert_equal "ancestor_type", Poly.ancestor_type_column_name + assert_equal "ancestor_type", Poly.new.ancestor_type_column_name end - #Tests descendant_type_column_name polymorphic instance and class method + # Tests descendant_type_column_name polymorphic instance and class method def test_descendant_type_column_name - assert_equal 'descendant_type', Poly.descendant_type_column_name - assert_equal 'descendant_type', Poly.new.descendant_type_column_name + assert_equal "descendant_type", Poly.descendant_type_column_name + assert_equal "descendant_type", Poly.new.descendant_type_column_name end - #Tests that count is a protected function and cannot be assigned + # Tests that count is a protected function and cannot be assigned def test_count_protected - assert_raises(ActiveRecord::ActiveRecordError) { d = Default.new(:count => 1) } - assert_raises(ActiveRecord::ActiveRecordError) { d = Default.new() - d.count = 8 } + assert_raises(ActiveRecord::ActiveRecordError) { Default.new(count: 1) } + assert_raises(ActiveRecord::ActiveRecordError) do + d = Default.new + d.count = 8 + end end - #Tests that direct is a protected function and cannot be assigned - #def test_direct_protected + # Tests that direct is a protected function and cannot be assigned + # def test_direct_protected # assert_raises(ActiveRecord::ActiveRecordError) { d = Default.new(:direct => 1) } # assert_raises(ActiveRecord::ActiveRecordError) { d = Default.new() # d.direct = false } - #end + # end - #Tests that make_direct instance method trues direct value and registers change + # Tests that make_direct instance method trues direct value and registers change def test_make_direct_method d = Default.new assert !d.direct_changed? @@ -224,7 +224,7 @@ def test_make_direct_method assert d.direct end - #Tests that make_indirect instance method falses direct value and registers change + # Tests that make_indirect instance method falses direct value and registers change def test_make_indirect_method d = Default.new assert !d.direct_changed? @@ -233,91 +233,91 @@ def test_make_indirect_method assert !d.direct end - #Tests that changes register initial settings + # Tests that changes register initial settings def test_direct_changed_init_pass_in - d = Default.new(:direct => true) + d = Default.new(direct: true) assert d.direct_changed? end - #Tests that endpoint construction works + # Tests that endpoint construction works def test_make_endpoint a = Node.create! - p = Default::EndPoint.from(a) + p = Default::EndPoint.from(a, nil) assert p.matches?(a) end - #Tests that polymorphic endpoint construction works + # Tests that polymorphic endpoint construction works def test_make_endpoint_poly a = AlphaNode.create! - p = Poly::EndPoint.from(a) + p = Poly::EndPoint.from(a, nil) assert p.matches?(a) end - #Tests that source is correct + # Tests that source is correct def test_source_method a = Node.create! b = Node.create! - edge = Default.new(:ancestor => a, :descendant => b) + edge = Default.new(ancestor: a, descendant: b) s = edge.source assert s.matches?(a) end - #Tests that sink is correct + # Tests that sink is correct def test_sink_method a = Node.create! b = Node.create! - edge = Default.new(:ancestor => a, :descendant => b) + edge = Default.new(ancestor: a, descendant: b) s = edge.sink assert s.matches?(b) end - #Tests that source is correct for polymorphic graphs + # Tests that source is correct for polymorphic graphs def test_source_method_poly a = AlphaNode.create! b = AlphaNode.create! - edge = Poly.new(:ancestor => a, :descendant => b) + edge = Poly.new(ancestor: a, descendant: b) s = edge.source assert s.matches?(a) end - #Tests that sink is correct for polymorphic graphs + # Tests that sink is correct for polymorphic graphs def test_sink_method_poly a = AlphaNode.create! b = AlphaNode.create! - edge = Poly.new(:ancestor => a, :descendant => b) + edge = Poly.new(ancestor: a, descendant: b) s = edge.sink assert s.matches?(b) end - #Tests that source is correct when created from a model + # Tests that source is correct when created from a model def test_source_method_on_resource a = Node.create! - s = Default::Source.from(a) + s = Default::Source.from(a, nil) assert s.matches?(a) end - #Tests that sink is correct when created from a model + # Tests that sink is correct when created from a model def test_sink_method_on_resource a = Node.create! - s = Default::Source.from(a) + s = Default::Source.from(a, nil) assert s.matches?(a) end - #Tests that source is correct when created from a model for a polymorphic graph + # Tests that source is correct when created from a model for a polymorphic graph def test_source_method_on_resource_poly a = AlphaNode.create! - s = Poly::Source.from(a) + s = Poly::Source.from(a, nil) assert s.matches?(a) end - #Tests that sink is correct when created from a model for a polymorphic graph + # Tests that sink is correct when created from a model for a polymorphic graph def test_sink_method_on_resource_poly a = AlphaNode.create! - s = Poly::Source.from(a) + s = Poly::Source.from(a, nil) assert s.matches?(a) end - #Tests that class method for build works + # Tests that class method for build works def test_build_lonely_edge a = Node.create! b = Node.create! @@ -326,7 +326,7 @@ def test_build_lonely_edge assert e.sink.matches?(b) end - #Tests that create_edge works + # Tests that create_edge works def test_create_lonely_edge a = Node.create! b = Node.create! @@ -334,7 +334,7 @@ def test_create_lonely_edge assert e end - #Tests that create_edge! works + # Tests that create_edge! works def test_create_exla_lonely_edge a = Node.create! b = Node.create! @@ -343,56 +343,56 @@ def test_create_exla_lonely_edge assert_equal e.descendant, b end - #Tests that find edge works + # Tests that find edge works def test_find_lonely_edge a = Node.create! b = Node.create! - e = Default.create_edge(a, b) + Default.create_edge(a, b) e = Default.find_edge(a, b) assert_equal e.ancestor, a assert_equal e.descendant, b end - #Tests that find link works and find_edge rejects indirects + # Tests that find link works and find_edge rejects indirects def test_find_lonely_link a = Node.create! b = Node.create! - e = Default.create_edge(a, b) + Default.create_edge(a, b) e = Default.find_link(a, b) assert_equal e.ancestor, a assert_equal e.descendant, b end - #Tests that we catch links that would be duplicated on creation + # Tests that we catch links that would be duplicated on creation def test_validation_on_create_duplication_catch a = Node.create! b = Node.create! - e = Default.create_edge(a, b) + Default.create_edge(a, b) e2 = Default.create_edge(a, b) assert !e2 - assert_raises(ActiveRecord::RecordInvalid) { e3 = Default.create_edge!(a, b) } + assert_raises(ActiveRecord::RecordInvalid) { Default.create_edge!(a, b) } end - #Tests that we catch reversed links on creation (cycles) + # Tests that we catch reversed links on creation (cycles) def test_validation_on_create_reverse_catch a = Node.create! b = Node.create! - e = Default.create_edge(a, b) + Default.create_edge(a, b) e2 = Default.create_edge(b, a) assert !e2 - assert_raises(ActiveRecord::RecordInvalid) { e3 = Default.create_edge!(b, a) } + assert_raises(ActiveRecord::RecordInvalid) { Default.create_edge!(b, a) } end - #Tests that we catch self to self links on creation (self cycles) + # Tests that we catch self to self links on creation (self cycles) def test_validation_on_create_short_cycle_catch a = Node.create! - b = Node.create! + Node.create! e = Default.create_edge(a, a) assert !e assert_raises(ActiveRecord::RecordInvalid) { e = Default.create_edge!(a, a) } end - #Tests that a direct edge with 1 count cannot be made indirect on update + # Tests that a direct edge with 1 count cannot be made indirect on update def test_validation_on_update_indirect_catch a = Node.create! b = Node.create! @@ -402,7 +402,7 @@ def test_validation_on_update_indirect_catch assert_raises(ActiveRecord::RecordInvalid) { e.save! } end - #Tests that nochanges fails save and save! + # Tests that nochanges fails save and save! def test_validation_on_update_no_change_catch a = Node.create! b = Node.create! @@ -411,37 +411,37 @@ def test_validation_on_update_no_change_catch assert_raises(ActiveRecord::RecordInvalid) { e.save! } end - #Tests that destroyable? works as required + # Tests that destroyable? works as required def tests_destroyable a = Node.create! b = Node.create! e = Default.create_edge!(a, b) assert e.destroyable? c = Node.create! - f = Default.create_edge!(b, c) + Default.create_edge!(b, c) assert !Default.find_link(a, c).destroyable? end - #Tests that destroy link works + # Tests that destroy link works def tests_destroy_link a = Node.create! b = Node.create! e = Default.create_edge!(a, b) e.destroy assert Default.find_edge(a, b).nil? - e = Default.create_edge!(a, b) + Default.create_edge!(a, b) c = Node.create! - f = Default.create_edge!(b, c) + Default.create_edge!(b, c) assert_raises(ActiveRecord::ActiveRecordError) { Default.find_link(a, c).destroy } end - #Tests the balancing of a graph in the transitive simple case + # Tests the balancing of a graph in the transitive simple case def test_create_pair_link_transitive a = Node.create! b = Node.create! c = Node.create! - e = Default.create_edge!(a, b) - f = Default.create_edge!(b, c) + Default.create_edge!(a, b) + Default.create_edge!(b, c) g = Default.find_link(a, c) h = Default.find_edge(a, c) assert_equal g.ancestor, a @@ -449,13 +449,13 @@ def test_create_pair_link_transitive assert_nil h end - #Tests the ability to make an indirect link direct + # Tests the ability to make an indirect link direct def test_make_direct_link a = Node.create! b = Node.create! c = Node.create! - e = Default.create_edge!(a, b) - f = Default.create_edge!(b, c) + Default.create_edge!(a, b) + Default.create_edge!(b, c) g = Default.find_link(a, c) g.make_direct g.save! @@ -463,13 +463,13 @@ def test_make_direct_link assert_equal 2, g.count end - #Tests the ability to make a direct link indirect + # Tests the ability to make a direct link indirect def test_make_indirect_link a = Node.create! b = Node.create! c = Node.create! - e = Default.create_edge!(a, b) - f = Default.create_edge!(b, c) + Default.create_edge!(a, b) + Default.create_edge!(b, c) g = Default.find_link(a, c) g.make_direct g.save! @@ -479,28 +479,28 @@ def test_make_indirect_link assert_equal 1, g.count end - #Tests advanced transitive cases for chain graph rebalancing + # Tests advanced transitive cases for chain graph rebalancing def test_create_chain_disjoint a = Node.create! b = Node.create! c = Node.create! d = Node.create! - e = Default.create_edge!(a, b) - f = Default.create_edge!(c, d) - g = Default.create_edge!(b, c) - #a to c + Default.create_edge!(a, b) + Default.create_edge!(c, d) + Default.create_edge!(b, c) + # a to c test = Default.find_link(a, c) testnil = Default.find_edge(a, c) assert_equal test.ancestor, a assert_equal test.descendant, c assert_nil testnil - #a to d + # a to d test = Default.find_link(a, d) testnil = Default.find_edge(a, d) assert_equal test.ancestor, a assert_equal test.descendant, d assert_nil testnil - #b to d + # b to d test = Default.find_link(b, d) testnil = Default.find_edge(b, d) assert_equal test.ancestor, b @@ -509,10 +509,10 @@ def test_create_chain_disjoint end ########################## - #TESTS FOR has_dag_links # + # TESTS FOR has_dag_links # ########################## - #Tests has_many links_as_ancestor + # Tests has_many links_as_ancestor def test_has_many_links_as_ancestor a = Node.create! b = Node.create! @@ -523,7 +523,7 @@ def test_has_many_links_as_ancestor assert_equal e.descendant, b end - #Tests has_many links_as_descendant + # Tests has_many links_as_descendant def test_has_many_links_as_descendant a = Node.create! b = Node.create! @@ -534,7 +534,7 @@ def test_has_many_links_as_descendant assert_equal e.descendant, b end - #Tests has_many links_as_parent + # Tests has_many links_as_parent def test_has_many_links_as_parent a = Node.create! b = Node.create! @@ -545,7 +545,7 @@ def test_has_many_links_as_parent assert_equal e.descendant, b end - #Tests has_many links_as_child + # Tests has_many links_as_child def test_has_many_links_as_child a = Node.create! b = Node.create! @@ -556,7 +556,7 @@ def test_has_many_links_as_child assert_equal e.descendant, b end - #Tests has_many descendants + # Tests has_many descendants def test_has_many_descendants a = Node.create! b = Node.create! @@ -565,15 +565,15 @@ def test_has_many_descendants assert !e.nil? end - #Tests self_and_descendants + # Tests self_and_descendants def test_self_and_descendants - a = Node.create!(:name => 'a') - b = Node.create!(:name => 'b') + a = Node.create!(name: "a") + b = Node.create!(name: "b") a.descendants << b - assert_equal [a,b], a.self_and_descendants + assert_equal [a, b], a.self_and_descendants end - #Tests has_many ancestors + # Tests has_many ancestors def test_has_many_ancestors a = Node.create! b = Node.create! @@ -582,15 +582,15 @@ def test_has_many_ancestors assert !e.nil? end - #Tests self_and_ancestors + # Tests self_and_ancestors def test_self_and_ancestors a = Node.create! b = Node.create! b.ancestors << a - assert_equal [b,a], b.self_and_ancestors + assert_equal [b, a], b.self_and_ancestors end - #Tests has_many children + # Tests has_many children def test_has_many_children a = Node.create! b = Node.create! @@ -599,7 +599,7 @@ def test_has_many_children assert !e.nil? end - #Tests has_many parents + # Tests has_many parents def test_has_many_parents a = Node.create! b = Node.create! @@ -615,7 +615,7 @@ def test_has_many_parents_build_assign assert b.valid?, b.errors.full_messages end - #Tests leaf? instance method + # Tests leaf? instance method def test_leaf_instance_method a = Node.create! assert a.leaf? @@ -627,7 +627,7 @@ def test_leaf_instance_method assert b.leaf? end - #Tests root? instance method + # Tests root? instance method def test_root_instance_method a = Node.create! b = Node.create! @@ -639,7 +639,7 @@ def test_root_instance_method assert a.root? end - #Tests has_many links_as_ancestor + # Tests has_many links_as_ancestor def test_has_many_links_as_ancestor_poly a = BetaNode.create! b = BetaNode.create! @@ -650,7 +650,7 @@ def test_has_many_links_as_ancestor_poly assert_equal e.descendant, b end - #Tests has_many links_as_descendant + # Tests has_many links_as_descendant def test_has_many_links_as_descendant_poly a = BetaNode.create! b = BetaNode.create! @@ -661,7 +661,7 @@ def test_has_many_links_as_descendant_poly assert_equal e.descendant, b end - #Tests has_many links_as_parent + # Tests has_many links_as_parent def test_has_many_links_as_parent_poly a = BetaNode.create! b = BetaNode.create! @@ -672,7 +672,7 @@ def test_has_many_links_as_parent_poly assert_equal e.descendant, b end - #Tests has_many links_as_child + # Tests has_many links_as_child def test_has_many_links_as_child_poly a = BetaNode.create! b = BetaNode.create! @@ -683,7 +683,7 @@ def test_has_many_links_as_child_poly assert_equal e.descendant, b end - #Tests leaf? instance method + # Tests leaf? instance method def test_leaf_instance_method_poly a = BetaNode.create! assert a.leaf? @@ -695,7 +695,7 @@ def test_leaf_instance_method_poly assert b.leaf? end - #Tests root? instance method + # Tests root? instance method def test_root_instance_method_poly a = BetaNode.create! b = BetaNode.create! @@ -707,7 +707,7 @@ def test_root_instance_method_poly assert a.root? end - #Tests has_many links_as_ancestor_for_* + # Tests has_many links_as_ancestor_for_* def test_has_many_links_as_ancestor_for a = AlphaNode.create! b = BetaNode.create! @@ -718,7 +718,7 @@ def test_has_many_links_as_ancestor_for assert_equal e.descendant, b end - #Tests has_many links_as_descendant_for_* + # Tests has_many links_as_descendant_for_* def test_has_many_links_as_descendant_for a = AlphaNode.create! b = BetaNode.create! @@ -729,7 +729,7 @@ def test_has_many_links_as_descendant_for assert_equal e.descendant, b end - #Tests has_many links_as_parent_for_* + # Tests has_many links_as_parent_for_* def test_has_many_links_as_parent_for a = AlphaNode.create! b = BetaNode.create! @@ -740,7 +740,7 @@ def test_has_many_links_as_parent_for assert_equal e.descendant, b end - #Tests has_many links_as_child_for_* + # Tests has_many links_as_child_for_* def test_has_many_links_as_child_for a = AlphaNode.create! b = BetaNode.create! @@ -751,7 +751,7 @@ def test_has_many_links_as_child_for assert_equal e.descendant, b end - #Tests has_many descendant_type + # Tests has_many descendant_type def test_has_many_descendant_dvds a = AlphaNode.create! b = BetaNode.create! @@ -760,7 +760,7 @@ def test_has_many_descendant_dvds assert !e.nil? end - #Tests has_many ancestor_type + # Tests has_many ancestor_type def test_has_many_ancestor_dvds a = AlphaNode.create! b = BetaNode.create! @@ -769,7 +769,7 @@ def test_has_many_ancestor_dvds assert !e.nil? end - #Tests has_many child_dvds + # Tests has_many child_dvds def test_has_many_child_dvds a = AlphaNode.create! b = BetaNode.create! @@ -778,7 +778,7 @@ def test_has_many_child_dvds assert !e.nil? end - #Tests has_many parents + # Tests has_many parents def test_has_many_parent_dvds a = AlphaNode.create! b = BetaNode.create! @@ -787,7 +787,7 @@ def test_has_many_parent_dvds assert !e.nil? end - #Tests leaf_for_*? instance method + # Tests leaf_for_*? instance method def test_leaf_for_instance_method a = BetaNode.create! b = BetaNode.create! @@ -798,7 +798,7 @@ def test_leaf_for_instance_method assert !b.leaf_for_beta_nodes? end - #Tests root_for_*? instance method + # Tests root_for_*? instance method def test_root_for_instance_method a = BetaNode.create! b = BetaNode.create! @@ -809,32 +809,32 @@ def test_root_for_instance_method assert !b.root_for_beta_nodes? end - #Tests that longest_path_between works + # Tests that longest_path_between works def test_longest_path_between a = Node.create! b = Node.create! c = Node.create! d = Node.create! - e = Default.create_edge(a,b) - e = Default.create_edge(b,c) - e = Default.create_edge(a,c) - e = Default.create_edge(c,d) - path = Default.longest_path_between(a,d) - assert_equal [b,c,d], path + Default.create_edge(a, b) + Default.create_edge(b, c) + Default.create_edge(a, c) + Default.create_edge(c, d) + path = Default.longest_path_between(a, d) + assert_equal [b, c, d], path end - #Tests that shortest_path_between works + # Tests that shortest_path_between works def test_shortest_path_between a = Node.create! b = Node.create! c = Node.create! d = Node.create! - e = Default.create_edge(a,b) - e = Default.create_edge(b,c) - e = Default.create_edge(a,c) - e = Default.create_edge(c,d) - path = Default.shortest_path_between(a,d) - assert_equal [c,d], path + Default.create_edge(a, b) + Default.create_edge(b, c) + Default.create_edge(a, c) + Default.create_edge(c, d) + path = Default.shortest_path_between(a, d) + assert_equal [c, d], path end # Tests that perpetuate upon destroy works @@ -842,9 +842,8 @@ def tests_perpetuate_upon_destroy_link a = Node.create! b = Node.create! c = Node.create! - e = Default.create_edge!(a,b) - f = Default.create_edge!(b,c) + e = Default.create_edge!(a, b) + Default.create_edge!(b, c) e.destroy end - end