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 +4 -4
- data/LICENSE +22 -0
- data/README.rdoc +3 -0
- data/config/routes.rb +2 -0
- data/lib/fertile_forest/engine.rb +13 -0
- data/lib/fertile_forest/modules/calculators.rb +231 -0
- data/lib/fertile_forest/modules/configs.rb +96 -0
- data/lib/fertile_forest/modules/entities.rb +233 -0
- data/lib/fertile_forest/modules/finders.rb +736 -0
- data/lib/fertile_forest/modules/reconstructers.rb +843 -0
- data/lib/fertile_forest/modules/states.rb +271 -0
- data/lib/fertile_forest/modules/utilities.rb +188 -0
- data/lib/fertile_forest/saplings.rb +330 -0
- data/lib/fertile_forest/version.rb +1 -1
- data/lib/fertile_forest.rb +7 -3
- data/lib/tasks/fertile_forest_tasks.rake +4 -0
- metadata +19 -6
@@ -0,0 +1,330 @@
|
|
1
|
+
require 'fertile_forest/modules/configs'
|
2
|
+
require 'fertile_forest/modules/utilities'
|
3
|
+
require 'fertile_forest/modules/calculators'
|
4
|
+
require 'fertile_forest/modules/finders'
|
5
|
+
require 'fertile_forest/modules/reconstructers'
|
6
|
+
require 'fertile_forest/modules/states'
|
7
|
+
require 'fertile_forest/modules/entities'
|
8
|
+
|
9
|
+
#
|
10
|
+
# Fertile Forest for Ruby on Rails
|
11
|
+
# The new model for storing hierarchical data in a database.
|
12
|
+
#
|
13
|
+
# @author StewEucen
|
14
|
+
# @copyright Copyright (c) 2015 Stew Eucen (http://lab.kochlein.com)
|
15
|
+
# @license http://www.opensource.org/licenses/mit-license.php MIT License
|
16
|
+
#
|
17
|
+
# @link http://lab.kochlein.com/FertileForest
|
18
|
+
# @since File available since Release 1.0.0
|
19
|
+
# @version 1.0.0
|
20
|
+
#
|
21
|
+
module StewEucen
|
22
|
+
# Name space of Stew Eucen's Acts
|
23
|
+
module Acts
|
24
|
+
# Name space of Fertile Forest
|
25
|
+
module FertileForest
|
26
|
+
# consts used in Table and Entity
|
27
|
+
ROOT_DEPTH = 0
|
28
|
+
|
29
|
+
APPEND_BASE_ID_FIELD = :ff_base_id
|
30
|
+
APPEND_NODE_RELATION_AS_LAST_CHILD = -1
|
31
|
+
APPEND_NODE_RELATION_AS_ELDER_SIBLING = false
|
32
|
+
|
33
|
+
ANCESTOR_ONLY_PARENT = 1
|
34
|
+
ANCESTOR_ONLY_ROOT = -1
|
35
|
+
ANCESTOR_ALL = 0
|
36
|
+
|
37
|
+
DESCENDANTS_ALL = 0
|
38
|
+
DESCENDANTS_ONLY_CHILD = 1
|
39
|
+
|
40
|
+
PRUNE_DESCENDANTS_ONLY = false
|
41
|
+
PRUNE_WITH_TOP_NODE = true
|
42
|
+
|
43
|
+
SUBTREE_WITHOUT_TOP_NODE = false
|
44
|
+
SUBTREE_WITH_TOP_NODE = true
|
45
|
+
|
46
|
+
ORDER_BY_QUEUE_INDEX = false
|
47
|
+
ORDER_BY_DEPTH_INDEX = true
|
48
|
+
|
49
|
+
# recommended queue interval for sprouting node
|
50
|
+
# QUEUE_DEFAULT_INTERVAL = 3
|
51
|
+
# QUEUE_MAX_VALUE = 15
|
52
|
+
QUEUE_DEFAULT_INTERVAL = 0x8000
|
53
|
+
QUEUE_MAX_VALUE = 0x7fffffff # 2147483647
|
54
|
+
|
55
|
+
# result to scoot over
|
56
|
+
SPROUT_VACANT_QUEUE_KEY = :vacant_queue # new queue to sprout
|
57
|
+
EVENIZE_AFFECTED_ROWS_KEY = :affected_rows # number of scooted over nodes
|
58
|
+
|
59
|
+
@@errors = [
|
60
|
+
append_empty_column: 'When has grove field, must set this fields.',
|
61
|
+
append_can_not_scoot_over: 'No space to append at queue, and can not scoots over.',
|
62
|
+
append_base_node_is_null: 'Not found base node to append.',
|
63
|
+
restructure_empty_column: 'When has grove field, must set this fields.',
|
64
|
+
restructure_defferent_groves: 'Defferent groves.',
|
65
|
+
restructure_graft_into_own: 'Graft into own subtree.',
|
66
|
+
restructure_are_not_siblings: 'Exists not sibling.',
|
67
|
+
]
|
68
|
+
|
69
|
+
#
|
70
|
+
# Table Methods to extend into ActiveRecord class.
|
71
|
+
#
|
72
|
+
# @author StewEucen
|
73
|
+
# @example Extend into ActiveRecord class.
|
74
|
+
# ActiveRecord::Base.send :extend, StewEucen::Acts::FertileForest::Table
|
75
|
+
# @since Release 1.0.0
|
76
|
+
#
|
77
|
+
module Table
|
78
|
+
#
|
79
|
+
# Initializer for eech model.
|
80
|
+
#
|
81
|
+
# @author StewEucen
|
82
|
+
# @example You write this method in a model of Rails as follows.
|
83
|
+
# class Category < ActiveRecord::Base
|
84
|
+
# acts_as_fertile_forest
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# # When use alias name of columns as:
|
88
|
+
# acts_as_fertile_forest({
|
89
|
+
# aliases: {
|
90
|
+
# ff_queue: :queue,
|
91
|
+
# ff_depth: :depth,
|
92
|
+
# ff_grove: :user_id,
|
93
|
+
# ff_soft_deleted: :deleted,
|
94
|
+
# }
|
95
|
+
# })
|
96
|
+
# @param options [Hash] Fertile Forest's options.
|
97
|
+
# @since Release 1.0.0
|
98
|
+
#
|
99
|
+
def acts_as_fertile_forest(options = {})
|
100
|
+
# To use these modules in case fertile forest only.
|
101
|
+
extend Configs
|
102
|
+
extend Utilities
|
103
|
+
extend Calculators
|
104
|
+
extend Finders
|
105
|
+
extend Reconstructers
|
106
|
+
extend States
|
107
|
+
|
108
|
+
# To change instance of ActiveRecord to Fertile Forest entity.
|
109
|
+
include Entity
|
110
|
+
|
111
|
+
ff_parse_options! options
|
112
|
+
ff_resolve_alias_columns!
|
113
|
+
|
114
|
+
# can set attr_accessor here
|
115
|
+
attr_accessor :ff_kinship
|
116
|
+
attr_accessor APPEND_BASE_ID_FIELD
|
117
|
+
attr_accessor :ff_grove unless has_grove?
|
118
|
+
|
119
|
+
ff_define_scopes!
|
120
|
+
ff_define_alias_methods!
|
121
|
+
ff_define_callbacks!
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def ff_resolve_alias_columns!
|
127
|
+
# aliases of ff_xxxxx 2015/05/27
|
128
|
+
if ff_options[:aliases].present?
|
129
|
+
ff_options[:aliases].each_pair do |ff_alias, ff_origin|
|
130
|
+
alias_attribute ff_alias, ff_origin
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
ff_required_columns = [
|
135
|
+
'id',
|
136
|
+
'ff_grove',
|
137
|
+
'ff_depth',
|
138
|
+
'ff_queue',
|
139
|
+
'ff_soft_delete',
|
140
|
+
]
|
141
|
+
|
142
|
+
ff_required_columns.each do |key|
|
143
|
+
instance_variable_set('@_' + key, attribute_aliases[key] || key)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def ff_define_alias_methods!
|
148
|
+
# Alias of ActiveRecord::Base::save.
|
149
|
+
# @see ActiveRecord::Base::save.
|
150
|
+
alias_method :sprout, :save
|
151
|
+
end
|
152
|
+
|
153
|
+
def ff_define_callbacks!
|
154
|
+
before_create :ff_before_create # must be an instance method
|
155
|
+
before_save :ff_before_save
|
156
|
+
|
157
|
+
after_save :ff_after_save
|
158
|
+
before_destroy :ff_before_destroy
|
159
|
+
end
|
160
|
+
|
161
|
+
def ff_default_options
|
162
|
+
{
|
163
|
+
virtual_columns: {
|
164
|
+
ff_base_id: 'ff_base_id',
|
165
|
+
},
|
166
|
+
|
167
|
+
enable_value: 0,
|
168
|
+
delete_value: 1,
|
169
|
+
|
170
|
+
enable_grove_delete: true,
|
171
|
+
subtree_limit_size: 1000,
|
172
|
+
}.freeze
|
173
|
+
end
|
174
|
+
|
175
|
+
def ff_parse_options!(options)
|
176
|
+
options = ff_default_options.merge(options)
|
177
|
+
|
178
|
+
class_attribute :ff_options
|
179
|
+
self.ff_options = options
|
180
|
+
end
|
181
|
+
|
182
|
+
def ff_define_scopes!
|
183
|
+
scope :ff_usual_conditions_scope, ->(grove_id = nil) do
|
184
|
+
grove_id ||= 0
|
185
|
+
|
186
|
+
conditions = all
|
187
|
+
|
188
|
+
conditions.where!(ff_soft_delete: ff_options[:enable_value]) \
|
189
|
+
if has_soft_delete?
|
190
|
+
|
191
|
+
if has_grove?
|
192
|
+
if 0 < grove_id
|
193
|
+
conditions.where!(ff_grove: grove_id)
|
194
|
+
else
|
195
|
+
conditions.where!(arel_table[@_ff_grove].gteq(0)) \
|
196
|
+
if enable_grove_delete?
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
conditions
|
201
|
+
end
|
202
|
+
|
203
|
+
scope :ff_usual_order_scope,
|
204
|
+
->(is_descendant = false, is_depth_index = ORDER_BY_QUEUE_INDEX) do
|
205
|
+
direction = is_descendant ? ' DESC' : ' ASC'
|
206
|
+
|
207
|
+
aim_orders = [];
|
208
|
+
aim_orders << @_ff_soft_delete + direction if has_soft_delete?
|
209
|
+
aim_orders << @_ff_grove + direction if has_grove?
|
210
|
+
aim_orders << @_ff_depth + direction if is_depth_index
|
211
|
+
aim_orders << @_ff_queue + direction
|
212
|
+
|
213
|
+
order(aim_orders.join(', '))
|
214
|
+
end
|
215
|
+
|
216
|
+
scope :ff_required_columns_scope,
|
217
|
+
->(add_columns = nil) do
|
218
|
+
columns = [@_id, @_ff_queue, @_ff_depth]
|
219
|
+
columns << @_ff_grove if has_grove?
|
220
|
+
|
221
|
+
columns += add_columns if add_columns.present?
|
222
|
+
|
223
|
+
select(columns)
|
224
|
+
end
|
225
|
+
|
226
|
+
scope :ff_subtree_scope,
|
227
|
+
->(base_node, with_top = false, use_coalesce = false) do
|
228
|
+
return nil if base_node.blank?
|
229
|
+
|
230
|
+
ffqq = arel_table[@_ff_queue]
|
231
|
+
ffdd = arel_table[@_ff_depth]
|
232
|
+
ffgg = arel_table[@_ff_grove]
|
233
|
+
|
234
|
+
compair = with_top ? :gteq : :gt
|
235
|
+
aim_query = ff_usual_conditions_scope(base_node.ff_grove)
|
236
|
+
.where(ffqq.send(compair, base_node.ff_queue))
|
237
|
+
|
238
|
+
if use_coalesce
|
239
|
+
# TODO: methodize
|
240
|
+
subquery = ff_create_subquery_string_to_find_tail_queue(base_node)
|
241
|
+
func_maker = Arel::Nodes::NamedFunction
|
242
|
+
coalesce_condition = func_maker.new('COALESCE', [subquery, QUEUE_MAX_VALUE])
|
243
|
+
aim_query.where!(ffqq.lteq(coalesce_condition))
|
244
|
+
else
|
245
|
+
boundary_queue = ff_get_boundary_queue(base_node)
|
246
|
+
if boundary_queue.blank?
|
247
|
+
# need this conditions for leaves @dd = ffdd.
|
248
|
+
aim_query.where!(ffqq.lteq(QUEUE_MAX_VALUE))
|
249
|
+
else
|
250
|
+
aim_query.where!(ffqq.lt(boundary_queue))
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
aim_query
|
255
|
+
end
|
256
|
+
|
257
|
+
# boundary node onditions scope
|
258
|
+
# tail node onditions scope
|
259
|
+
# pre nodes conditions scope
|
260
|
+
end
|
261
|
+
|
262
|
+
def ff_usual_projection(grove_id = nil)
|
263
|
+
res = arel_table.project()
|
264
|
+
|
265
|
+
res = res.where(arel_table[@_ff_soft_delete].eq(ff_options[:enable_value])) \
|
266
|
+
if has_soft_delete?
|
267
|
+
|
268
|
+
if has_grove?
|
269
|
+
ffgg = arel_table[@_ff_grove]
|
270
|
+
if grove_id.present?
|
271
|
+
# res = res.send :where, {@_ff_grove => grove_id}
|
272
|
+
if grove_id.instance_of?(Array)
|
273
|
+
res = res.where(ffgg.in(grove_id))
|
274
|
+
else
|
275
|
+
res = res.where(ffgg.eq(grove_id))
|
276
|
+
end
|
277
|
+
else
|
278
|
+
res = res.where(ffgg.gteq(0)) \
|
279
|
+
if enable_grove_delete?
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
res
|
284
|
+
end
|
285
|
+
|
286
|
+
def ff_create_usual_conditions_hash(grove_id = nil)
|
287
|
+
res = {}
|
288
|
+
res[:ff_soft_delete] = ff_options[:enable_value] \
|
289
|
+
if has_soft_delete?
|
290
|
+
|
291
|
+
if has_grove?
|
292
|
+
if grove_id.present?
|
293
|
+
res[:ff_grove] = grove_id
|
294
|
+
else
|
295
|
+
res['ff_grove >='] = 0 if enable_grove_delete?
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
res
|
300
|
+
end
|
301
|
+
|
302
|
+
def ff_all_optional_columns(optional_columns = nil)
|
303
|
+
optional_columns ||= column_names
|
304
|
+
|
305
|
+
required_columns = [@_id, @_ff_grove, @_ff_depth, @_ff_queue]
|
306
|
+
regexp_string = required_columns
|
307
|
+
.map { |column| column.to_s }
|
308
|
+
.join('|')
|
309
|
+
delete_regexp = /#{regexp_string}/i
|
310
|
+
|
311
|
+
# return value (must be Array).
|
312
|
+
optional_columns.delete_if do |column|
|
313
|
+
delete_regexp.match(column)
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
def ff_create_subquery_string_to_find_tail_queue(aim_node)
|
318
|
+
ffqq = arel_table[@_ff_queue]
|
319
|
+
ffdd = arel_table[@_ff_depth]
|
320
|
+
|
321
|
+
ff_usual_projection(aim_node.ff_grove)
|
322
|
+
.project(ffqq.minimum.to_sql + " - 1 AS boundary_queue")
|
323
|
+
.where(ffdd.lteq(aim_node.ff_depth))
|
324
|
+
.where(ffqq.gt(aim_node.ff_queue))
|
325
|
+
end
|
326
|
+
# end of private
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
data/lib/fertile_forest.rb
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
require 'fertile_forest/engine'
|
4
|
+
require 'fertile_forest/saplings'
|
5
|
+
require 'fertile_forest/version'
|
6
|
+
|
7
|
+
ActiveRecord::Base.send :extend, StewEucen::Acts::FertileForest::Table
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fertile_forest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stew Eucen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-10-
|
11
|
+
date: 2015-10-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '4.
|
19
|
+
version: '4.2'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '4.
|
26
|
+
version: '4.2'
|
27
27
|
description: Fertile Forest is the new model to store hierarchical data in a database.
|
28
28
|
Conventional models are "adjacency list", "route enumeration", "nested set" and
|
29
29
|
"closure table". Fertile Forest has some excellent features than each conventional
|
@@ -34,9 +34,22 @@ executables: []
|
|
34
34
|
extensions: []
|
35
35
|
extra_rdoc_files: []
|
36
36
|
files:
|
37
|
+
- LICENSE
|
38
|
+
- README.rdoc
|
37
39
|
- Rakefile
|
40
|
+
- config/routes.rb
|
38
41
|
- lib/fertile_forest.rb
|
42
|
+
- lib/fertile_forest/engine.rb
|
43
|
+
- lib/fertile_forest/modules/calculators.rb
|
44
|
+
- lib/fertile_forest/modules/configs.rb
|
45
|
+
- lib/fertile_forest/modules/entities.rb
|
46
|
+
- lib/fertile_forest/modules/finders.rb
|
47
|
+
- lib/fertile_forest/modules/reconstructers.rb
|
48
|
+
- lib/fertile_forest/modules/states.rb
|
49
|
+
- lib/fertile_forest/modules/utilities.rb
|
50
|
+
- lib/fertile_forest/saplings.rb
|
39
51
|
- lib/fertile_forest/version.rb
|
52
|
+
- lib/tasks/fertile_forest_tasks.rake
|
40
53
|
homepage: http://lab.kochlein.com/FertileForest
|
41
54
|
licenses:
|
42
55
|
- MIT
|
@@ -49,7 +62,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
49
62
|
requirements:
|
50
63
|
- - ">="
|
51
64
|
- !ruby/object:Gem::Version
|
52
|
-
version:
|
65
|
+
version: 2.1.5
|
53
66
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
67
|
requirements:
|
55
68
|
- - ">="
|
@@ -57,7 +70,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
57
70
|
version: '0'
|
58
71
|
requirements: []
|
59
72
|
rubyforge_project:
|
60
|
-
rubygems_version: 2.
|
73
|
+
rubygems_version: 2.2.2
|
61
74
|
signing_key:
|
62
75
|
specification_version: 4
|
63
76
|
summary: The new model to store hierarchical data in a database.
|