taxonomite 0.1.0 → 0.2.3

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