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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/lib/taxonomite/exceptions.rb +42 -3
  3. data/lib/taxonomite/node.rb +104 -108
  4. data/lib/taxonomite/taxonomite_configuration.rb +57 -4
  5. data/lib/taxonomite/taxonomy.rb +144 -0
  6. data/lib/taxonomite/tree.rb +67 -27
  7. data/lib/taxonomite/version.rb +1 -1
  8. data/spec/dummy/app/assets/stylesheets/application.css +2 -2
  9. data/spec/dummy/app/controllers/taxonomite_controller.rb +3 -3
  10. data/spec/dummy/app/views/layouts/application.html.erb +2 -2
  11. data/spec/dummy/app/views/taxonomite/_node.html.erb +1 -1
  12. data/spec/dummy/app/views/taxonomite/edit.html.erb +5 -10
  13. data/spec/dummy/app/views/taxonomite/index.html.erb +1 -1
  14. data/spec/dummy/app/views/taxonomite/new.html.erb +0 -4
  15. data/spec/dummy/app/views/taxonomite/show.html.erb +4 -5
  16. data/spec/dummy/config/application.rb +0 -3
  17. data/spec/dummy/config/routes.rb +3 -3
  18. data/spec/dummy/log/development.log +4003 -0
  19. data/spec/dummy/log/test.log +9906 -0
  20. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/4vdQYQIAhYwrL0QVMvfa54lhQG6llPr5uoxQk1ouaM8.cache +1 -0
  21. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/7ucWh-CcCYtuzX4nESCM8wP6Oo-dugQ9oWvKpJBfGXc.cache +0 -0
  22. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/8QLYXmTDdck6lEnExFXVUgVd0VmTjBqiwgkBUaI0mfI.cache +1 -0
  23. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/DHPriyPTqAKI-VE0nnqxYbNlqmqdd3RgpylNEg22ZEU.cache +0 -0
  24. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/DmmfrCpXtt74Hr6NO54lxyOCDv6klnDyBqeDFR7oDU8.cache +2 -0
  25. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/H6fMQ0CH2SZoDE1LEjfMDSDeMVjPnKKABOQsu-cwfTA.cache +1 -0
  26. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/H_sT8XqVfJtCW-AC1MLG9rXc7bZgFDHNDRNIyiHQN80.cache +2 -0
  27. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/HcyLAwu9aQkSXaKmo86TjBGj5kCqEEqEe0PnSGBuHFA.cache +2 -0
  28. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/JuGo9fAopWqzVuLTjAj8tdAP316MxbMVXhPB88frxSE.cache +2 -0
  29. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/OhXYCvG-a_22n0rNMEUgOaqjiz2CPC5MA9Zo-ElvVr0.cache +1 -0
  30. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/PzDdhgDidlsbrgnnvqY64gLSrP2kaVIqwUFPnd-h-q0.cache +1 -0
  31. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/SAk_VBRi2O1BhT-LTDYCY4zy6rntLx1RDoCXZmA9dqA.cache +0 -0
  32. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/WmFpKlhm7tR4kHRsyBRQqupfX_VtT46yYZo6ysyujVA.cache +1 -0
  33. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/XjFN6x13TIQd-Y0w5iv5yiUaeTXVFTUJZ8fi3-Zf9nY.cache +1 -0
  34. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/YR7u7z93aL2h6rPUUFI6nZXzmR57bMMQ0GfS0NAwnTo.cache +0 -0
  35. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/a2YAOseOUMHfaabIoOQHYrg_L8Zr-Dmf2GsQnMkW-Os.cache +0 -0
  36. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/aCaKUaA2zFHYb85d3HsEaLzqqcySgu1RNprCoAsVud0.cache +1 -0
  37. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/asZJOwOxXbTo1DcWgp7Bepytus9KbEtoyjSk5ZGRpCg.cache +1 -0
  38. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/ekIbgUIaZdrL5rxYSxMCKZcTjHANQi6KjM56x166Q2U.cache +2 -0
  39. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/gVc7mlhXRPZKcR593Z7dQrsKdJCjbg3FuDacsK6KC3Q.cache +0 -0
  40. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/hZi1k6tpxxCGYxRe7zY74ItcOI8gZrREOpGuA8JSpGg.cache +3 -0
  41. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/nJmf-nKlKYmmb_4KPWVHI6UtkeXBMYHiMt4fHuhL3NI.cache +1 -0
  42. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/oktKvwt0h2v1ixmHfahaBJKnWt_3nTIpuilb35Nk5IU.cache +1 -0
  43. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/pEhaat2KBd5SrT7szC_8R1_6hK17FTpvoRFkmCRSD3M.cache +2 -0
  44. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/tJb-RVxVPbizlMUsIVfCaQ-Ly2e8fNxjJMxUXCyqO2c.cache +0 -0
  45. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/xhy8Vy-n9RUeqeVICYHCfPXkDfYOtv_dXeuIJEwK_8k.cache +0 -0
  46. data/spec/dummy/tmp/cache/assets/development/sprockets/v3.0/zTELlyyx2TMwFIUjf_8PuaPdHIH7k6AKMjaUTaV7VwY.cache +1 -0
  47. data/spec/factories/taxonomy_taxon.rb +25 -14
  48. data/spec/models/taxonomite/hierarchy_spec.rb +113 -20
  49. data/spec/models/taxonomite/model_spec.rb +0 -5
  50. data/spec/models/taxonomite/tree_spec.rb +120 -10
  51. data/spec/models/taxonomite/zoology.rb +14 -13
  52. data/spec/spec_helper.rb +0 -5
  53. metadata +88 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 23b3b1dfb04a5bba6964518d64495f5ce765a401
