taxonomite 0.1.0 → 0.2.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 +4 -4
- data/lib/taxonomite/exceptions.rb +42 -3
- data/lib/taxonomite/node.rb +104 -108
- data/lib/taxonomite/taxonomite_configuration.rb +57 -4
- data/lib/taxonomite/taxonomy.rb +144 -0
- data/lib/taxonomite/tree.rb +67 -27
- data/lib/taxonomite/version.rb +1 -1
- data/spec/dummy/app/assets/stylesheets/application.css +2 -2
- data/spec/dummy/app/controllers/taxonomite_controller.rb +3 -3
- data/spec/dummy/app/views/layouts/application.html.erb +2 -2
- data/spec/dummy/app/views/taxonomite/_node.html.erb +1 -1
- data/spec/dummy/app/views/taxonomite/edit.html.erb +5 -10
- data/spec/dummy/app/views/taxonomite/index.html.erb +1 -1
- data/spec/dummy/app/views/taxonomite/new.html.erb +0 -4
- data/spec/dummy/app/views/taxonomite/show.html.erb +4 -5
- data/spec/dummy/config/application.rb +0 -3
- data/spec/dummy/config/routes.rb +3 -3
- data/spec/dummy/log/development.log +4003 -0
- data/spec/dummy/log/test.log +9906 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/4vdQYQIAhYwrL0QVMvfa54lhQG6llPr5uoxQk1ouaM8.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/7ucWh-CcCYtuzX4nESCM8wP6Oo-dugQ9oWvKpJBfGXc.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/8QLYXmTDdck6lEnExFXVUgVd0VmTjBqiwgkBUaI0mfI.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/DHPriyPTqAKI-VE0nnqxYbNlqmqdd3RgpylNEg22ZEU.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/DmmfrCpXtt74Hr6NO54lxyOCDv6klnDyBqeDFR7oDU8.cache +2 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/H6fMQ0CH2SZoDE1LEjfMDSDeMVjPnKKABOQsu-cwfTA.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/H_sT8XqVfJtCW-AC1MLG9rXc7bZgFDHNDRNIyiHQN80.cache +2 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/HcyLAwu9aQkSXaKmo86TjBGj5kCqEEqEe0PnSGBuHFA.cache +2 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/JuGo9fAopWqzVuLTjAj8tdAP316MxbMVXhPB88frxSE.cache +2 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/OhXYCvG-a_22n0rNMEUgOaqjiz2CPC5MA9Zo-ElvVr0.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/PzDdhgDidlsbrgnnvqY64gLSrP2kaVIqwUFPnd-h-q0.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/SAk_VBRi2O1BhT-LTDYCY4zy6rntLx1RDoCXZmA9dqA.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/WmFpKlhm7tR4kHRsyBRQqupfX_VtT46yYZo6ysyujVA.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/XjFN6x13TIQd-Y0w5iv5yiUaeTXVFTUJZ8fi3-Zf9nY.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/YR7u7z93aL2h6rPUUFI6nZXzmR57bMMQ0GfS0NAwnTo.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/a2YAOseOUMHfaabIoOQHYrg_L8Zr-Dmf2GsQnMkW-Os.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/aCaKUaA2zFHYb85d3HsEaLzqqcySgu1RNprCoAsVud0.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/asZJOwOxXbTo1DcWgp7Bepytus9KbEtoyjSk5ZGRpCg.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/ekIbgUIaZdrL5rxYSxMCKZcTjHANQi6KjM56x166Q2U.cache +2 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/gVc7mlhXRPZKcR593Z7dQrsKdJCjbg3FuDacsK6KC3Q.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/hZi1k6tpxxCGYxRe7zY74ItcOI8gZrREOpGuA8JSpGg.cache +3 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/nJmf-nKlKYmmb_4KPWVHI6UtkeXBMYHiMt4fHuhL3NI.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/oktKvwt0h2v1ixmHfahaBJKnWt_3nTIpuilb35Nk5IU.cache +1 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/pEhaat2KBd5SrT7szC_8R1_6hK17FTpvoRFkmCRSD3M.cache +2 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/tJb-RVxVPbizlMUsIVfCaQ-Ly2e8fNxjJMxUXCyqO2c.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/xhy8Vy-n9RUeqeVICYHCfPXkDfYOtv_dXeuIJEwK_8k.cache +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/zTELlyyx2TMwFIUjf_8PuaPdHIH7k6AKMjaUTaV7VwY.cache +1 -0
- data/spec/factories/taxonomy_taxon.rb +25 -14
- data/spec/models/taxonomite/hierarchy_spec.rb +113 -20
- data/spec/models/taxonomite/model_spec.rb +0 -5
- data/spec/models/taxonomite/tree_spec.rb +120 -10
- data/spec/models/taxonomite/zoology.rb +14 -13
- data/spec/spec_helper.rb +0 -5
- metadata +88 -32
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3e1a9457110c23a02d4d8f75753b972457abb2d1
|
|
4
|
+
data.tar.gz: dc4cda724409ded545dd499b0ef7d70676b7fa88
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0e680ba41a6ac6e7ea6e5bda6bb0bfc311779203951c7e915b8b6194d5d6f45b222e09aa7b4cd71d51c678143a6c287f5aed5c70f8337e2d3f2b50f2236d528b
|
|
7
|
+
data.tar.gz: b09f729c8a5b7addf448dffa31f96f462f87231bc13288aab14ae6be210f96b65f0a3fdb5c6cbe223d2197f629eec21c5f2ac9d1957f7b822701a3b7fc27e218
|
|
@@ -1,7 +1,46 @@
|
|
|
1
1
|
# taxonomite/exceptions.rb
|
|
2
2
|
|
|
3
3
|
module Taxonomite
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
|
|
5
|
+
module CreateClassMethod
|
|
6
|
+
def create(parent, child)
|
|
7
|
+
new("", parent, child)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class InvalidChild < RuntimeError
|
|
12
|
+
extend CreateClassMethod
|
|
13
|
+
|
|
14
|
+
def initialize(msg = "Invalid attempt to add Taxonomite::Node.", parent = nil, child = nil)
|
|
15
|
+
msg = "Cannot add " +
|
|
16
|
+
( child.nil? ? "nil child" : "#{child.name} (#{child.entity_type})" ) +
|
|
17
|
+
" as child of #{parent.name} (#{parent.entity_type})" unless parent.nil?
|
|
18
|
+
super(msg)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class InvalidParent < RuntimeError
|
|
23
|
+
extend CreateClassMethod
|
|
24
|
+
|
|
25
|
+
def initialize(msg = "Invalid attempt to add Taxonomite::Node.", parent = nil, child = nil)
|
|
26
|
+
unless (parent.nil? && child.nil?)
|
|
27
|
+
msg = "Cannot add " +
|
|
28
|
+
( child.nil? ? "nil child" : "#{child.name} (#{child.entity_type})" ) +
|
|
29
|
+
" as child of #{parent.name} (#{parent.entity_type})" unless parent.nil?
|
|
30
|
+
end
|
|
31
|
+
super(msg)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class CircularRelation < RuntimeError
|
|
36
|
+
extend CreateClassMethod
|
|
37
|
+
|
|
38
|
+
def initialize(msg = "Circular relation in attempt to add Taxonomite::Node.", parent = nil, child = nil)
|
|
39
|
+
msg = "Circular reference in adding " +
|
|
40
|
+
( child.nil? ? "nil child" : "#{child.name} (#{child.entity_type})" ) +
|
|
41
|
+
" as child of #{parent.name} (#{parent.entity_type})" unless parent.nil?
|
|
42
|
+
super(msg)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
7
46
|
end # taxonomite
|
data/lib/taxonomite/node.rb
CHANGED
|
@@ -1,158 +1,154 @@
|
|
|
1
1
|
require 'taxonomite/taxonomite_configuration'
|
|
2
2
|
require 'taxonomite/tree'
|
|
3
|
+
require 'mongoid'
|
|
3
4
|
|
|
4
5
|
module Taxonomite
|
|
6
|
+
##
|
|
7
|
+
# Class which defines a node within a tree hierarchy. Validation on addition
|
|
8
|
+
# of children and parents does occur in this class to provide for class specific
|
|
9
|
+
# validation in subclasses. That said, enforcing a taxonomy (rules about how the
|
|
10
|
+
# hierarchy is constructed) falls to the Taxonomite::Taxonomy class which is
|
|
11
|
+
# used to join parents and children according to specified rules. Thus, if
|
|
12
|
+
# someone does Obj.children << node -- no special validation will occur, other
|
|
13
|
+
# than that provided within this class or subclasses via is_valid_child? and
|
|
14
|
+
# is_valid_parent?
|
|
15
|
+
#
|
|
5
16
|
class Node
|
|
6
|
-
|
|
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
|
|
17
|
+
extend Taxonomite::ConfiguredGlobally
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
# handle configurable options - this is essentially how the tree is stored.
|
|
20
|
+
case Node.config.use_tree_model
|
|
21
|
+
when :self
|
|
22
|
+
include ::Mongoid::Document
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
end
|
|
24
|
+
# make this protected such that other objects cannot access
|
|
25
|
+
include Taxonomite::Tree
|
|
29
26
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
end
|
|
27
|
+
# configure the way the tree behaves
|
|
28
|
+
before_destroy :nullify_children
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
|
|
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}'
|
|
30
|
+
else
|
|
31
|
+
raise RuntimeError, 'Invalid option for Node.config.use_tree_model: #{Node.config.use_tree_model}'
|
|
43
32
|
end
|
|
44
33
|
|
|
45
34
|
field :name, type: String # name of this particular object (not really node)
|
|
46
|
-
field :description, type: String # description of this item
|
|
47
35
|
field :entity_type, type: String, default: ->{ self.get_entity_type } # type of entity (i.e. state, county, city, etc.)
|
|
48
36
|
|
|
49
37
|
belongs_to :owner, polymorphic: true # this is the associated object
|
|
50
38
|
|
|
51
|
-
##
|
|
52
|
-
|
|
53
|
-
#
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return res
|
|
65
|
-
end
|
|
39
|
+
##
|
|
40
|
+
# evaluate a method on the owner of this node (if present). If an owner is
|
|
41
|
+
# not present, then the method is evaluated on this object. In either case
|
|
42
|
+
# a check is made to ensure that the object will respond_to? the method call.
|
|
43
|
+
# If the owner exists but does not respond to the method, then the method is
|
|
44
|
+
# tried on this node object in similar fashion.
|
|
45
|
+
# !!! SHOULD THIS JUST OVERRIDE instance_eval ??
|
|
46
|
+
# @param [Method] m method to call
|
|
47
|
+
# @return [] the result of the method call, or nil if unable to evaluate
|
|
48
|
+
def evaluate(m)
|
|
49
|
+
return self.owner.instance_eval(m) if self.owner != nil && self.owner.respond_to?(m)
|
|
50
|
+
return self.instance_eval(m) if self.respond_to?(m)
|
|
51
|
+
nil
|
|
66
52
|
end
|
|
67
53
|
|
|
68
|
-
##
|
|
69
|
-
|
|
54
|
+
##
|
|
70
55
|
# typeify name w entity (i.e. 'Washington state' vs. 'Seattle')
|
|
56
|
+
# @return [String] the typeified name
|
|
71
57
|
def typeifiedname
|
|
72
58
|
s = self.name
|
|
73
59
|
s += (" " + self.entity_type.capitalize) if self.includetypeinname?
|
|
74
60
|
return s
|
|
75
61
|
end
|
|
76
62
|
|
|
77
|
-
|
|
78
|
-
#
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
else
|
|
86
|
-
return s.include?(pa.entity_type)
|
|
87
|
-
end
|
|
88
|
-
else
|
|
89
|
-
return false
|
|
90
|
-
end
|
|
63
|
+
##
|
|
64
|
+
# determine whether child is a valid child based upon class evalution
|
|
65
|
+
# (outside of a taxonomy). Default is always true - subclasses should
|
|
66
|
+
# override if validation outside of a separate taxonomy class is desired.
|
|
67
|
+
# @param [Taxonomite::Node] child child to evaluate
|
|
68
|
+
# @return [Boolean] default is true
|
|
69
|
+
def is_valid_child?(child)
|
|
70
|
+
return true
|
|
91
71
|
end
|
|
92
72
|
|
|
93
|
-
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return ch.is_valid_parent?(self)
|
|
102
|
-
else
|
|
103
|
-
return false
|
|
104
|
-
end
|
|
105
|
-
else
|
|
106
|
-
return false
|
|
107
|
-
end
|
|
73
|
+
##
|
|
74
|
+
# determine whether parent is a valid parent based upon class evalution
|
|
75
|
+
# (outside of a taxonomy). Default is always true - subclasses should
|
|
76
|
+
# override if validation outside of a separate taxonomy class is desired.
|
|
77
|
+
# @param [Taxonomite::Node] parent parent to evaluate
|
|
78
|
+
# @return [Boolean] default is true
|
|
79
|
+
def is_valid_parent?(child)
|
|
80
|
+
return true
|
|
108
81
|
end
|
|
109
82
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
83
|
+
##
|
|
84
|
+
# add a child to this object; default is that each parent may have many children;
|
|
85
|
+
# this will validate the child using thetaxonomy object passed in to the field.
|
|
86
|
+
# @param [Taxonomite::Node] child the node to evaluate
|
|
87
|
+
def add_child(child)
|
|
88
|
+
self.children << child
|
|
113
89
|
end
|
|
114
90
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
91
|
+
##
|
|
92
|
+
# add a parent for this object (default is that each object can have only one
|
|
93
|
+
# parent).
|
|
94
|
+
# this will validate the child using the taxonomy object passed in to the field.
|
|
95
|
+
# @param [Taxonomite::Node] parent the node to evaluate
|
|
96
|
+
# @param [Taxonomite::Taxonomy] taxonomy to use to validate the hierarchy
|
|
97
|
+
def add_parent(parent)
|
|
98
|
+
parent.add_child(self)
|
|
118
99
|
end
|
|
119
100
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
101
|
+
##
|
|
102
|
+
# remove a child from this node.
|
|
103
|
+
# @param [Taxonomite::Node] child node to remove
|
|
104
|
+
def remove_child(child)
|
|
105
|
+
self.children.delete(child)
|
|
123
106
|
end
|
|
124
107
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
108
|
+
##
|
|
109
|
+
# remove the parent from this node. This should cause a reciprocal removal
|
|
110
|
+
# of self from the parent's children
|
|
111
|
+
def remove_parent
|
|
112
|
+
self.parent.remove_child(self) unless self.parent.nil?
|
|
128
113
|
end
|
|
129
114
|
|
|
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
115
|
protected
|
|
138
|
-
|
|
116
|
+
##
|
|
139
117
|
# include type in the name of this place (i.e. 'Washington state')
|
|
118
|
+
# @return [Boolean]
|
|
140
119
|
def includetypeinname?
|
|
141
120
|
return false
|
|
142
121
|
end
|
|
143
122
|
|
|
144
|
-
|
|
123
|
+
##
|
|
124
|
+
# access the entity type for this Object
|
|
125
|
+
# @return [String]
|
|
145
126
|
def get_entity_type
|
|
146
|
-
'
|
|
127
|
+
'node'
|
|
147
128
|
end
|
|
148
129
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
130
|
+
private
|
|
131
|
+
##
|
|
132
|
+
# determine whether the child is valid for this object; there are two
|
|
133
|
+
# layers to validation 1) provided by this method is_valid_parent? (and subclasses which override
|
|
134
|
+
# is_valid_parent?).
|
|
135
|
+
# @param [Taxonomite::Node] child the parent to validate
|
|
136
|
+
# @return [Boolean] whether validation was successful
|
|
137
|
+
def validate_child(child)
|
|
138
|
+
raise InvalidParent.create(self, child) unless (!child.nil? && is_valid_child?(child))
|
|
139
|
+
true
|
|
140
|
+
end
|
|
152
141
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
142
|
+
##
|
|
143
|
+
# determine whether the parent is valid for this object. See description of
|
|
144
|
+
# validate_child for more detail. This method calls validate_child to perform
|
|
145
|
+
# the actual validation.
|
|
146
|
+
# @param [Taxonomite::Node] parent the parent to validate
|
|
147
|
+
# @return [Boolean] whether validation was successful
|
|
148
|
+
def validate_parent(parent)
|
|
149
|
+
raise InvalidParent.create(parent, self) unless (!parent.nil? && is_valid_parent?(parent))
|
|
150
|
+
parent.validate_child(self)
|
|
151
|
+
end
|
|
156
152
|
|
|
157
153
|
end # class Node
|
|
158
154
|
end # module Taxonomite
|
|
@@ -3,15 +3,68 @@
|
|
|
3
3
|
#
|
|
4
4
|
|
|
5
5
|
module Taxonomite
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
# Allows the class to be configured on its very own, such that it
|
|
9
|
+
# creates its own @configuration class object. Thus if ClassB inherited
|
|
10
|
+
# from ClassA and ClassA extends Configurable, then both could have their own
|
|
11
|
+
# configuration options? Any class that extends this must declare a
|
|
12
|
+
# create_configuration class method.
|
|
13
|
+
module Configurable
|
|
14
|
+
class << self
|
|
15
|
+
attr_writer :configuration
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def config
|
|
19
|
+
@configuration ||= create_configuration
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def configure
|
|
23
|
+
yield(config)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def reset
|
|
27
|
+
@configuration = create_configuration
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# The configuration class for Taxonomite gem. All classes which are configured via
|
|
34
|
+
# this mechanism should extend Taxonomite::Configured (below).
|
|
6
35
|
class Configuration
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
36
|
+
extend Taxonomite::Configurable
|
|
37
|
+
|
|
38
|
+
def self.create_configuration
|
|
39
|
+
Taxonomite::Configuration.new
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# future versions may extend to using different tree models
|
|
43
|
+
# - for now uses a custom tree model (:self)
|
|
44
|
+
attr_accessor :use_tree_model
|
|
45
|
+
attr_accessor :default_taxonomy_require_both
|
|
10
46
|
|
|
11
47
|
protected
|
|
12
|
-
|
|
48
|
+
##
|
|
49
|
+
# initialize object variables to defaults
|
|
13
50
|
def initialize
|
|
14
51
|
@use_tree_model = :self
|
|
52
|
+
@default_taxonomy_require_both = true
|
|
15
53
|
end
|
|
16
54
|
end
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
##
|
|
58
|
+
# All classes which are configured using Taxonomite::Configuration should
|
|
59
|
+
# extend this module, such that they can access the configuration via their
|
|
60
|
+
# own class methods (i.e. ClassA.config.option)
|
|
61
|
+
module ConfiguredGlobally
|
|
62
|
+
def config
|
|
63
|
+
Taxonomite::Configuration.config
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def reset
|
|
67
|
+
Taxonomite::Configuration.reset
|
|
68
|
+
end
|
|
69
|
+
end
|
|
17
70
|
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
require 'taxonomite/taxonomite_configuration'
|
|
2
|
+
require 'taxonomite/tree'
|
|
3
|
+
|
|
4
|
+
module Taxonomite
|
|
5
|
+
##
|
|
6
|
+
# Class which enforces a particular hierarchy among objects.
|
|
7
|
+
class Taxonomy
|
|
8
|
+
include Mongoid::Document
|
|
9
|
+
extend Taxonomite::ConfiguredGlobally
|
|
10
|
+
|
|
11
|
+
# ? whether this needs to be stored in the database or not, as most
|
|
12
|
+
# of the time would be instanciated by an application
|
|
13
|
+
field :down_taxonomy, type: Hash, default: ->{ Hash.new("*") }
|
|
14
|
+
field :up_taxonomy, type: Hash, default: ->{ Hash.new("*") }
|
|
15
|
+
field :require_both, type: Boolean, default: ->{ Taxonomite::Taxonomy.config.default_taxonomy_require_both }
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# verify according to the down-looking taxonomy hash.
|
|
19
|
+
# @param [Taxonomite::Node] parent the proposed parent node
|
|
20
|
+
# @param [Taxonomite::Node] child the proposed child node
|
|
21
|
+
# @return [Boolean] whether the child appropriate for the parent, default true
|
|
22
|
+
def is_valid_down_relation?(parent, child)
|
|
23
|
+
[self.down_taxonomy[parent.entity_type]].map { |t| return true if [child.entity_type, "*"].include?(t) }
|
|
24
|
+
false
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# verify according to the down-looking taxonomy hash.
|
|
29
|
+
# @param [Taxonomite::Node] parent the proposed parent node
|
|
30
|
+
# @param [Taxonomite::Node] child the proposed child node
|
|
31
|
+
# @return [Boolean] whether the child appropriate for the parent, default true
|
|
32
|
+
def is_valid_up_relation?(parent, child)
|
|
33
|
+
[self.up_taxonomy[child.entity_type]].map { |t| return true if [parent.entity_type, "*"].include?(t) }
|
|
34
|
+
false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# determine whether the parent is a valid parent for the child. If no
|
|
39
|
+
# taxonomy is defined (i.e. the hashes are empty) then default is to return
|
|
40
|
+
# true. Requires that *both* an updward and a downward relation are present if
|
|
41
|
+
# the require_both flag is set (default is true -- this can be set via Taxonomite::Configuration).
|
|
42
|
+
# This flag can also be passed into the method here.
|
|
43
|
+
# @param [Taxonomite::Node] parent the proposed parent node
|
|
44
|
+
# @param [Taxonomite::Node] child the proposed child node
|
|
45
|
+
# @param [Boolean] require_both to require both downward and upward match, or just one or the other
|
|
46
|
+
# @return [Boolean] whether the child appropriate for the parent, default true
|
|
47
|
+
def is_valid_relation?(parent, child, require_both = self.require_both)
|
|
48
|
+
# depending upon the
|
|
49
|
+
require_both ? is_valid_down_relation?(parent, child) && is_valid_up_relation?(parent, child)
|
|
50
|
+
: is_valid_down_relation?(parent, child) || is_valid_up_relation?(parent, child)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
##
|
|
54
|
+
# access the appropriate parent entity_types for a particular child or child entity_type
|
|
55
|
+
# @param [Taxonomy::Node, String] child the child object or entity_type string
|
|
56
|
+
# @return [Array] an array of strings which are the valid parent types for the child
|
|
57
|
+
def valid_parent_types(child)
|
|
58
|
+
# could be a node object, or maybe a string
|
|
59
|
+
str = child.respond_to?(:entity_type) ? child.entity_type : child
|
|
60
|
+
self.up_taxonomy[str]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
##
|
|
64
|
+
# access the appropriate child entity_types for a particular parent or parent entity_type
|
|
65
|
+
# @param [Taxonomy::Node, String] parent the parent object or entity_type string
|
|
66
|
+
# @return [Array] an array of strings which are the valid child types for the child
|
|
67
|
+
def valid_child_types(parent)
|
|
68
|
+
# could be a node object, or maybe a string
|
|
69
|
+
str = parent.respond_to?(:entity_type) ? parent.entity_type : child
|
|
70
|
+
self.down_taxonomy[str]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# ##
|
|
74
|
+
# # Is this the direct owner of the node passed. This allows for auto-organizing
|
|
75
|
+
# # hierarchies. Sublcasses should override this method. Defaults to false - hence
|
|
76
|
+
# # no structure.
|
|
77
|
+
# # @param [Taxonomite::Node] node node in question
|
|
78
|
+
# # @return [Boolean] whether self should directly own node as a child, default is false
|
|
79
|
+
# def contains?(node)
|
|
80
|
+
# false
|
|
81
|
+
# end
|
|
82
|
+
|
|
83
|
+
##
|
|
84
|
+
# Find the direct owner of a node within the tree. Returns nil if no direct
|
|
85
|
+
# owner exists within the tree starting at root self. This works down the
|
|
86
|
+
# tree (rather than up.) If root is nil it simply trys all of the Node objects
|
|
87
|
+
# which match the appropriate parent entity_type for node. The default simply
|
|
88
|
+
# finds the first available valid parent depending upon the search method employed.
|
|
89
|
+
# @param [Taxonomite::Node] node the node to evaluate
|
|
90
|
+
# @param [Taxonomite::Node] root the root of the tree to evaluate; if nil will search for the parents of the node first
|
|
91
|
+
# @return [Taxonomite::Node] the appropriate node or nil if none found
|
|
92
|
+
def find_owner(node, root = nil)
|
|
93
|
+
valid_parent_types(node).presence.each do |t|
|
|
94
|
+
getallnodes = lambda { |v| v == '*' ? Taxonomite::Node.find() : Taxonomite::Node.find_by(:entity_type => t) }
|
|
95
|
+
getrootnodes = lambda { |v| v == '*' ? root.self_and_descendants : root.self_and_descendants.find_by(:entity_type => t) }
|
|
96
|
+
( root.nil? ? getallnodes.call(t) : getrootnodes.call(t) ).each { |n| return n if is_valid_relation(n,node) }
|
|
97
|
+
end
|
|
98
|
+
nil
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
##
|
|
102
|
+
# see if this node belongs directly under a particular parent; this allows for
|
|
103
|
+
# assignment within a hierarchy. Subclasses should override to provide better
|
|
104
|
+
# functionality. Default behavior asks the node if it contains(self).
|
|
105
|
+
# @param [Taxonomite::Node] child the node to evaluate
|
|
106
|
+
# @param [Taxonomite::Parent] parent the parent node to evaluate for the child
|
|
107
|
+
def belongs_under?(parent, child)
|
|
108
|
+
self.find_owner(child, parent) != nil
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# ##
|
|
112
|
+
# # see if this node belongs directly to another node (i.e. would appropriately)
|
|
113
|
+
# # be added as a child of the node. Default returns false. Convention to get
|
|
114
|
+
# # self-organizing hierarchy to work is for belongs_under to return true when
|
|
115
|
+
# # belongs_directly_to is true as well. Default behaviour is to ask the node if
|
|
116
|
+
# # it directly_contains(self).
|
|
117
|
+
# # @param [Taxonomite::Node] node to evaluate
|
|
118
|
+
# # @return [Boolean] whether node contains this object (self)
|
|
119
|
+
# def belongs_directly_to(node)
|
|
120
|
+
# node.contains?(self)
|
|
121
|
+
# end
|
|
122
|
+
|
|
123
|
+
##
|
|
124
|
+
# try to add a child to a parent, with validation by this Taxonomy
|
|
125
|
+
# @param [Taxonomite::Node] parent the parent node
|
|
126
|
+
# @param [Taxonomite::Node] child the child node
|
|
127
|
+
def add(parent, child)
|
|
128
|
+
raise InvalidChild::create(parent,child) unless self.is_valid_relation?(parent, child)
|
|
129
|
+
parent.add_child(child)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
protected
|
|
133
|
+
|
|
134
|
+
# ##
|
|
135
|
+
# # return the default initial hash for this object, default is empty.
|
|
136
|
+
# # Subclasses should override to provide a default hash. A hierarchy could
|
|
137
|
+
# # be createed with this method alone.
|
|
138
|
+
# # @return [Hash] the default (initialized) taxonomy_hash values
|
|
139
|
+
# def initial_hash
|
|
140
|
+
# {}
|
|
141
|
+
# end
|
|
142
|
+
|
|
143
|
+
end # class Taxonomy
|
|
144
|
+
end # module Taxonomite
|