taxonomite 0.1.0 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|