4
- data.tar.gz: 8b97678d947fb2f0fea89b2298885bad97e01f61
3
+ metadata.gz: 3e1a9457110c23a02d4d8f75753b972457abb2d1
4
+ data.tar.gz: dc4cda724409ded545dd499b0ef7d70676b7fa88
5
5
  SHA512:
6
- metadata.gz: ab087ff6284c6b3208d6dc1be6b64aa6cd47c0464f5dbc9cd3abc4074f8fdfdd7c0685118350b98393bbb5f42534b7a042d47211927b778a36cf4b33427dfc34
7
- data.tar.gz: 263d4a6ca733fad72e9074e6c852f4d36da4a00324c85523671ffa8a246c97c3982b3a193f96c01a40a40a9031d79dc5f1407040f0a4f8c7ecba740d6c8883ce
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
- class InvalidChild < RuntimeError; end
5
- class InvalidParent < RuntimeError; end
6
- class CircularRelation < RuntimeError; end
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
@@ -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
- # 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
17
+ extend Taxonomite::ConfiguredGlobally
21
18
 
22
- def self.configure
23
- yield(config)
24
- end
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
- def self.reset
27
- @configuration = Taxonomite::Configuration.new
28
- end
24
+ # make this protected such that other objects cannot access
25
+ include Taxonomite::Tree
29
26
 
30
- def self.primary_key
31
- "_id"
32
- end
27
+ # configure the way the tree behaves
28
+ before_destroy :nullify_children
33
29
 
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}'
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
- ## 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
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
- ## Typeification methods
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
- # 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
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
- # 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
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
- # default parent types (subclasses should override as needed) - default specifies any
111
- def valid_parent_types
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
- # default child types (subclasses should override as needed) - default specifies any
116
- def valid_child_types
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
- # default invalid child types
121
- def invalid_child_types
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
- # default invalid parent types
126
- def invalid_parent_types
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
- # set the entity type (each subclass should override)
123
+ ##
124
+ # access the entity type for this Object
125
+ # @return [String]
145
126
  def get_entity_type
146
- 'Node'
127
+ 'node'
147
128
  end
148
129
 
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
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
- 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
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
- # future versions may extend to using different tree models
8
- # - for now uses a custom tree model (:self)
9
- attr_accessor :use_tree_model
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
- # initialize
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