action_blocks 0.1.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.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +121 -0
  4. data/Rakefile +33 -0
  5. data/app/assets/config/action_blocks.js +2 -0
  6. data/app/assets/javascripts/action_blocks/application.js +15 -0
  7. data/app/assets/stylesheets/action_blocks/application.css +15 -0
  8. data/app/controllers/action_blocks/attachments_controller.rb +22 -0
  9. data/app/controllers/action_blocks/barchart_blocks_controller.rb +14 -0
  10. data/app/controllers/action_blocks/base_controller.rb +22 -0
  11. data/app/controllers/action_blocks/blocks_controller.rb +16 -0
  12. data/app/controllers/action_blocks/command_blocks_controller.rb +6 -0
  13. data/app/controllers/action_blocks/form_blocks_controller.rb +13 -0
  14. data/app/controllers/action_blocks/model_blocks_controller.rb +13 -0
  15. data/app/controllers/action_blocks/table_blocks_controller.rb +13 -0
  16. data/app/controllers/action_blocks/workspace_blocks_controller.rb +6 -0
  17. data/app/helpers/action_blocks/application_helper.rb +4 -0
  18. data/app/jobs/action_blocks/application_job.rb +4 -0
  19. data/app/mailers/action_blocks/application_mailer.rb +6 -0
  20. data/app/models/action_blocks/application_record.rb +5 -0
  21. data/app/views/layouts/action_blocks/application.html.erb +16 -0
  22. data/config/initializers/action_blocks.rb +9 -0
  23. data/config/routes.rb +10 -0
  24. data/lib/action_block_loader.rb +120 -0
  25. data/lib/action_blocks.rb +76 -0
  26. data/lib/action_blocks/builders/authorization_builder.rb +21 -0
  27. data/lib/action_blocks/builders/barchart_builder.rb +48 -0
  28. data/lib/action_blocks/builders/base_builder.rb +221 -0
  29. data/lib/action_blocks/builders/block_type.rb +11 -0
  30. data/lib/action_blocks/builders/command_builder.rb +6 -0
  31. data/lib/action_blocks/builders/form_builder.rb +117 -0
  32. data/lib/action_blocks/builders/layout_builder.rb +15 -0
  33. data/lib/action_blocks/builders/model_builder.rb +566 -0
  34. data/lib/action_blocks/builders/summary_field_aggregation_functions.rb +41 -0
  35. data/lib/action_blocks/builders/table_builder.rb +259 -0
  36. data/lib/action_blocks/builders/workspace_builder.rb +282 -0
  37. data/lib/action_blocks/data_engine/authorization_adapter.rb +127 -0
  38. data/lib/action_blocks/data_engine/data_engine.rb +116 -0
  39. data/lib/action_blocks/data_engine/database_functions.rb +39 -0
  40. data/lib/action_blocks/data_engine/fields_engine.rb +103 -0
  41. data/lib/action_blocks/data_engine/filter_adapter.rb +105 -0
  42. data/lib/action_blocks/data_engine/filter_engine.rb +88 -0
  43. data/lib/action_blocks/data_engine/selections_via_where_engine.rb +134 -0
  44. data/lib/action_blocks/data_engine/summary_engine.rb +72 -0
  45. data/lib/action_blocks/engine.rb +5 -0
  46. data/lib/action_blocks/error.rb +62 -0
  47. data/lib/action_blocks/generator_helper.rb +134 -0
  48. data/lib/action_blocks/generators/action_blocks/model_block/USAGE +8 -0
  49. data/lib/action_blocks/generators/action_blocks/model_block/model_block_generator.rb +17 -0
  50. data/lib/action_blocks/generators/action_blocks/model_block/templates/model_block.rb +13 -0
  51. data/lib/action_blocks/generators/action_blocks/type/USAGE +8 -0
  52. data/lib/action_blocks/generators/action_blocks/type/templates/controller.rb +3 -0
  53. data/lib/action_blocks/generators/action_blocks/type/templates/dsl.rb +38 -0
  54. data/lib/action_blocks/generators/action_blocks/type/templates/type.css +3 -0
  55. data/lib/action_blocks/generators/action_blocks/type/templates/type.js +22 -0
  56. data/lib/action_blocks/generators/action_blocks/type/type_generator.rb +33 -0
  57. data/lib/action_blocks/store.rb +151 -0
  58. data/lib/action_blocks/version.rb +3 -0
  59. data/lib/generators/active_blocks/model_block/USAGE +8 -0
  60. data/lib/generators/active_blocks/model_block/model_block_generator.rb +17 -0
  61. data/lib/generators/active_blocks/model_block/templates/model_block.rb +13 -0
  62. data/lib/generators/active_blocks/type/USAGE +8 -0
  63. data/lib/generators/active_blocks/type/templates/controller.rb +3 -0
  64. data/lib/generators/active_blocks/type/templates/dsl.rb +38 -0
  65. data/lib/generators/active_blocks/type/templates/type.css +3 -0
  66. data/lib/generators/active_blocks/type/templates/type.js +22 -0
  67. data/lib/generators/active_blocks/type/type_generator.rb +33 -0
  68. data/lib/tasks/active_blocks_tasks.rake +4 -0
  69. metadata +128 -0
