index_tree 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 331b4073422929cabfbb5292d94d11bd1aca0e80
4
- data.tar.gz: 20958ba592b248ed9137ddac5b72f9f2f1b8cc89
3
+ metadata.gz: fd82c79ed504406d34114df0b5b9eddbe5b93129
4
+ data.tar.gz: 4777ebc01c42f0e811cc8603ad327efa8095ffdb
5
5
  SHA512:
6
- metadata.gz: 7f0b87af9ac9a7d5be4921f588464bec77655f1abb75ebefa7f38bf63fc9c4cf14897b940941defdf89b56c6f79565e9fa4f3c5fab398c35542f9fae0bc3aa4d
7
- data.tar.gz: 18ea0a9f0a72749582ba0bd6713e104d7ff577819afc49526070078de54a1353ab40563812f8349c7457d7aa3a071ec07fd8b8af4b1a073c73e0bcf6df2d16b0
6
+ metadata.gz: 5e3b6a75d319175569f5defccf9968ce8ea398e5e28854970500c167ef7f4c370a984cb45f2988800a02e11ee8ce8849e16e88681ee9759e58260baeb4e52476
7
+ data.tar.gz: 9d5b9aca049aba6a5fafc90b34da1ec337731cfcd22a859881ab00f99a554acee12cfbf1ccb79d0c26c5ad31f1e9a36ea01aee95824af5e0f406feb1e792121d
data/.coveralls.yml ADDED
@@ -0,0 +1 @@
1
+ service_name: travis-ci
data/.gitignore CHANGED
@@ -1,6 +1,7 @@
1
1
  /.bundle/
2
2
  /.yardoc
3
3
  /.idea
4
+ /.DS_Store
4
5
  /Gemfile.lock
5
6
  /_yardoc/
6
7
  /coverage/
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.2
4
+
5
+ gemfile:
6
+ - gemfiles/rails-4.0.gemfile
data/Gemfile CHANGED
@@ -1,4 +1,8 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in index-tree.gemspec
4
3
  gemspec
