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.
@@ -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
@@ -1,3 +1,3 @@
1
1
  module FertileForest
2
- VERSION = "0.0.0"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -1,3 +1,7 @@
1
- puts '******************************'
2
- puts 'Fertile Forest Loading Test OK'
3
- puts '******************************'
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
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :fertile_forest do
3
+ # # Task goes here
4
+ # end
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: 0.0.0
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-10 00:00:00.000000000 Z
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.0'
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.0'
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: '2.0'
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.4.8
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.