index_tree 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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: {}