4
+
5
+ group :test do
6
+ gem 'rails'
7
+ gem 'coveralls', require: false
8
+ end
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![Build Status](https://secure.travis-ci.org//Natural-Intelligence/index_tree.svg?branch=master)](https://travis-ci.org/Natural-Intelligence/index\_tree)
2
+ [![Coverage Status](https://coveralls.io/repos/AlexStanovsky/index_tree/badge.png)](https://coveralls.io/r/AlexStanovsky/index_tree)
1
3
  # IndexTree
2
4
 
3
5
  This Gem eager loads trees by indexing the nodes of the tree. The number of queries needed to load a tree is N,
@@ -5,11 +7,11 @@ when N is the number of different models(ActiveRecords) in the tree.
5
7
 
6
8
  Each inner object in the tree have an index node instance that is connecting it to the root.
7
9
  When the root of the tree is loaded, only the objects that are in the tree are fetched(Pruning).
8
- The index nodes are created when the root element is saved.
10
+ The index nodes are created when the root element is saved and stored in the IndexNode model.
9
11
 
10
12
  Example:
11
13
 
12
- class Equation
14
+ class Equation < ActiveRecord::Base
13
15
  acts_as_indexed_node :root => true do
14
16
  has_many :expressions
15
17
  end
@@ -18,7 +20,7 @@ Example:
18
20
  end
19
21
 
20
22
 
21
- class Expression
23
+ class Expression < ActiveRecord::Base
22
24
  acts_as_indexed_node do
23
25
  has_many :expressions
24
26
  end
@@ -64,7 +66,7 @@ And then execute:
64
66
  $ bundle
65
67
  $ rake db:migrate
66
68
 
67
- There is a migration which creates index_tree_index_node table
69
+ There is a migration which creates index_tree_index_node table(IndexNode model)
68
70
 
69
71
  ## Declaration
70
72
 
data/Rakefile CHANGED
@@ -1,2 +1,33 @@
1
1
  require "bundler/gem_tasks"
2
2
 
3
+ desc "Run unit tests."
4
+ task :test do
5
+ $: << "test"
6
+
7
+ require 'simplecov'
8
+ require 'coveralls'
9
+
10
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
11
+ SimpleCov::Formatter::HTMLFormatter,
12
+ Coveralls::SimpleCov::Formatter
13
+ ]
14
+ SimpleCov.start do
15
+ add_filter 'test/'
16
+ add_filter 'db/'
17
+ end
18
+
19
+ Dir["test/*_test.rb"].each { |f| require f[5..-4] }
20
+ end
21
+
22
+ task :default => :test
23
+
24
+ require 'rdoc/task'
25
+ RDoc::Task.new do |rdoc|
26
+ require "index_tree/version"
27
+ version = IndexTree::VERSION
28
+
29
+ rdoc.rdoc_dir = 'rdoc'
30
+ rdoc.title = "index_tree #{version}"
31
+ rdoc.rdoc_files.include('README*')
32
+ rdoc.rdoc_files.include('lib/**/*.rb')
33
+ end
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem 'coveralls', require: false
4
+ gem "rails", "~> 4.0"
5
+ gem "index_tree", path: "../"
6
+
7
+ gemspec path: "../"
data/index_tree.gemspec CHANGED
@@ -22,5 +22,8 @@ The index nodes are created when the root element is saved.}
22
22
  spec.files = `git ls-files`.split($/)
23
23
  spec.require_paths = %w(lib app)
24
24
 
25
- spec.add_dependency "activerecord"
25
+ spec.add_dependency "activerecord", ">= 3.0.0"
26
+ spec.add_development_dependency "sqlite3"
27
+ spec.add_development_dependency "rdoc"
28
+ spec.add_development_dependency "minitest"
26
29
  end
@@ -1,14 +1,13 @@
1
1
  # Rails Engine copies the migration to the Rails app
2
- module IndexTree
3
- class Engine < Rails::Engine
4
- isolate_namespace IndexTree
2
+ class IndexTree::Engine < Rails::Engine
3
+ isolate_namespace IndexTree
5
4
 
6
- initializer :append_migrations do |app|
7
- unless app.root.to_s.match root.to_s
8
- config.paths["db/migrate"].expanded.each do |expanded_path|
9
- app.config.paths["db/migrate"] << expanded_path
10
- end
5
+ initializer :append_migrations do |app|
6
+ unless app.root.to_s.match root.to_s
7
+ config.paths["db/migrate"].expanded.each do |expanded_path|
8
+ app.config.paths["db/migrate"] << expanded_path
11
9
  end
12
10
  end
13
11
  end
14
12
  end
13
+
@@ -41,10 +41,9 @@ module IndexTree
41
41
  return valid_associations
42
42
  end
43
43
 
44
- # Invoke index node creation for children node
44
+ # Invoke index node creation for children nodes
45
45
  # @param [root_element] id of the root element
46
46
  def create_index_nodes_for_children(root_element)
47
-
48
47
  self.child_nodes.keys.each do |association_name|
49
48
  association_value = send(association_name)
50
49
 
@@ -56,4 +55,4 @@ module IndexTree
56
55
 
57
56
  end
58
57
  end
59
- end
58
+ end
@@ -1,8 +1,7 @@
1
- # Defines the ActsAssTreeNode Method
2
- module IndexTree
3
- class Railtie < Rails::Railtie
4
- ActiveSupport.on_load(:active_record) do
5
- extend IndexTree::ActsAsIndexedNode
6
- end
1
+ # Defines the ActsAsIndexedNode Method
2
+ class IndexTree::Railtie < Rails::Railtie
3
+ ActiveSupport.on_load(:active_record) do
4
+ extend IndexTree::ActsAsIndexedNode
7
5
  end
8
- end
6
+ end
7
+
@@ -1,7 +1,7 @@
1
1
  require 'active_support/concern'
2
2
 
3
3
  # Defines the root element of the tree, it invokes the recursive index nodes creation.
4
- # And Hold the structure of the tree
4
+ # And Holds the structure of the tree
5
5
  module IndexTree
6
6
  module RootElement
7
7
  extend ActiveSupport::Concern
@@ -26,7 +26,8 @@ module IndexTree
26
26
 
27
27
  # @return [tree_structure] Each Root Element hold the structure of the tree
28
28
  def self.tree_structure
29
- @@tree_structure ||= create_tree_structure({}, self)
29
+ @@tree_structure ||= {}
30
+ @@tree_structure[self] ||= create_tree_structure({}, self)
30
31
  end
31
32
 
32
33
  # Recursive function that create the structure of the tree
@@ -1,5 +1,7 @@
1
1
  module IndexTree
2
2
  module TreePreloader
3
+ # Reads the loading instruction from the tree structures and load the entities
4
+ # @param [root_entities] entities to load
3
5
  def self.preload_entities(root_entities)
4
6
 
5
7
  root_entities_array = Array(root_entities)
@@ -28,8 +30,10 @@ module IndexTree
28
30
 
29
31
  def self.preload_class(cache, root_entity_class, class_to_load)
30
32
  unless cache.has_key?(class_to_load)
31
- cache[class_to_load] = Hash[class_to_load.joins(:index_tree_index_node).where(:index_tree_index_nodes => {:root_element_type => cache[root_entity_class].values.first.class,
32
- :root_element_id => cache[root_entity_class].keys}).all.map { |t| [t.id, t] }]
33
+ cache[class_to_load] = Hash[class_to_load.joins(:index_tree_index_node)
34
+ .where(:index_tree_index_nodes => {:root_element_type => root_entity_class,
35
+ :root_element_id => cache[root_entity_class].keys})
36
+ .load.map { |entity| [entity.id, entity] }]
33
37
  end
34
38
  end
35
39
 
@@ -41,8 +45,10 @@ module IndexTree
41
45
  end
42
46
 
43
47
  parent_id = source_entity.send(association_name.to_s + '_id')
44
- source_entity.association(association_name).target = cache[target_class][parent_id]
45
- source_entity.association(association_name).loaded!
48
+ if (parent_id)
49
+ source_entity.association(association_name).target = cache[target_class][parent_id]
50
+ source_entity.association(association_name).loaded!
51
+ end
46
52
  end
47
53
  end
48
54
 
@@ -1,3 +1,3 @@
1
1
  module IndexTree
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.3"
3
3
  end
data/test/base_test.rb ADDED
@@ -0,0 +1,101 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/benchmark'
3
+ require 'active_record'
4
+ require 'rails'
5
+ require 'index_tree'
6
+
7
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
8
+
9
+ class MiniTest::Unit::TestCase
10
+ def assert_queries(num = 1, &block)
11
+ query_count, result = count_queries(&block)
12
+ result
13
+ ensure
14
+ assert_equal num, query_count, "#{query_count} instead of #{num} queries were executed."
15
+ end
16
+
17
+ def assert_no_queries(&block)
18
+ assert_queries(0, &block)
19
+ end
20
+
21
+ def count_queries &block
22
+ count = 0
23
+
24
+ counter_f = ->(name, started, finished, unique_id, payload) {
25
+ unless %w[ CACHE SCHEMA ].include? payload[:name]
26
+ count += 1
27
+ end
28
+ }
29
+
30
+ result = ActiveSupport::Notifications.subscribed(counter_f, "sql.active_record", &block)
31
+
32
+ [count, result]
33
+ end
34
+ end
35
+
36
+
37
+ def setup_db
38
+ ActiveRecord::Schema.define(version: 1) do
39
+ create_table :index_tree_index_nodes do |t|
40
+ t.integer :root_element_id, null: false
41
+ t.string :root_element_type, null: false
42
+ t.integer :node_element_id, null: false
43
+ t.string :node_element_type, null: false
44
+ t.datetime :created_at
45
+ t.datetime :updated_at
46
+ end
47
+
48
+
49
+ create_table :equations do |t|
50
+ end
51
+
52
+ create_table :expressions do |t|
53
+ t.references :equation
54
+ t.references :expression
55
+ end
56
+
57
+ create_table :poly_equations do |t|
58
+ t.references :poly_expression
59
+ end
60
+
61
+ create_table :expression_containers do |t|
62
+ t.references :poly_expression
63
+ t.references :base_expression, polymorphic: true
64
+ end
65
+
66
+ create_table :poly_expressions do |t|
67
+ end
68
+
69
+ create_table :values do |t|
70
+ end
71
+
72
+ create_table :sti_equations do |t|
73
+ t.references :sti_expression
74
+ end
75
+
76
+ create_table :sti_expressions do |t|
77
+ t.string :type, null: false
78
+ t.references :sti_expression
79
+ end
80
+ end
81
+
82
+ end
83
+
84
+ def teardown_db
85
+ ActiveRecord::Base.connection.tables.each do |table|
86
+ ActiveRecord::Base.connection.drop_table(table)
87
+ end
88
+ end
89
+
90
+ def preload_tree(class_to_test,num_of_queries)
91
+ not_load = class_to_test.first
92
+ assert_queries(num_of_queries) do
93
+ not_load.traverse
94
+ end
95
+
96
+ loaded = class_to_test.first.preload_tree
97
+ assert_no_queries do
98
+ loaded.traverse
99
+ end
100
+ end
101
+
@@ -0,0 +1,88 @@
1
+ require 'base_test'
2
+
3
+ class PolyEquation < ActiveRecord::Base
4
+ acts_as_indexed_node :root => true do
5
+ belongs_to :poly_expression
6
+ end
7
+
8
+ def traverse
9
+ poly_expression.traverse
10
+ end
11
+ end
12
+
13
+ class ExpressionContainer < ActiveRecord::Base
14
+ belongs_to :poly_expression
15
+
16
+ acts_as_indexed_node do
17
+ belongs_to :base_expression, polymorphic: true
18
+ end
19
+
20
+ def traverse
21
+ base_expression.traverse
22
+ end
23
+ end
24
+
25
+ class PolyExpression < ActiveRecord::Base
26
+ acts_as_indexed_node do
27
+ has_many :expression_containers
28
+ end
29
+
30
+ has_one :expression_container, as: :base_expression
31
+
32
+ def traverse
33
+ expression_containers.map(&:traverse)
34
+ end
35
+ end
36
+
37
+ class Value < ActiveRecord::Base
38
+ acts_as_indexed_node
39
+
40
+ has_one :expression_container, as: :base_expression
41
+
42
+ def traverse
43
+ end
44
+ end
45
+
46
+ class PolymorphicTreeTest < MiniTest::Unit::TestCase
47
+
48
+ def setup
49
+ # teardown_db
50
+ setup_db
51
+
52
+ value1 = Value.create!()
53
+ value2 = Value.create!()
54
+ value3 = Value.create!()
55
+ value4 = Value.create!()
56
+
57
+ expression1 = PolyExpression.create!()
58
+ ExpressionContainer.create!(poly_expression: expression1, base_expression: value1)
59
+ ExpressionContainer.create!(poly_expression: expression1, base_expression: value2)
60
+
61
+ expression2 = PolyExpression.create!()
62
+ ExpressionContainer.create!(poly_expression: expression2, base_expression: value3)
63
+ ExpressionContainer.create!(poly_expression: expression2, base_expression: value4)
64
+ ExpressionContainer.create!(poly_expression: expression2, base_expression: expression1)
65
+ ExpressionContainer.create!(poly_expression: expression2, base_expression: expression1)
66
+
67
+ PolyEquation.create!(poly_expression: expression2)
68
+ end
69
+
70
+ def teardown
71
+ teardown_db
72
+ end
73
+
74
+ def test_preload_tree
75
+ preload_tree(PolyEquation,12)
76
+ # not_load_equation = PolyEquation.first
77
+ #
78
+ # assert_queries(12) do
79
+ # not_load_equation.traverse
80
+ # end
81
+ #
82
+ # load_equation = PolyEquation.first.preload_tree
83
+ #
84
+ # assert_no_queries do
85
+ # load_equation.traverse
86
+ # end
87
+ end
88
+ end
@@ -0,0 +1,92 @@
1
+ require 'base_test'
2
+
3
+
4
+ class Equation < ActiveRecord::Base
5
+ acts_as_indexed_node :root => true do
6
+ has_one :expression, inverse_of: :equation
7
+ end
8
+
9
+ def traverse
10
+ expression.traverse
11
+ end
12
+ end
13
+
14
+ class Expression < ActiveRecord::Base
15
+ belongs_to :equation, inverse_of: :expression
16
+ belongs_to :expression
17
+
18
+ acts_as_indexed_node do
19
+ has_many :expressions
20
+ end
21
+
22
+ def traverse
23
+ expressions.map(&:traverse)
24
+ end
25
+ end
26
+
27
+ class SimpleTreeTest < MiniTest::Unit::TestCase
28
+
29
+ def setup
30
+ setup_db
31
+
32
+ equation = Equation.create!()
33
+
34
+ @expression1 = Expression.create!(equation: equation)
35
+ expression2 = Expression.create!(expression: @expression1)
36
+ expression3 = Expression.create!(expression: @expression1)
37
+ expression4 = Expression.create!(expression: expression2)
38
+ expression5 = Expression.create!(expression: expression2)
39
+ expression6 = Expression.create!(expression: expression4)
40
+ expression7 = Expression.create!(expression: expression4)
41
+ expression8 = Expression.create!(expression: expression5)
42
+
43
+ # Rebuild index tree
44
+ equation.save
45
+ end
46
+
47
+ def teardown
48
+ teardown_db
49
+ end
50
+
51
+ def test_preload_tree
52
+ preload_tree(Equation, 9)
53
+ end
54
+
55
+
56
+ def test_preload_multi_tree
57
+ not_load = Equation.all
58
+ assert_queries(10) do
59
+ not_load.map(&:traverse)
60
+ end
61
+
62
+ loaded = Equation.all.preload_tree
63
+ assert_no_queries do
64
+ loaded.map(&:traverse)
65
+ end
66
+
67
+ end
68
+
69
+ def count_nodes(tree_node)
70
+ num_of_nodes = 0
71
+
72
+ tree_node.class.child_nodes.keys.each do |association_name|
73
+ association_value = tree_node.send(association_name)
74
+
75
+ Array(association_value).each do |child_node|
76
+ num_of_nodes += count_nodes(child_node) + 1
77
+ end
78
+ end
79
+
80
+ return num_of_nodes
81
+ end
82
+
83
+ def test_index_node_creation
84
+
85
+ equation1 = Equation.first
86
+
87
+ num_of_tree_nodes = count_nodes(equation1)
88
+ assert_equal num_of_tree_nodes, equation1.index_tree_index_nodes.size, "#{num_of_tree_nodes} instead of #{equation1.index_tree_index_nodes.size} index node were created."
89
+ end
90
+ end
91
+
92
+
@@ -0,0 +1,59 @@
1
+ require 'base_test'
2
+
3
+ class StiEquation < ActiveRecord::Base
4
+ acts_as_indexed_node :root => true do
5
+ belongs_to :sti_expression
6
+ end
7
+
8
+ def traverse
9
+ sti_expression.traverse
10
+ end
11
+ end
12
+
13
+ class StiExpression < ActiveRecord::Base
14
+ belongs_to :sti_expression
15
+
16
+ acts_as_indexed_node do
17
+ has_many :sti_expressions
18
+ end
19
+
20
+ def traverse
21
+ sti_expressions.map(&:traverse)
22
+ end
23
+ end
24
+
25
+ class AExpression < StiExpression
26
+ end
27
+ class BExpression < StiExpression
28
+ end
29
+ class CExpression < StiExpression
30
+ end
31
+ class DExpression < StiExpression
32
+ end
33
+
34
+
35
+ class StiTreeTest < MiniTest::Unit::TestCase
36
+
37
+ def setup
38
+ setup_db
39
+
40
+ expression1 = AExpression.create!
41
+ expression2 = BExpression.create!(sti_expression: expression1)
42
+ expression3 = CExpression.create!(sti_expression: expression1)
43
+ expression4 = DExpression.create!(sti_expression: expression2)
44
+ expression5 = AExpression.create!(sti_expression: expression2)
45
+ expression6 = BExpression.create!(sti_expression: expression4)
46
+ expression7 = CExpression.create!(sti_expression: expression4)
47
+ expression8 = DExpression.create!(sti_expression: expression5)
48
+
49
+ root = StiEquation.create!(sti_expression: expression1)
50
+ end
51
+
52
+ def teardown
53
+ teardown_db
54
+ end
55
+
56
+ def test_preload_tree
57
+ preload_tree(StiEquation,9)
58
+ end
59
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: index_tree
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Stanovsky
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-29 00:00:00.000000000 Z
11
+ date: 2014-10-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,9 +16,51 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: 3.0.0
20
20
  type: :runtime
21
21
  prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 3.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: sqlite3
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rdoc
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
22
64
  version_requirements: !ruby/object:Gem::Requirement
23
65
  requirements:
24
66
  - - ">="
@@ -33,7 +75,9 @@ executables: []
33
75
  extensions: []
34
76
  extra_rdoc_files: []
35
77
  files:
78
+ - ".coveralls.yml"
36
79
  - ".gitignore"
80
+ - ".travis.yml"
37
81
  - Gemfile
38
82
  - LICENSE
39
83
  - LICENSE.txt
@@ -42,6 +86,7 @@ files:
42
86
  - app/models/index_node.rb
43
87
  - db/migrate/20141004075233_create_index_tree_index_nodes.rb
44
88
  - db/migrate/20141004075333_add_index_to_tree_index_nodes.rb
89
+ - gemfiles/rails-4.0.gemfile
45
90
  - index_tree.gemspec
46
91
  - lib/index_tree.rb
47
92
  - lib/index_tree/acts_as_indexed_node.rb
@@ -52,6 +97,10 @@ files:
52
97
  - lib/index_tree/root_element.rb
53
98
  - lib/index_tree/tree_preloader.rb
54
99
  - lib/index_tree/version.rb
100
+ - test/base_test.rb
101
+ - test/polymorphic_tree_test.rb
102
+ - test/simple_tree_test.rb
103
+ - test/sti_tree_test.rb
55
104
  homepage: http://www.naturalint.com
56
105
  licenses: []
57
106
  metadata: {}