@@ -0,0 +1,15 @@
1
+ module ActionBlocks
2
+ class LayoutBuilder < ActionBlocks::BlockType
3
+ block_type :layout
4
+ sets :title
5
+ builds_many :workspaces, :workspace, 'ActionBlocks::WorkspaceBuilder'
6
+
7
+ def hashify(user)
8
+ {
9
+ key: key,
10
+ title: @title,
11
+ workspace_keys: @workspaces.map(&:key),
12
+ }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,566 @@
1
+ module ActionBlocks
2
+ class ModelBuilder < ActionBlocks::BlockType
3
+ block_type :model
4
+ sets :active_model
5
+ sets :singular_name
6
+ sets :plural_name
7
+ references :name_field, :field, -> (s, obj) {"field-#{obj.id}-#{s}"}
8
+
9
+ builds_many :fields, :identity, 'ActionBlocks::IdentityFieldBuilder'
10
+ builds_many :fields, :string, 'ActionBlocks::StringFieldBuilder'
11
+ builds_many :fields, :text, 'ActionBlocks::TextFieldBuilder'
12
+ builds_many :fields, :datetime, 'ActionBlocks::DatetimeFieldBuilder'
13
+ builds_many :fields, :date, 'ActionBlocks::DateFieldBuilder'
14
+ builds_many :fields, :integer, 'ActionBlocks::IntegerFieldBuilder'
15
+ builds_many :fields, :attachment, 'ActionBlocks::AttachmentBuilder'
16
+
17
+ builds_many :fields, :references, 'ActionBlocks::ReferenceFieldBuilder'
18
+ builds_many :selections, :selection, 'ActionBlocks::SelectionBuilder'
19
+
20
+ validate :active_model_is_an_active_record_class
21
+ def active_model_is_an_active_record_class
22
+ if @active_model.class != Class
23
+ errors.add(:active_model, "Must be an ActiveRecord Class")
24
+ else
25
+ if !@active_model.ancestors.include? ActiveRecord::Base
26
+ errors.add(:active_model, "Must be an ActiveRecord Class")
27
+ end
28
+ end
29
+ end
30
+
31
+ def after_build(parent, *args)
32
+ # Every model has an identity field
33
+ if !@fields.map(&:id).include?(:id)
34
+ @dsl_delegate.identity
35
+ end
36
+ end
37
+
38
+ def select_requirements(fields = nil)
39
+ sel_reqs = @fields.map(&:select_requirements)
40
+ sel_reqs = sel_reqs.select { |r| fields.include? r[:field_name] } unless fields.nil?
41
+
42
+ return sel_reqs
43
+ end
44
+
45
+ def name_to_json(record_id:, user:)
46
+ select_reqs = [name_field.select_requirements]
47
+ filter_reqs = [:eq, :id, record_id]
48
+ engine = DataEngine.new(active_model, select_reqs: select_reqs, filter_reqs: filter_reqs)
49
+ engine.first_to_json
50
+ end
51
+
52
+ def selection_filter_reqs(record_id:, user:)
53
+ [
54
+ {
55
+ base_path: [active_model, :id],
56
+ predicate: :eq,
57
+ related_path: [record_id]
58
+ }
59
+ ]
60
+ end
61
+
62
+ def filter_reqs(record_id:, user:)
63
+ [:eq, :id, record_id]
64
+ end
65
+
66
+ def hashify(user)
67
+ {
68
+ key: key,
69
+ type: type,
70
+ name_field: name_field.try(:id),
71
+ singular_name: singular_name,
72
+ plural_name: plural_name
73
+ }
74
+ end
75
+
76
+ end
77
+
78
+ class ActionBlocks::SelectionBuilder < ActionBlocks::BlockType
79
+ attr_accessor :base_model, :related_model_id, :match_conditions_source
80
+ references :related_model
81
+ block_type :selection
82
+ builds_many :match_conditions, :match_condition, 'ActionBlocks::MatchConditionBuilder'
83
+ builds_many :summaries, :summary, 'ActionBlocks::SummaryFieldBuilder'
84
+
85
+ validate :match_conditions_exist
86
+ def match_conditions_exist
87
+ if @match_conditions.length == 0
88
+ errors.add(:match_conditions, "No match conditions on selection #{key}")
89
+ end
90
+ end
91
+
92
+ validate :match_conditions_come_from_relation_if_relation_exists
93
+ def match_conditions_come_from_relation_if_relation_exists
94
+ if @match_conditions_source == 'user defined'
95
+ # puts @base_model.active_model.reflections.keys.inspect
96
+ relation = @base_model.active_model.reflections[@id.to_s]
97
+
98
+ if relation && relation.collection?
99
+ errors.add(:match_conditions, "Selections name matches name of has_many, but defines match_conditions anyway")
100
+ end
101
+ end
102
+ end
103
+
104
+ def key
105
+ "selection-#{@base_model.id}-#{@id}"
106
+ end
107
+
108
+ def before_build(parent, id, *args)
109
+ @base_model = parent
110
+ @id = id # aka name
111
+ if args[0]
112
+ @related_model_id = args[0].to_sym
113
+ else
114
+ @related_model_id = @id.to_s.singularize.to_sym
115
+ end
116
+ @related_model_key = "model-#{@related_model_id}"
117
+ end
118
+
119
+ def after_build(parent, *args)
120
+ if @match_conditions.length == 0
121
+ build_match_conditions_by_reflecting
122
+ else
123
+ @match_conditions_source = "user defined"
124
+ end
125
+ end
126
+
127
+ def build_match_conditions_by_reflecting
128
+ relation = @base_model.active_model.reflections[@id.to_s]
129
+ if relation && relation.collection?
130
+ @match_conditions_source = "relation"
131
+ if relation.class == ActiveRecord::Reflection::HasManyReflection
132
+ @dsl_delegate.match_condition relation.join_keys.foreign_key.to_sym, :eq, relation.join_keys.key.to_sym
133
+ else
134
+ raise "Relation #{relation.class} not supported"
135
+ end
136
+ end
137
+ end
138
+
139
+ def match_reqs(*p)
140
+ user = p.first
141
+ match_conditions.map(&:match_reqs)
142
+ end
143
+
144
+ def record_filter_reqs(record:, user:)
145
+ if record.class != @base_model.active_model
146
+ raise "Selection issue."
147
+ end
148
+ @base_model.selection_filter_reqs(record_id: record.id, user: user)
149
+ end
150
+
151
+ def hashify(user)
152
+ {
153
+ key: key,
154
+ type: type
155
+ }
156
+ end
157
+
158
+ end
159
+
160
+ # Base - Refers to the selecting model and fields on selecting model
161
+ # Related - Refers to the selected model and fields on selected model
162
+ # e.g. When a Work Order selects Items
163
+ # Item is Related Model
164
+ # WorkOrder is Base Model
165
+ #
166
+ # When building a match condition, its defined in the Base Models
167
+ # builder.
168
+ # e.g. When a Work Order selects Items
169
+ # This selection is declared in the Work Order Model
170
+ #
171
+ # When using a selection to select related items, such as a table
172
+ # the Base Model (Work Order) feels a bit misnamed from this context
173
+ # as Items are being selected and form the foundation of the query.
174
+
175
+ class ActionBlocks::MatchConditionBuilder < ActionBlocks::BaseBuilder
176
+ attr_accessor :related_field_id, :base_field_id, :parent_model, :related_model
177
+ references :base_field
178
+ references :related_field
179
+ sets :predicate
180
+
181
+ def before_build(parent, base_field, predicate, related_field)
182
+ if base_field.is_a? Symbol
183
+ @base_model = parent.base_model
184
+ @base_field_id = base_field
185
+ @base_field_key = "field-#{parent.base_model.id}-#{@base_field_id}"
186
+ else
187
+ @base_value = base_field
188
+ end
189
+
190
+ @related_model = parent.related_model
191
+ @related_field_id = related_field
192
+ @related_field_key = "field-#{parent.related_model_id}-#{@related_field_id}"
193
+
194
+ @predicate = predicate
195
+ end
196
+
197
+ def base_match_reqs
198
+ if @base_value
199
+ {
200
+ path: [@base_value]
201
+ }
202
+ else
203
+ field_reqs = base_field.match_requirements(@base_model.active_model)
204
+ {
205
+ path: field_reqs[:path]
206
+ }
207
+ end
208
+ end
209
+
210
+ def related_match_reqs
211
+ field_reqs = related_field.match_requirements()
212
+ {
213
+ path: field_reqs[:path]
214
+ }
215
+ end
216
+
217
+ def match_reqs
218
+ {
219
+ base_path: base_match_reqs[:path],
220
+ predicate: @predicate,
221
+ related_path: related_match_reqs[:path]
222
+ }
223
+ end
224
+ end
225
+
226
+
227
+ class ActionBlocks::FieldBlock < ActionBlocks::BlockType
228
+ attr_accessor :parent_model, :field_type
229
+ block_type :field
230
+ sets :name
231
+ sets :label
232
+
233
+ def key
234
+ "field-#{@parent_model.id}-#{@id}"
235
+ end
236
+
237
+ def select_requirements(select_as_prefix = nil)
238
+ {
239
+ field_name: [select_as_prefix,@id].compact.join('_').to_sym,
240
+ path: [@id]
241
+ }
242
+ end
243
+
244
+ def match_requirements(select_as_prefix = nil)
245
+ {
246
+ path: [select_as_prefix, @id].compact
247
+ }
248
+ end
249
+
250
+ def before_build(parent, *args)
251
+ @parent_model = parent
252
+ end
253
+ end
254
+
255
+ class SummaryFieldBuilder < ActionBlocks::FieldBlock
256
+ include ActionBlocks::SummaryFieldAggregationFunctions
257
+ attr_accessor :parent_reference, :aggregation, :base_model, :related_model_id, :related_model
258
+ references :target_field
259
+ sets :aggregate
260
+ sets :aggregate_parameters
261
+ builds_many :filters, :filter, 'ActionBlocks::MatchConditionBuilder'
262
+
263
+ # validates :aggregate, inclusion: { in: %i[sum count avg min max string_agg] }
264
+
265
+ def before_build(parent, *args)
266
+ @base_model = parent.base_model # required lookup for match conditions
267
+ @related_model = parent.related_model # required lookup for match conditions
268
+ @related_model_id = parent.related_model_id # required lookup for match conditions
269
+
270
+ @field_type = 'summary'
271
+ @field_name = args[0]
272
+ @parent_reference = parent
273
+ @parent_model = @parent_reference.base_model
274
+
275
+ @aggregate = args[1]
276
+ end
277
+
278
+ def after_build(parent, *args)
279
+ @parent_model.fields.append(self)
280
+ end
281
+
282
+ # summary_reqs = [{
283
+ # type: :summary
284
+ # root_klass: Rate,
285
+ # select_req: { field_name: :count_of_rates, path: [:id], aggregate: ->(*args) { count(*args) } },
286
+ # match_reqs: [{
287
+ # base_path: [WorkOrder, :rate_sheet_id], # work_order
288
+ # predicate: :eq,
289
+ # related_path: [:rate_sheet_id] # rate
290
+ # }]
291
+ # }]
292
+ def select_requirements(select_as_prefix = nil)
293
+ path_and_function = instance_exec(&@aggregate)
294
+ path = path_and_function[:path]
295
+ function = path_and_function[:function]
296
+
297
+ {
298
+ type: :summary,
299
+ root_klass: parent_reference.related_model.active_model,
300
+ select_req: {
301
+ field_name: @field_name,
302
+ path: path,
303
+ function: function
304
+ },
305
+ match_reqs: parent_reference.match_reqs,
306
+ filter_reqs: filters.map(&:match_reqs)
307
+ }
308
+ end
309
+
310
+ def hashify(user)
311
+ {
312
+ type: :summary,
313
+ id: @id
314
+ }
315
+ end
316
+ end
317
+
318
+ class IdentityFieldBuilder < ActionBlocks::FieldBlock
319
+ def before_build(parent, *args)
320
+ @field_type = 'identity'
321
+ @id = :id
322
+ super(parent, :id)
323
+ end
324
+
325
+ def select_requirements(select_as_prefix = nil)
326
+ {
327
+ field_name: [select_as_prefix,@id].compact.join('_').to_sym,
328
+ path: [@id]
329
+ }
330
+ end
331
+
332
+ def match_requirements(select_as_prefix = nil)
333
+ {
334
+ path: [select_as_prefix, @id].compact
335
+ }
336
+ end
337
+
338
+ def hashify(user)
339
+ {
340
+ type: :identity,
341
+ id: @id,
342
+ key: @key
343
+ }
344
+ end
345
+ end
346
+
347
+
348
+ class ReferenceFieldBuilder < ActionBlocks::FieldBlock
349
+ attr_accessor :association_name
350
+ references :model
351
+ builds_many :lookups, :lookup, 'ActionBlocks::LookupFieldBuilder'
352
+
353
+ def before_build(parent, *args)
354
+ super(parent, *args)
355
+ @association_name = args[0]
356
+ @field_type = 'reference'
357
+ @model_key = "model-#{args[1] || args[0]}"
358
+ add_foreign_key_as_integer(parent)
359
+ end
360
+
361
+ def add_foreign_key_as_integer(parent)
362
+ if relation
363
+ parent.dsl_delegate.integer(relation.join_foreign_key.to_sym)
364
+ end
365
+ end
366
+
367
+ validate :validate_association_name
368
+
369
+ def relation
370
+ if @parent_model.active_model
371
+ @parent_model.active_model.reflections[@association_name.to_s]
372
+ end
373
+ end
374
+
375
+ def validate_association_name
376
+ # puts @parent_model.active_model.reflections.keys.inspect
377
+ # puts @association_name.to_s
378
+ unless relation
379
+ errors.add(:association_name, "Association #{@association_name.to_s} is not a valid relationship for model #{@parent_model.active_model.to_s}. Valid relations include: #{@parent_model.active_model.reflections.keys.inspect}")
380
+ end
381
+
382
+ unless relation && relation.belongs_to?
383
+ errors.add(:association_name, "Association #{@association_name.to_s} is not a valid belongs_to relationship")
384
+ end
385
+ end
386
+
387
+ def select_requirements(select_as_prefix = nil)
388
+ target_data_reqs = model.name_field.select_requirements([select_as_prefix, association_name].compact.join('_'))
389
+ {
390
+ field_name: association_name,
391
+ path: [association_name, target_data_reqs[:path]].flatten,
392
+ }
393
+ end
394
+
395
+ def match_requirements(select_as_prefix = nil)
396
+ {
397
+ path: [select_as_prefix, [association_name, relation.join_primary_key].join('_').to_sym].compact.flatten,
398
+ }
399
+ end
400
+
401
+ def hashify(user)
402
+ {
403
+ type: :reference,
404
+ id: @id
405
+ }
406
+ end
407
+ end
408
+
409
+ class LookupFieldBuilder < ActionBlocks::FieldBlock
410
+ attr_accessor :parent_reference
411
+ references :target_field
412
+
413
+ def before_build(parent, *args)
414
+ @field_type = 'lookup'
415
+ @parent_reference = parent
416
+ mk = @parent_reference.model_key.clone
417
+ mk['model-'] = ''
418
+ @target_field_key = "field-#{mk}-#{args[0]}"
419
+ @parent_model = @parent_reference.parent_model
420
+ @id = "#{parent_reference.id}_#{args[0]}"
421
+ end
422
+
423
+ def after_build(parent, *args)
424
+ @parent_model.fields.append(self)
425
+ end
426
+
427
+ def select_requirements(select_as_prefix = nil)
428
+ target_data_reqs = target_field.select_requirements([select_as_prefix, parent_reference.association_name].compact.join('_'))
429
+ {
430
+ field_name: target_data_reqs[:field_name],
431
+ path: [parent_reference.association_name, target_data_reqs[:path]].flatten,
432
+ }
433
+ end
434
+
435
+ def match_requirements(select_as_prefix = nil)
436
+ target_data_reqs = target_field.select_requirements([select_as_prefix, parent_reference.association_name].compact.join('_'))
437
+ {
438
+ path: [select_as_prefix, parent_reference.association_name, target_data_reqs[:path]].compact.flatten,
439
+ }
440
+ end
441
+
442
+ def hashify(user)
443
+ {
444
+ type: :lookup,
445
+ id: @id
446
+ }
447
+ end
448
+ end
449
+
450
+ class StringFieldBuilder < ActionBlocks::FieldBlock
451
+ def before_build(parent, *args)
452
+ super(parent, *args)
453
+ @field_type = 'string'
454
+ end
455
+
456
+ def hashify(user)
457
+ {
458
+ type: :string,
459
+ id: @id
460
+ }
461
+ end
462
+ end
463
+
464
+ class TextFieldBuilder < ActionBlocks::FieldBlock
465
+ def before_build(parent, *args)
466
+ super(parent, *args)
467
+ @field_type = 'text'
468
+ end
469
+
470
+ def hashify(user)
471
+ {
472
+ type: :text,
473
+ id: @id
474
+ }
475
+ end
476
+ end
477
+
478
+ class DatetimeFieldBuilder < ActionBlocks::FieldBlock
479
+ def before_build(parent, *args)
480
+ super(parent, *args)
481
+ @field_type = 'datetime'
482
+ end
483
+
484
+ def select_requirements(select_as_prefix = nil)
485
+ {
486
+ field_name: [select_as_prefix,@id].compact.join('_').to_sym,
487
+ path: [@id],
488
+ function: -> (*args) { timezone('US/Central', *args) }
489
+ }
490
+ end
491
+
492
+ def hashify(user)
493
+ {
494
+ type: :datetime,
495
+ id: @id
496
+ }
497
+ end
498
+ end
499
+
500
+ class AttachmentBuilder < ActionBlocks::FieldBlock
501
+ sets :attachment_type
502
+
503
+ validates :attachment_type, inclusion: { in: %w(image file),
504
+ message: "%{value} is not a valid attachment" }
505
+
506
+ def before_build(parent, *args)
507
+ super(parent, *args)
508
+ @attachment_type = 'image'
509
+ @field_type = 'attachment'
510
+ end
511
+
512
+ def select_requirements(select_as_prefix = nil)
513
+ {
514
+ field_name: [select_as_prefix,@id].compact.join('_').to_sym,
515
+ path: [:id]
516
+ }
517
+ end
518
+
519
+ def match_requirements(select_as_prefix = nil)
520
+ raise "should not use attachment in match conditions"
521
+ {
522
+ path: [select_as_prefix, @id].compact
523
+ }
524
+ end
525
+
526
+ def hashify(user)
527
+ {
528
+ type: :attachment,
529
+ attachment_type: @attachment_type,
530
+ id: @id,
531
+ model_key: @parent_model.key
532
+ }
533
+ end
534
+ end
535
+
536
+
537
+ class DateFieldBuilder < ActionBlocks::FieldBlock
538
+ def before_build(parent, *args)
539
+ super(parent, *args)
540
+ @field_type = 'date'
541
+ end
542
+
543
+ def hashify(user)
544
+ {
545
+ type: :date,
546
+ id: @id
547
+ }
548
+ end
549
+ end
550
+
551
+ class IntegerFieldBuilder < ActionBlocks::FieldBlock
552
+ def before_build(parent, *args)
553
+ super(parent, *args)
554
+ @field_type = 'integer'
555
+ end
556
+
557
+ def hashify(user)
558
+ {
559
+ type: :integer,
560
+ id: @id
561
+ }
562
+ end
563
+ end
564
+
565
+
566
+ end