taxonomite 0.1.0
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 +7 -0
- data/Rakefile +30 -0
- data/lib/taxonomite.rb +6 -0
- data/lib/taxonomite/entity.rb +39 -0
- data/lib/taxonomite/exceptions.rb +7 -0
- data/lib/taxonomite/node.rb +158 -0
- data/lib/taxonomite/taxonomite_configuration.rb +17 -0
- data/lib/taxonomite/tree.rb +130 -0
- data/lib/taxonomite/version.rb +3 -0
- data/spec/controllers/taxonomite/taxonomite_controller_spec.rb +31 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +19 -0
- data/spec/dummy/app/assets/stylesheets/scaffold.css +56 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/taxonomite_controller.rb +226 -0
- data/spec/dummy/app/helpers/application_helper.rb +13 -0
- data/spec/dummy/app/helpers/taxonomite_helper.rb +9 -0
- data/spec/dummy/app/views/layouts/application.html.erb +22 -0
- data/spec/dummy/app/views/shared/_error_messages.html.erb +14 -0
- data/spec/dummy/app/views/shared/_flash_messages.html.erb +9 -0
- data/spec/dummy/app/views/taxonomite/_node.html.erb +6 -0
- data/spec/dummy/app/views/taxonomite/edit.html.erb +49 -0
- data/spec/dummy/app/views/taxonomite/index.html.erb +9 -0
- data/spec/dummy/app/views/taxonomite/new.html.erb +16 -0
- data/spec/dummy/app/views/taxonomite/show.html.erb +28 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +29 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +35 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +38 -0
- data/spec/dummy/config/environments/production.rb +76 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/mongoid.rb +8 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +10 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/mongoid.yml +103 -0
- data/spec/dummy/config/routes.rb +10 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/lib/tasks/db_populate.rake +66 -0
- data/spec/dummy/lib/util.rb +28 -0
- data/spec/dummy/log/development.log +7169 -0
- data/spec/dummy/log/test.log +915 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/-rejHCRn0GBGCrHMyZAcq10cNOCXaG4dVN13pFMvLgA.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/1TWMTorbA_3E6hatKbP5T9n5oDSeVMk68LF8CRn1JjI.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/3B4hMNppXANClZ-lGO7Jps_MbXOz_GLxSqnastPo3Sg.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/8H7HwvVynNVZNgOjRJ5eAUqlWB8-oDl9VwBJlQSTBQw.cache +2 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/8y77NTXIYLg5cxm011nNxQAil9n5WVHkcp3HuxsFBMY.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/AmhMJ3oocG6mNJTk0T7UZTZNv6MhN1nj2WL-k__XZlI.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/CbVJZSB6kte1IYJqiASuO95NmINClhlHaFdLKMor0c0.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/D05VjCHQqo1_JqnZLVivHPybbW2MCDXAQTDeLMBqYIQ.cache +2 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/GalDJvDt8BphTIma_xp93plZ7ay3K9A9uJk3txZw_00.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/HMK_AuhVBxFLR6tGFGS75xvUQp8JlDZBwUddr14DleY.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/Hac0rR46nAtlqEtr6S-s7g79TUGQAKO_jxYLbyARW4k.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/IygAr9Ty7v-NJrroesXGkla15XgRR3nUI7BVZaPZigk.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/K46UGuIKOnMfRnYBSmbjd8UJ9LfdCS5OBhkXG00GCvY.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/KVHAw7mbk0REIRf0Zrv40JhknZB1lVmcExhj4bfZduY.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/LhBEGJtsniGETE6aGB-IrzlM_CxS6iAfBviyrUM2AvA.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/PzbUQc7wYDmhK5RZzXNRAHPhpwryEK2aIRRUfbsFMjw.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/Q2QIHypAobobCW8WZnDEKFQqFczCiXaPAf3Hgk9MA_o.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/RnBEbAuuDsDqp3JRLnX8pp-IHFk1d1rGbXxYy6DhkY4.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/TIm2-xmbi7CMNL3syzvG5LyRxJtycIGXtwz8Mco3sdA.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/U-xdOqbEKMZubNF_Mq_pBnN5uGgDj_w3I9193KH2XIE.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/ZEWFmXe3i1eUOHbMoM_K0-6G0ejgZCWT3qF9dyjYEzQ.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/_k3G10-SEe8_SHlmwYbslXN42uDRmFGK0Q7nR3VyH7s.cache +2 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/bkkgBW_hSRUeyxOZ8u-7Artvy3oCRMUtd0iTsd8wrtk.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/bpttkziOegeD-17ihmHMccaVxwU9_YMeXXsmFpQQh2E.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/c0embYAEeyo6qdjqQ-b8QNzuj70uZn-q1c-dhX4aeaQ.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/dbpC0p6RBdyEjXhOxUegedCy8aK12GwuakY1DqzSB1k.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/exOsm6yrmkOg_T82Brjsyr-e2U5HAJCqiPy1NJjPBJg.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/f7S__u6qEm3h40_nJwEAwibtxI3vV-uWkuJAdnyIEho.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/lEOEeQNsy0n89Y25rZEAe5P6zFLjkhRpxQ2ZEfi_giM.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/m_RzwKTs1azS37mZ7W1stXJCfGBM4xF-Pge7trJaXhY.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/nMQn6BRwxARa2nhmNQEdm_5xj6tr2XYpLzE_7Eac2h4.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/nl0sTVgLFpHD_1Q4c8NGcatzmud7AWL6yin3iSgziRc.cache +2 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/rKpLfQIjRPLcR43VeI7lwjxj1BWPcxjVoSyDmJru04o.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/rhofdc-dVgpC_YOqzpz2yMRHD9LW4sIGBcPdzR-uM6E.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/sjZWL1ZezAb-_PmJJS_5mi-Q9X1Jcic0D7F33sUC3vs.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/tLUV7eduShLmpqrXfsgemL-zKEBEQQDdqUw-MGoqMA0.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/uYLC0l4_HXjqNdRF2mvEqpdcIsQgz0XVMe9YxQ3dnlc.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/voPvH6F_x-d5fK63I1IjMzEfTcybOHN-g9q3x4BIGGw.cache +1 -0
- data/spec/factories/entity.rb +13 -0
- data/spec/factories/taxonomy_taxon.rb +45 -0
- data/spec/models/taxonomite/entity_spec.rb +34 -0
- data/spec/models/taxonomite/hierarchy_spec.rb +34 -0
- data/spec/models/taxonomite/model_spec.rb +39 -0
- data/spec/models/taxonomite/taxonomite_spec.rb +11 -0
- data/spec/models/taxonomite/tree_spec.rb +56 -0
- data/spec/models/taxonomite/zoology.rb +119 -0
- data/spec/rails_helper.rb +53 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/taxonomite_helper.rb +5 -0
- data/spec/views/taxonomite/taxonomite/index.html.erb_spec.rb +5 -0
- data/spec/views/taxonomite/taxonomite/show.html.erb_spec.rb +5 -0
- metadata +456 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 23b3b1dfb04a5bba6964518d64495f5ce765a401
|
|
4
|
+
data.tar.gz: 8b97678d947fb2f0fea89b2298885bad97e01f61
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ab087ff6284c6b3208d6dc1be6b64aa6cd47c0464f5dbc9cd3abc4074f8fdfdd7c0685118350b98393bbb5f42534b7a042d47211927b778a36cf4b33427dfc34
|
|
7
|
+
data.tar.gz: 263d4a6ca733fad72e9074e6c852f4d36da4a00324c85523671ffa8a246c97c3982b3a193f96c01a40a40a9031d79dc5f1407040f0a4f8c7ecba740d6c8883ce
|
data/Rakefile
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env rake
|
|
2
|
+
begin
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
require 'bundler/gem_tasks'
|
|
5
|
+
rescue LoadError
|
|
6
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
|
7
|
+
end
|
|
8
|
+
require 'rspec/core/rake_task'
|
|
9
|
+
require 'rdoc/task'
|
|
10
|
+
|
|
11
|
+
# APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
|
12
|
+
|
|
13
|
+
Bundler::GemHelper.install_tasks
|
|
14
|
+
#Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each {|f| load f }
|
|
15
|
+
|
|
16
|
+
desc "Run all specs in spec directory (excluding plugin specs)"
|
|
17
|
+
RSpec::Core::RakeTask.new(:spec) do |task|
|
|
18
|
+
task.rspec_opts = ['--color', '--format', 'documentation']
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# RDoc tasks
|
|
22
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
|
23
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
24
|
+
rdoc.title = 'Places'
|
|
25
|
+
rdoc.options << '--line-numbers'
|
|
26
|
+
rdoc.rdoc_files.include('README.rdoc')
|
|
27
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
task :default => :spec
|
data/lib/taxonomite.rb
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# entity.rb
|
|
2
|
+
|
|
3
|
+
require 'active_support/concern'
|
|
4
|
+
|
|
5
|
+
module Taxonomite
|
|
6
|
+
module Entity
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
included do
|
|
10
|
+
has_one :taxonomy_node, class_name: 'Taxonomite::Node', as: :owner, validate: true
|
|
11
|
+
before_save :do_setup
|
|
12
|
+
|
|
13
|
+
class_eval "def base_class; ::#{self.name}; end"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def get_taxonomy_node
|
|
17
|
+
if (self.taxonomy_node == nil)
|
|
18
|
+
self.taxonomy_node = self.create_taxonomy_node
|
|
19
|
+
end
|
|
20
|
+
self.taxonomy_node
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
protected
|
|
24
|
+
# classes overload to create the appropriate taxonomy_node
|
|
25
|
+
# def create_taxonomy_node
|
|
26
|
+
# Taxonomite::Node.new(name: self.name)
|
|
27
|
+
# end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
# subclasses should overload create_taxonomy_node to create the appropriate Place object and set it up
|
|
31
|
+
def do_setup
|
|
32
|
+
if (self.taxonomy_node == nil)
|
|
33
|
+
self.taxonomy_node = self.respond_to?(:create_taxonomy_node) ? self.create_taxonomy_node : Taxonomite::Node.new(name: self.name)
|
|
34
|
+
self.taxonomy_node.owner = self
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end # module Location
|
|
39
|
+
end # module Places
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
require 'taxonomite/taxonomite_configuration'
|
|
2
|
+
require 'taxonomite/tree'
|
|
3
|
+
|
|
4
|
+
module Taxonomite
|
|
5
|
+
class Node
|
|
6
|
+
# Class declarations
|
|
7
|
+
#
|
|
8
|
+
# configuration
|
|
9
|
+
#
|
|
10
|
+
# !! note that configuration is class-wide, rather than on an
|
|
11
|
+
# instance-instance value; this could be changed, for example if
|
|
12
|
+
# two simultaneous persistence models for the place were desired
|
|
13
|
+
#
|
|
14
|
+
class << self
|
|
15
|
+
attr_writer :configiration
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.config
|
|
19
|
+
@configuration ||= Taxonomite::Configuration.new
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.configure
|
|
23
|
+
yield(config)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.reset
|
|
27
|
+
@configuration = Taxonomite::Configuration.new
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.primary_key
|
|
31
|
+
"_id"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
case Node.config.use_tree_model
|
|
35
|
+
when :self
|
|
36
|
+
include ::Mongoid::Document
|
|
37
|
+
include Taxonomite::Tree
|
|
38
|
+
|
|
39
|
+
# configure the way the tree behaves
|
|
40
|
+
before_destroy :nullify_children
|
|
41
|
+
else
|
|
42
|
+
raise RuntimeError, 'Invalid option for Node.config.use_tree_model: #{Node.config.use_tree_model}'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
field :name, type: String # name of this particular object (not really node)
|
|
46
|
+
field :description, type: String # description of this item
|
|
47
|
+
field :entity_type, type: String, default: ->{ self.get_entity_type } # type of entity (i.e. state, county, city, etc.)
|
|
48
|
+
|
|
49
|
+
belongs_to :owner, polymorphic: true # this is the associated object
|
|
50
|
+
|
|
51
|
+
## Data collection methods
|
|
52
|
+
|
|
53
|
+
# aggregates the results of calling method m on all leaves of the tree and returns that
|
|
54
|
+
def aggregate_leaves(m)
|
|
55
|
+
if self.leaf?
|
|
56
|
+
return self.owner.instance_eval(m) if self.owner != nil && self.owner.respond_to?(m)
|
|
57
|
+
return self.instance_eval(m) if self.respond_to?(m)
|
|
58
|
+
return 0
|
|
59
|
+
else
|
|
60
|
+
res = 0
|
|
61
|
+
self.children.each do |c|
|
|
62
|
+
res = res + c.aggregate_leaves(m)
|
|
63
|
+
end
|
|
64
|
+
return res
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
## Typeification methods
|
|
69
|
+
|
|
70
|
+
# typeify name w entity (i.e. 'Washington state' vs. 'Seattle')
|
|
71
|
+
def typeifiedname
|
|
72
|
+
s = self.name
|
|
73
|
+
s += (" " + self.entity_type.capitalize) if self.includetypeinname?
|
|
74
|
+
return s
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# subclasses should override to make sure the parent is appropriate for this child
|
|
78
|
+
# !! could move this to the tree structure
|
|
79
|
+
def is_valid_parent?(pa)
|
|
80
|
+
# !! need to figure out how to do this with regexp
|
|
81
|
+
unless self.invalid_parent_types.include?(pa.entity_type)
|
|
82
|
+
s = self.valid_parent_types
|
|
83
|
+
if s.include?('*')
|
|
84
|
+
return true
|
|
85
|
+
else
|
|
86
|
+
return s.include?(pa.entity_type)
|
|
87
|
+
end
|
|
88
|
+
else
|
|
89
|
+
return false
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# check for validity, with adding error checking this would be the appropriate
|
|
94
|
+
# place to add exceptions, etc. (to allow recovery);
|
|
95
|
+
# !! could move this to the tree structure
|
|
96
|
+
def is_valid_child?(ch)
|
|
97
|
+
# !! need to figure out how to do this with regexp
|
|
98
|
+
unless self.invalid_child_types.include?(ch.entity_type)
|
|
99
|
+
s = self.valid_child_types
|
|
100
|
+
if s.include?('*') || s.include?(ch.entity_type)
|
|
101
|
+
return ch.is_valid_parent?(self)
|
|
102
|
+
else
|
|
103
|
+
return false
|
|
104
|
+
end
|
|
105
|
+
else
|
|
106
|
+
return false
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# default parent types (subclasses should override as needed) - default specifies any
|
|
111
|
+
def valid_parent_types
|
|
112
|
+
'*'
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# default child types (subclasses should override as needed) - default specifies any
|
|
116
|
+
def valid_child_types
|
|
117
|
+
'*'
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# default invalid child types
|
|
121
|
+
def invalid_child_types
|
|
122
|
+
""
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# default invalid parent types
|
|
126
|
+
def invalid_parent_types
|
|
127
|
+
""
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# don't want to index off of place name? - could have multiple entries w. similar names
|
|
131
|
+
# create an index off of the place name, alone; will later create on off of the
|
|
132
|
+
# geo locations *additionally*
|
|
133
|
+
# index({ place_name: 1 }, { unique: false, name: "name_index"})
|
|
134
|
+
|
|
135
|
+
# ** would like to hide these interfaces in a private/protected manner, not clear
|
|
136
|
+
# ** how to do this in Ruby - doesn't have 'const' access similar to c++, etc.
|
|
137
|
+
protected
|
|
138
|
+
|
|
139
|
+
# include type in the name of this place (i.e. 'Washington state')
|
|
140
|
+
def includetypeinname?
|
|
141
|
+
return false
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# set the entity type (each subclass should override)
|
|
145
|
+
def get_entity_type
|
|
146
|
+
'Node'
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def validate_child(ch)
|
|
150
|
+
raise InvalidChild, "Cannot add #{ch.name} (#{ch.entity_type}) as child of #{self.name} (#{self.entity_type})" if !is_valid_child?(ch)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def validate_parent(pa)
|
|
154
|
+
raise InvalidParent, "Cannot add #{pa.name} (#{pa.enity_type}) as parent of #{self.name} (#{self.entity_type})" if !is_valid_parent?(pa)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
end # class Node
|
|
158
|
+
end # module Taxonomite
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# taxonomite_configuration.rb
|
|
2
|
+
# hold configuration parameters for the library
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
module Taxonomite
|
|
6
|
+
class Configuration
|
|
7
|
+
# future versions may extend to using different tree models
|
|
8
|
+
# - for now uses a custom tree model (:self)
|
|
9
|
+
attr_accessor :use_tree_model
|
|
10
|
+
|
|
11
|
+
protected
|
|
12
|
+
# initialize
|
|
13
|
+
def initialize
|
|
14
|
+
@use_tree_model = :self
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# taxonomite_tree.rb
|
|
2
|
+
|
|
3
|
+
require 'active_support/concern'
|
|
4
|
+
require 'taxonomite/exceptions'
|
|
5
|
+
|
|
6
|
+
module Taxonomite
|
|
7
|
+
module Tree
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
# this is what will be included in the class that uses this
|
|
11
|
+
#
|
|
12
|
+
included do
|
|
13
|
+
has_many :children, :class_name => self.name, :inverse_of => :parent, :before_add => :validate_child!
|
|
14
|
+
belongs_to :parent, :class_name => self.name, :inverse_of => :children
|
|
15
|
+
|
|
16
|
+
# this allows for faster database searchign when adding/checking validity
|
|
17
|
+
# !! not implemented field :parent_ids, :type => Array, :default => []
|
|
18
|
+
|
|
19
|
+
class_eval "def base_class; ::#{self.name}; end"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
module ClassMethods
|
|
23
|
+
# find all roots for this tree (Mongoid query)
|
|
24
|
+
def roots
|
|
25
|
+
where(:parent_id => nil)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# find all leaves fro this tree (Mongoid query)
|
|
29
|
+
def leaves
|
|
30
|
+
where(:_id.nin => only(:parent_id).collect(&:parent_id))
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# is this a root node?
|
|
35
|
+
def root?
|
|
36
|
+
self.parent == nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# is this a leaf?
|
|
40
|
+
def leaf?
|
|
41
|
+
self.children.empty?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# find the root of this node
|
|
45
|
+
def root
|
|
46
|
+
self.root? ? self : self.parent.root
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# is the object an ancestor of nd?
|
|
50
|
+
def is_ancestor?(nd)
|
|
51
|
+
self.root? ? false : (self.parent == nd || self.parent.is_ancestor?(nd))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# is this an ancestor of another node?
|
|
55
|
+
def ancestor_of?(nd)
|
|
56
|
+
nd.is_ancestor?(self)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# is this a descendant of nd?
|
|
60
|
+
def descendant_of?(nd)
|
|
61
|
+
(self == nd) ? true : is_ancestor?(nd)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# return a chainable Mongoid criteria to get all descendants
|
|
65
|
+
def descendants
|
|
66
|
+
doc = Array.new
|
|
67
|
+
children.each do |c|
|
|
68
|
+
doc += [c] + c.descendants
|
|
69
|
+
end
|
|
70
|
+
return doc
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# return a chainable Mongoid criteria to get self + all descendants
|
|
74
|
+
def self_and_descendants
|
|
75
|
+
[self] + self.descendants
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# nullifies all children's parent id (cuts link)
|
|
79
|
+
def nullify_children
|
|
80
|
+
children.each do |c|
|
|
81
|
+
c.parent = nil
|
|
82
|
+
c.save
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# delete children; this *removes all of the children fromt he data base (and ensuing)
|
|
87
|
+
def destroy_children
|
|
88
|
+
children.destroy_all
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# move all children to the parent node
|
|
92
|
+
def move_children_to_parent
|
|
93
|
+
children.each do |c|
|
|
94
|
+
self.parent.children << c
|
|
95
|
+
c.parent = self.parent # is this necessary?
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# perform validation on whether this child is an acceptable child or not?
|
|
100
|
+
# the base_class must have a method 'is_valid_child?' to implement domain logic there
|
|
101
|
+
def validate_child!(ch)
|
|
102
|
+
raise InvalidChild "Attempted to add nil child to #{self}" if (ch == nil)
|
|
103
|
+
raise CircularRelation, "Circular relationship adding #{ch} to #{self}" if self.descendant_of?(ch)
|
|
104
|
+
if base_class.method_defined? :is_valid_child?
|
|
105
|
+
self.validate_child(ch) # this should throw an error if not valid
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# perform validation on whether this parent is an acceptable parent or not?
|
|
110
|
+
# the base_class should have a method 'is_valid_parent?' to invoke domain logic
|
|
111
|
+
# calls - invalid_parent_error(ch), if it exists, if the parent is invalid (for error handling)
|
|
112
|
+
# def validate_parent(pa)
|
|
113
|
+
# if (pa == nil) || (pa == self)
|
|
114
|
+
# self.invalid_parent_error(pa)
|
|
115
|
+
# else
|
|
116
|
+
# while par != nil do
|
|
117
|
+
# unless self.is_valid_parent(pa)
|
|
118
|
+
# end
|
|
119
|
+
# end
|
|
120
|
+
# end
|
|
121
|
+
# end
|
|
122
|
+
|
|
123
|
+
# to do - find a way to add a Mongoid criteria to return all of the nodes for this object
|
|
124
|
+
# descendants
|
|
125
|
+
# descendants_and_self
|
|
126
|
+
# ancestors
|
|
127
|
+
# ancestors_and_self
|
|
128
|
+
|
|
129
|
+
end # Tree
|
|
130
|
+
end # Taxonomite
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'rails_helper'
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
require 'taxonomite/node'
|
|
4
|
+
|
|
5
|
+
module Taxonomite
|
|
6
|
+
|
|
7
|
+
RSpec.describe TaxonomiteController, type: :controller do
|
|
8
|
+
|
|
9
|
+
before :all do
|
|
10
|
+
# populate the database prior to trying index/get
|
|
11
|
+
@sample = FactoryGirl.create(:taxonomite_node)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe "GET #index" do
|
|
15
|
+
it "returns http success" do
|
|
16
|
+
get :index
|
|
17
|
+
expect(response).to have_http_status(:success)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# really just test that an invalid id (=1) fails
|
|
22
|
+
describe "GET #show" do
|
|
23
|
+
it "returns http success" do
|
|
24
|
+
get :show, :id => @sample.id
|
|
25
|
+
expect(response).to have_http_status(:success)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
== README
|
|
2
|
+
|
|
3
|
+
This README would normally document whatever steps are necessary to get the
|
|
4
|
+
application up and running.
|
|
5
|
+
|
|
6
|
+
Things you may want to cover:
|
|
7
|
+
|
|
8
|
+
* Ruby version
|
|
9
|
+
|
|
10
|
+
* System dependencies
|
|
11
|
+
|
|
12
|
+
* Configuration
|
|
13
|
+
|
|
14
|
+
* Database creation
|
|
15
|
+
|
|
16
|
+
* Database initialization
|
|
17
|
+
|
|
18
|
+
* How to run the test suite
|
|
19
|
+
|
|
20
|
+
* Services (job queues, cache servers, search engines, etc.)
|
|
21
|
+
|
|
22
|
+
* Deployment instructions
|
|
23
|
+
|
|
24
|
+
* ...
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
Please feel free to use a different markup language if you do not plan to run
|
|
28
|
+
<tt>rake doc:app</tt>.
|