fertile_forest 0.0.0 → 1.0.0

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