fertile_forest 0.0.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 20188f6dbc2388fcfdbe3fe5b0759955a200bc36
4
- data.tar.gz: c2f95eb0e924a6c8a05ea1c33ab7d69cc63eb9cc
3
+ metadata.gz: e7fb8b7fe6a221ca6fa537152448626b03bfa453
4
+ data.tar.gz: 55da6e1a5fcb682e8e0a1cc2239df52e32bfb53f
5
5
  SHA512:
6
- metadata.gz: ea5732a9f44c1208526c7f6767c7b606905e97b40602f7b874c4a2cecbeb437afcc8aa244adaab5be8a79fbed1d8479ce357a0462a2bbac757ea10516edd4384
7
- data.tar.gz: 1a54b09adf164e7d40693a795789ddf2e35cfa7216a269ac3fbc1815029d2719107b8d59286f05156c4b87abc569366a1c0832a06b9b4a2fe31f6dc576dab839
6
+ metadata.gz: 3600025777793045fc0faaa17c1b3a150dc91bb11432589fd199b64b12f62ae4295960a6e2d5b5aa088880e6528dfb5cf7853800138ab9dc109637bf477653bf
7
+ data.tar.gz: 9eaf893afade620cbdae109ce6144b36fac204dcae85d2a18fb87b555784543c6a5b7bbb14f4e4a7a4b19deff3c8ca562174b5fb977cf7c849056569b614d623
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Stew Eucen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ = FertileForest
2
+
3
+ This project rocks and uses MIT-LICENSE.
data/config/routes.rb ADDED
@@ -0,0 +1,2 @@
1
+ FertileForest::Engine.routes.draw do
2
+ end
@@ -0,0 +1,13 @@
1
+ module FertileForest
2
+ #
3
+ # Template of Engine for Demo of Fertile Forest in future.
4
+ # @private
5
+ #
6
+ class Engine < ::Rails::Engine
7
+ isolate_namespace FertileForest
8
+
9
+ #### setting demo start
10
+
11
+ #### setting demo end
12
+ end
13
+ end
@@ -0,0 +1,231 @@
1
+ #
2
+ # Calcurators methods
3
+ # Fertile Forest for Ruby: The new model for storing hierarchical data in a database.
4
+ #
5
+ # @author StewEucen
6
+ # @copyright Copyright (c) 2015 Stew Eucen (http://lab.kochlein.com)
7
+ # @license http://www.opensource.org/licenses/mit-license.php MIT License
8
+ #
9
+ # @link http://lab.kochlein.com/FertileForest
10
+ # @since File available since Release 1.0.0
11
+ # @version 1.0.0
12
+ #
13
+ module StewEucen
14
+ # Name space of Stew Eucen's Acts
15
+ module Acts
16
+ # Name space of Fertile Forest
17
+ module FertileForest
18
+ # Name space of class methods for Fertile Forest
19
+ module Table
20
+ # This module is for extending into derived class by ActiveRecord.<br>
21
+ # The caption contains "Instance Methods",
22
+ # but it means "Class Methods" of each derived class.
23
+ # @private
24
+ module Calculators
25
+
26
+ def fill_required_columns_to_append!(node)
27
+ # calculate depth and queue for appending.
28
+ # If no interval, try to scoot over queue.
29
+ if node.ff_base_id.to_i <= 0
30
+ fill_info = ff_calc_required_columns_for_appending_as_root(node)
31
+ else
32
+ fill_info = ff_calc_required_columns_for_appending_as_internal(node)
33
+ end
34
+
35
+ # When fail to calc, can not save.
36
+ # no need to set error message here.
37
+ return false if fill_info.blank?
38
+
39
+ # not need to set ff_grove, because posted node has it already.
40
+ node.ff_queue = fill_info[:ff_queue]
41
+ node.ff_depth = fill_info[:ff_depth]
42
+
43
+ true
44
+ end
45
+
46
+ def ff_parse_kinship(node)
47
+ ff_kinship_key = node.ff_kinship
48
+ if /true|false/i.match(ff_kinship_key)
49
+ ff_kinship_key === 'true'
50
+ else
51
+ ff_kinship_key.to_i
52
+ end
53
+ end
54
+
55
+ #
56
+ # Calculate depth and queue as root node to append.
57
+ # @return [Hash] Calculated queue and depth.
58
+ # @return [false] Can not calculate.
59
+ #
60
+ def ff_calc_required_columns_for_appending_as_root(node)
61
+ # can be null=zero
62
+ posted_grove = node.ff_grove.to_i
63
+
64
+ # When append as root, need to post ff_grove.
65
+ if has_grove? && posted_grove <= 0
66
+ # TODO: set error message 'append_empty_column'
67
+ return false
68
+ end
69
+
70
+ # depth is fixed value
71
+ fill_info = {ff_depth: ROOT_DEPTH}
72
+
73
+ ####################################################################
74
+ #
75
+ # calculate queue
76
+ #
77
+
78
+ # get max queue in grove
79
+ last_queue = ff_get_last_queue(posted_grove) # can be nil
80
+
81
+ if last_queue.nil?
82
+ append_queue = 0
83
+ elsif QUEUE_MAX_VALUE <= last_queue
84
+ # Try to scoot over pre-nodes.
85
+ evenize_res = ff_evenize(posted_grove, nil, nil, 1) # 1: append node count
86
+
87
+ # When fail to evenize, filled all id.
88
+ if evenize_res.blank?
89
+ # TODO: set error append.canNotScootsOver'
90
+ return false
91
+ end
92
+
93
+ append_queue = evenize_res[SPROUT_VACANT_QUEUE_KEY]
94
+ elsif QUEUE_MAX_VALUE - last_queue < QUEUE_DEFAULT_INTERVAL
95
+ append_queue = QUEUE_MAX_VALUE
96
+ else
97
+ append_queue = last_queue + QUEUE_DEFAULT_INTERVAL
98
+ end
99
+
100
+ # return value
101
+ fill_info.merge({ff_queue: append_queue})
102
+ end
103
+
104
+ #
105
+ # Calculate depth and queue as internal node to append.
106
+ # (1) has space before base-node, calc median queue.
107
+ # (2) When no space befre base node, try to evenize.
108
+ # (3) can not evenize, can not append.
109
+ # @return [Hash] Calculated queue and depth.
110
+ # @return [false] Can not calculate.
111
+ #
112
+ def ff_calc_required_columns_for_appending_as_internal(node)
113
+ base_id = node.ff_base_id.to_i
114
+ grove_id = node.ff_grove.to_i
115
+
116
+ # get base node by ff_base_id
117
+ # use ff_grove for find, because grove means USER_ID
118
+ base_node = ff_required_columns_scope()
119
+ .ff_usual_conditions_scope(grove_id)
120
+ .where(id: base_id)
121
+ .first
122
+
123
+ # When has ff_base_id and the node is nothing, fail to append.
124
+ if base_node.blank?
125
+ # TODO: set errors append.baseNodeIsNull
126
+ return false
127
+ end
128
+
129
+ kinship = ff_parse_kinship(node)
130
+ is_sibling = ff_is_bool(kinship)
131
+
132
+ # depth is fixed value
133
+ fill_info = {ff_depth: base_node.ff_depth.to_i + (is_sibling ? 0 : 1)}
134
+
135
+ # pick up node for wedged node to scoot over. (can be null)
136
+ wedged_node = ff_get_wedged_node(base_node, kinship)
137
+
138
+ # When wedged node is nothing, it means last queue.
139
+ # In the case, calc appending queue is "lastQueue + INTERVAL"
140
+
141
+ if wedged_node.blank?
142
+ last_queue = ff_get_last_queue(grove_id, 0)
143
+ if last_queue < QUEUE_MAX_VALUE
144
+ if QUEUE_DEFAULT_INTERVAL <= QUEUE_MAX_VALUE - last_queue
145
+ calc_queue = last_queue + QUEUE_DEFAULT_INTERVAL
146
+ else
147
+ calc_queue = QUEUE_MAX_VALUE
148
+ end
149
+
150
+ return fill_info.merge({ff_queue: calc_queue})
151
+ end
152
+ else
153
+ #
154
+ # When got wedged node, calc median queue.
155
+ # (1) get previous node of the wedge node.
156
+ # (2) calc median queue.
157
+ #
158
+ append_queue = ff_calc_median_queue(wedged_node)
159
+
160
+ return fill_info.merge({ff_queue: append_queue}) \
161
+ if append_queue.present?
162
+ end
163
+
164
+ # When no space before wedged node, try to scoot over.
165
+ append_queue = ff_evenize_for_appending(base_node, wedged_node)
166
+
167
+ return fill_info.merge({ff_queue: append_queue}) \
168
+ if append_queue.present?
169
+
170
+ # TODO: set error message append.canNotScootsOver
171
+ false
172
+ end
173
+
174
+ def ff_calc_median_queue(wedged_node)
175
+ tail_node = ff_get_previous_node(wedged_node)
176
+
177
+ # tail_node never be null, because parent-node exists.
178
+ return nil if tail_node.blank?
179
+
180
+ tail_queue = tail_node.ff_queue
181
+ wedged_queue = wedged_node.ff_queue
182
+
183
+ return nil if wedged_queue - tail_queue <= 1
184
+
185
+ # not need to use (int)
186
+ (tail_queue + wedged_queue) / 2
187
+ end
188
+
189
+ def ff_evenize_for_appending(base_node, wedged_node)
190
+ append_node_count = 1
191
+
192
+ grove_id = base_node.ff_grove
193
+ base_queue = base_node.blank? ? nil : base_node .ff_queue
194
+ wedged_queue = wedged_node.blank? ? nil : wedged_node.ff_queue
195
+
196
+ # try to evenize all pre-nodes from this base node.
197
+ evenize_res = ff_evenize(grove_id, base_queue, wedged_queue, append_node_count)
198
+ return evenize_res[SPROUT_VACANT_QUEUE_KEY] if evenize_res.present?
199
+
200
+ # try to evenize all pre-nodes from this root node.
201
+ # {
202
+ # $rootNode = $this->root($baseNode);
203
+ # $evenizeRes = $this->_evenize($grove, $rootNode, $wedgedQueue, $appendNodeCount);
204
+ # if (!empty($evenizeRes)) {
205
+ # return $evenizeRes[self::SPROUT_VACANT_QUEUE_KEY];
206
+ # }
207
+ # }
208
+
209
+ # try to evenize all pre-nodes.
210
+ evenize_res = ff_evenize(grove_id, nil, wedged_queue, append_node_count)
211
+ return evenize_res[SPROUT_VACANT_QUEUE_KEY] if evenize_res.present?
212
+
213
+ # try to evenize all post-nodes.
214
+ evenize_res = ff_evenize(grove_id, wedged_queue, nil, append_node_count, true)
215
+ return evenize_res[SPROUT_VACANT_QUEUE_KEY] if evenize_res.present?
216
+
217
+ # can not evenize.
218
+ false
219
+ end
220
+
221
+ protected :ff_parse_kinship,
222
+ :ff_calc_required_columns_for_appending_as_root,
223
+ :ff_calc_required_columns_for_appending_as_internal,
224
+ :ff_calc_median_queue,
225
+ :ff_evenize_for_appending
226
+
227
+ end
228
+ end
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,96 @@
1
+ #
2
+ # Configs methods
3
+ # Fertile Forest for Ruby: The new model for storing hierarchical data in a database.
4
+ #
5
+ # @author StewEucen
6
+ # @copyright Copyright (c) 2015 Stew Eucen (http://lab.kochlein.com)
7
+ # @license http://www.opensource.org/licenses/mit-license.php MIT License
8
+ #
9
+ # @link http://lab.kochlein.com/FertileForest
10
+ # @since File available since Release 1.0.0
11
+ # @version 1.0.0
12
+ #
13
+ module StewEucen
14
+ # Name space of Stew Eucen's Acts
15
+ module Acts
16
+ # Name space of Fertile Forest
17
+ module FertileForest
18
+ # Name space of class methods for Fertile Forest
19
+ module Table
20
+ # This module is for extending into derived class by ActiveRecord.<br>
21
+ # The caption contains "Instance Methods",
22
+ # but it means "Class Methods" of each derived class.
23
+ module Configs
24
+
25
+ #
26
+ # Exists grove field in table?
27
+ #
28
+ # @author StewEucen
29
+ # @return [Boolean] true: has grove column.
30
+ # @return [Boolean] false: no grove column.
31
+ # @since Release 1.0.0
32
+ #
33
+ def has_grove?
34
+ ff_has_column? :ff_grove
35
+ end
36
+
37
+ #
38
+ # Exists soft-delete field in table?
39
+ #
40
+ # @author StewEucen
41
+ # @return [Boolean] true: has soft-delete column.
42
+ # @return [Boolean] false: no soft-delete column.
43
+ # @since Release 1.0.0
44
+ #
45
+ def has_soft_delete?
46
+ ff_has_column? :ff_soft_delete
47
+ end
48
+
49
+ #
50
+ # Is enable to use soft-delete by grove field?
51
+ #
52
+ # @author StewEucen
53
+ # @return [Boolean] true: enable.
54
+ # @return [Boolean] false: disable.
55
+ # @since Release 1.0.0
56
+ #
57
+ def enable_grove_delete?
58
+ has_grove? \
59
+ && !has_soft_delete? \
60
+ && ff_options[:enable_grove_delete]
61
+ # Need back slashes for this writing.
62
+ end
63
+
64
+ #
65
+ # Exists field in table?
66
+ #
67
+ # @author StewEucen
68
+ # @param column [Symbol] Column symbol to check.
69
+ # @return [Boolean] true: has specified column.
70
+ # @return [Boolean] false: no specified column.
71
+ # @since Release 1.0.0
72
+ #
73
+ def ff_has_column?(column)
74
+ key = column.to_s
75
+ attribute_aliases[key] || column_names.include?(key)
76
+ end
77
+
78
+ #
79
+ # Recommended queue interval for appending node.
80
+ # Can overwrite [queue interval] at setup()/initialize().
81
+ #
82
+ # @author StewEucen
83
+ # @return [Integer] Default queue interval.
84
+ # @since Release 1.0.0
85
+ #
86
+ def ff_get_query_interval
87
+ QUEUE_DEFAULT_INTERVAL
88
+ end
89
+
90
+ protected :ff_has_column?,
91
+ :ff_get_query_interval
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,233 @@
1
+ #
2
+ # Fertile Forest for Ruby
3
+ # The new model for storing hierarchical data in a database.
4
+ #
5
+ # @author StewEucen
6
+ # @copyright Copyright (c) 2015 Stew Eucen (http://lab.kochlein.com)
7
+ # @license http://www.opensource.org/licenses/mit-license.php MIT License
8
+ #
9
+ # @link http://lab.kochlein.com/FertileForest
10
+ # @since File available since Release 1.0.0
11
+ # @version 1.0.0
12
+ #
13
+ module StewEucen
14
+ # Name space of Stew Eucen's Acts
15
+ module Acts
16
+ # Name space of Fertile Forest
17
+ module FertileForest
18
+ #
19
+ # Instance methods to include into derived class by ActiveRecord.
20
+ #
21
+ # @author StewEucen
22
+ # @example Include into ActiveRecord class.
23
+ # ActiveRecord::Base.send :include, StewEucen::Acts::FertileForest::Entity
24
+ # @since Release 1.0.0
25
+ #
26
+ module Entity
27
+ #
28
+ # Before save listener (must be an instance methoed).
29
+ # Transparently manages setting the required fields for FertileForestBehavior
30
+ # if the parent field is included in the parameters to be saved.
31
+ #
32
+ # @return boolean true:Continue to save./false:Abort to save.
33
+ #
34
+ def ff_before_save
35
+ # when not new record, to update.
36
+ # no need to set ff_columns (id, grove, queue, depth)
37
+ return true unless new_record?
38
+
39
+ if self.class.has_grove?
40
+ posted_grove = self.ff_grove
41
+ if posted_grove.blank?
42
+ # TODO: set_error
43
+ return false
44
+ end
45
+ end
46
+
47
+ # return value
48
+ self.class.fill_required_columns_to_append!(self)
49
+ end
50
+
51
+ def ff_before_create
52
+ end
53
+
54
+ def ff_after_save
55
+ end
56
+
57
+ def ff_before_destroy
58
+ end
59
+
60
+ protected :ff_before_save,
61
+ :ff_before_create,
62
+ :ff_after_save,
63
+ :ff_before_destroy
64
+
65
+ ########################################################################
66
+
67
+ def ff_reset_values
68
+ {
69
+ parent: {0 => nil},
70
+ children: {},
71
+ }
72
+ end
73
+
74
+ def ff_get_options
75
+ @fertile_forest ||= ff_reset_values
76
+ end
77
+
78
+ def nest_unset_parent
79
+ ff_get_options[:parent] = {0 => nil}
80
+ end
81
+
82
+ def nest_unset_children(id = nil)
83
+ if id.blank?
84
+ ff_get_options[:children] = {}
85
+ else
86
+ ff_get_options[:children][id] = nil
87
+ end
88
+ end
89
+
90
+ ##
91
+ #
92
+ # accessors
93
+ #
94
+ #
95
+ ########################################################################
96
+
97
+ def nest_parent_node
98
+ ff_get_options[:parent].values.first
99
+ end
100
+
101
+ def nest_child_nodes
102
+ ff_get_options[:children]
103
+ end
104
+
105
+ def nest_parent_id
106
+ ff_get_options[:parent].keys.first
107
+ end
108
+
109
+ def nest_child_ids
110
+ ff_get_options[:children].keys
111
+ end
112
+
113
+ alias nest_parent nest_parent_node
114
+ alias nest_genitor nest_parent_node
115
+ alias nest_children nest_child_nodes
116
+
117
+ ########################################################################
118
+
119
+ def nest_set_parent_id(aim_id)
120
+ ff_get_options[:parent] = {aim_id => nil} # always overwrite
121
+ end
122
+
123
+ def nest_set_child_id(aim_id)
124
+ ff_get_options[:children][aim_id] = nil
125
+ end
126
+
127
+ ########################################################################
128
+
129
+ def nest_set_parent_node(aim_id, node)
130
+ ff_get_options[:parent] = {aim_id => node} \
131
+ if aim_id.present? && node.present?
132
+ end
133
+
134
+ def nest_set_child_node(aim_id, node)
135
+ ff_get_options[:children][aim_id] = node \
136
+ if aim_id.present? && node.present?
137
+ end
138
+
139
+ ########################################################################
140
+
141
+ def nest_leaf?
142
+ ff_get_options[:children].blank?
143
+ end
144
+
145
+ def nest_parent?
146
+ !nest_leaf?
147
+ end
148
+
149
+ ########################################################################
150
+
151
+ def trunk(range = ANCESTOR_ALL, columns = nil)
152
+ self.class.trunk(self, range, columns)
153
+ end
154
+
155
+ def ancestors(columns = nil)
156
+ self.class.ancestors(self, columns)
157
+ end
158
+
159
+ def genitor(columns = nil)
160
+ self.class.genitor(self, columns)
161
+ end
162
+
163
+ def root(columns = nil)
164
+ self.class.root(self, columns)
165
+ end
166
+
167
+ def grandparent(columns = nil)
168
+ self.class.grandparent(self, columns)
169
+ end
170
+
171
+ def subtree(range = DESCENDANTS_ALL, with_top = true, columns = nil)
172
+ self.class.subtree(self, range, with_top, columns)
173
+ end
174
+
175
+ def descendants(columns = nil)
176
+ self.class.descendants(self, columns)
177
+ end
178
+
179
+ def children(columns = nil)
180
+ self.class.children(self, columns)
181
+ end
182
+
183
+ def nth_child(nth = 0, columns = nil)
184
+ self.class.nth_child(self, nth, columns)
185
+ end
186
+
187
+ def grandchildren(columns = nil)
188
+ self.class.grandchildren(self, columns)
189
+ end
190
+
191
+ def siblings(columns = nil)
192
+ self.class.siblings(self, columns)
193
+ end
194
+
195
+ def nth_sibling(nth = 0, columns = nil)
196
+ self.class.nth_sibling(self, nth, columns)
197
+ end
198
+
199
+ def elder_sibling(columns = nil)
200
+ self.class.elder_sibling(self, columns)
201
+ end
202
+
203
+ def younger_sibling(columns = nil)
204
+ self.class.younger_sibling(self, columns)
205
+ end
206
+
207
+ def offset_sibling(offset, columns = nil)
208
+ self.class.offset_sibling(self, offset, columns)
209
+ end
210
+
211
+ def leaves(columns = nil)
212
+ self.class.leaves(self, columns)
213
+ end
214
+
215
+ def internals(columns = nil)
216
+ self.class.internals(self, columns)
217
+ end
218
+
219
+ def height
220
+ self.class.height(self)
221
+ end
222
+
223
+ def size
224
+ self.class.size(self)
225
+ end
226
+
227
+ protected :ff_reset_values,
228
+ :ff_get_options
229
+ end
230
+ end
231
+
232
+ end
233
+ end