super 0.0.16 → 0.17.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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/super/application.js +146 -13
  3. data/app/assets/stylesheets/super/application.css +0 -1
  4. data/app/controllers/super/application_controller.rb +15 -2
  5. data/app/controllers/super/substructure_controller.rb +19 -8
  6. data/app/views/super/application/_display_actions.html.erb +1 -1
  7. data/app/views/super/application/_filter.html.erb +62 -2
  8. data/app/views/super/application/_member_header.html.erb +1 -1
  9. data/app/views/super/application/_sort_expression.html.erb +2 -2
  10. data/frontend/super-frontend/dist/application.css +0 -1
  11. data/frontend/super-frontend/dist/application.js +146 -13
  12. data/lib/super.rb +1 -0
  13. data/lib/super/display/guesser.rb +1 -1
  14. data/lib/super/display/schema_types.rb +0 -1
  15. data/lib/super/error.rb +2 -0
  16. data/lib/super/filter.rb +1 -1
  17. data/lib/super/filter/form_object.rb +74 -48
  18. data/lib/super/filter/guesser.rb +2 -0
  19. data/lib/super/filter/operator.rb +90 -64
  20. data/lib/super/filter/schema_types.rb +63 -80
  21. data/lib/super/form/builder.rb +6 -3
  22. data/lib/super/form/field_transcript.rb +43 -0
  23. data/lib/super/form/guesser.rb +1 -1
  24. data/lib/super/form/schema_types.rb +11 -20
  25. data/lib/super/link.rb +1 -1
  26. data/lib/super/schema.rb +4 -0
  27. data/lib/super/version.rb +1 -1
  28. metadata +4 -7
  29. data/app/views/super/application/_filter_type_select.html.erb +0 -21
  30. data/app/views/super/application/_filter_type_text.html.erb +0 -18
  31. data/app/views/super/application/_filter_type_timestamp.html.erb +0 -24
  32. data/app/views/super/application/_form_field_select.html.erb +0 -1
@@ -413,7 +413,7 @@ var Super = (function (exports) {
413
413
  }).call(this);
414
414
  (function () {
415
415
  var AcceptHeaders, CSRFProtection, createXHR, cspNonce, prepareOptions, processResponse;
416
- cspNonce = Rails.cspNonce, CSRFProtection = Rails.CSRFProtection, Rails.fire;
416
+ cspNonce = Rails.cspNonce, CSRFProtection = Rails.CSRFProtection;
417
417
  AcceptHeaders = {
418
418
  '*': '*/*',
419
419
  text: 'text/plain',
@@ -3975,7 +3975,7 @@ var Super = (function (exports) {
3975
3975
  };
3976
3976
  }
3977
3977
 
3978
- var _default$6 = /*#__PURE__*/function (_Controller) {
3978
+ var _default$8 = /*#__PURE__*/function (_Controller) {
3979
3979
  _inherits(_default, _Controller);
3980
3980
 
3981
3981
  var _super = _createSuper(_default);
@@ -4004,7 +4004,7 @@ var Super = (function (exports) {
4004
4004
  return _default;
4005
4005
  }(Controller);
4006
4006
 
4007
- var _default$5 = /*#__PURE__*/function (_Controller) {
4007
+ var _default$7 = /*#__PURE__*/function (_Controller) {
4008
4008
  _inherits(_default, _Controller);
4009
4009
 
4010
4010
  var _super = _createSuper(_default);
@@ -4040,7 +4040,7 @@ var Super = (function (exports) {
4040
4040
  return _default;
4041
4041
  }(Controller);
4042
4042
 
4043
- var _default$4 = /*#__PURE__*/function (_Controller) {
4043
+ var _default$6 = /*#__PURE__*/function (_Controller) {
4044
4044
  _inherits(_default, _Controller);
4045
4045
 
4046
4046
  var _super = _createSuper(_default);
@@ -4064,7 +4064,7 @@ var Super = (function (exports) {
4064
4064
  return _default;
4065
4065
  }(Controller);
4066
4066
 
4067
- var _default$3 = /*#__PURE__*/function (_Controller) {
4067
+ var _default$5 = /*#__PURE__*/function (_Controller) {
4068
4068
  _inherits(_default, _Controller);
4069
4069
 
4070
4070
  var _super = _createSuper(_default);
@@ -4094,7 +4094,7 @@ var Super = (function (exports) {
4094
4094
  return _default;
4095
4095
  }(Controller);
4096
4096
 
4097
- var _default$2 = /*#__PURE__*/function (_Controller) {
4097
+ var _default$4 = /*#__PURE__*/function (_Controller) {
4098
4098
  _inherits(_default, _Controller);
4099
4099
 
4100
4100
  var _super = _createSuper(_default);
@@ -6354,7 +6354,7 @@ var Super = (function (exports) {
6354
6354
  window.flatpickr = flatpickr;
6355
6355
  }
6356
6356
 
6357
- var _default$1 = /*#__PURE__*/function (_Controller) {
6357
+ var _default$3 = /*#__PURE__*/function (_Controller) {
6358
6358
  _inherits(_default, _Controller);
6359
6359
 
6360
6360
  var _super = _createSuper(_default);
@@ -6382,6 +6382,137 @@ var Super = (function (exports) {
6382
6382
  return _default;
6383
6383
  }(Controller);
6384
6384
 
6385
+ var _default$2 = /*#__PURE__*/function (_Controller) {
6386
+ _inherits(_default, _Controller);
6387
+
6388
+ var _super = _createSuper(_default);
6389
+
6390
+ function _default() {
6391
+ _classCallCheck(this, _default);
6392
+
6393
+ return _super.apply(this, arguments);
6394
+ }
6395
+
6396
+ _createClass(_default, [{
6397
+ key: "connect",
6398
+ value: function connect() {
6399
+ var _this = this;
6400
+
6401
+ this.tabTargets.forEach(function (tab) {
6402
+ tab.tabContainer = _this;
6403
+ });
6404
+ }
6405
+ }, {
6406
+ key: "activeTabIdentifier",
6407
+ get: function get() {
6408
+ return this.controlTarget.value;
6409
+ }
6410
+ }, {
6411
+ key: "change",
6412
+ value: function change(event) {
6413
+ this.update(event.target.value);
6414
+ }
6415
+ }, {
6416
+ key: "update",
6417
+ value: function update(newActiveTabIdentifier) {
6418
+ var _this2 = this;
6419
+
6420
+ this.tabTargets.forEach(function (tab) {
6421
+ var tabController = _this2.application.getControllerForElementAndIdentifier(tab, _this2.tabControllerNameValue);
6422
+
6423
+ if (tab.dataset[_this2.tabIdentifierGetterValue] == newActiveTabIdentifier) {
6424
+ tabController.show();
6425
+ } else {
6426
+ tabController.hide();
6427
+ }
6428
+ });
6429
+ }
6430
+ }], [{
6431
+ key: "targets",
6432
+ get: function get() {
6433
+ return ["control", "tab"];
6434
+ }
6435
+ }, {
6436
+ key: "values",
6437
+ get: function get() {
6438
+ return {
6439
+ tabIdentifierGetter: String,
6440
+ tabControllerName: String
6441
+ };
6442
+ }
6443
+ }]);
6444
+
6445
+ return _default;
6446
+ }(Controller);
6447
+
6448
+ var _default$1 = /*#__PURE__*/function (_Controller) {
6449
+ _inherits(_default, _Controller);
6450
+
6451
+ var _super = _createSuper(_default);
6452
+
6453
+ function _default() {
6454
+ _classCallCheck(this, _default);
6455
+
6456
+ return _super.apply(this, arguments);
6457
+ }
6458
+
6459
+ _createClass(_default, [{
6460
+ key: "connect",
6461
+ value: function connect() {
6462
+ var tabContainer = this.element[this.tabContainerGetterValue];
6463
+
6464
+ if (tabContainer.activeTabIdentifier === this.identifierValue) {
6465
+ this.show();
6466
+ } else {
6467
+ this.hide();
6468
+ }
6469
+ }
6470
+ }, {
6471
+ key: "toggle",
6472
+ value: function toggle() {
6473
+ if (this.hasContentTarget) {
6474
+ this.hide();
6475
+ } else {
6476
+ this.show();
6477
+ }
6478
+ }
6479
+ }, {
6480
+ key: "show",
6481
+ value: function show() {
6482
+ if (this.hasContentTarget) {
6483
+ return;
6484
+ }
6485
+
6486
+ var pocketContent = this.pocketTarget.content.cloneNode(true);
6487
+ this.element.appendChild(pocketContent);
6488
+ }
6489
+ }, {
6490
+ key: "hide",
6491
+ value: function hide() {
6492
+ if (!this.hasContentTarget) {
6493
+ return;
6494
+ }
6495
+
6496
+ this.contentTarget.remove();
6497
+ }
6498
+ }], [{
6499
+ key: "targets",
6500
+ get: function get() {
6501
+ return ["pocket", "content"];
6502
+ }
6503
+ }, {
6504
+ key: "values",
6505
+ get: function get() {
6506
+ return {
6507
+ identifier: String,
6508
+ tabContainerGetter: String
6509
+ };
6510
+ }
6511
+ }]);
6512
+
6513
+ return _default;
6514
+ }(Controller);
6515
+
6385
6516
  var _default = /*#__PURE__*/function (_Controller) {
6386
6517
  _inherits(_default, _Controller);
6387
6518
 
@@ -6412,12 +6543,14 @@ var Super = (function (exports) {
6412
6543
  }(Controller);
6413
6544
 
6414
6545
  var StimulusApplication = Application.start();
6415
- StimulusApplication.register("apply-template", _default$6);
6416
- StimulusApplication.register("clean-filter-param", _default$5);
6417
- StimulusApplication.register("clean-filter-params", _default$4);
6418
- StimulusApplication.register("click-outside-to-close", _default$3);
6419
- StimulusApplication.register("delete", _default$2);
6420
- StimulusApplication.register("flatpickr", _default$1);
6546
+ StimulusApplication.register("apply-template", _default$8);
6547
+ StimulusApplication.register("clean-filter-param", _default$7);
6548
+ StimulusApplication.register("clean-filter-params", _default$6);
6549
+ StimulusApplication.register("click-outside-to-close", _default$5);
6550
+ StimulusApplication.register("delete", _default$4);
6551
+ StimulusApplication.register("flatpickr", _default$3);
6552
+ StimulusApplication.register("tab-container", _default$2);
6553
+ StimulusApplication.register("tab", _default$1);
6421
6554
  StimulusApplication.register("toggle-pending-destruction", _default);
6422
6555
 
6423
6556
  exports.StimulusApplication = StimulusApplication;
data/lib/super.rb CHANGED
@@ -25,6 +25,7 @@ require "super/filter/operator"
25
25
  require "super/filter/schema_types"
26
26
  require "super/form"
27
27
  require "super/form/builder"
28
+ require "super/form/field_transcript"
28
29
  require "super/form/guesser"
29
30
  require "super/form/inline_errors"
30
31
  require "super/form/schema_types"
@@ -30,7 +30,7 @@ module Super
30
30
  when :time
31
31
  @type.time
32
32
  else
33
- @type.text
33
+ @type.string
34
34
  end
35
35
  end
36
36
  end
@@ -130,7 +130,6 @@ module Super
130
130
  end
131
131
 
132
132
  def string; real(&:to_s); end
133
- alias text string
134
133
 
135
134
  def timestamp; real(&:to_s); end
136
135
  def time; real { |value| value.strftime("%H:%M:%S") }; end
data/lib/super/error.rb CHANGED
@@ -28,6 +28,8 @@ module Super
28
28
  # a more specific error
29
29
  class Initalization < Error; end
30
30
  class ArgumentError < Error; end
31
+ class AlreadyRegistered < Error; end
32
+ class AlreadyTranscribed < Error; end
31
33
 
32
34
  class Enum < Error
33
35
  class ImpossibleValue < Enum; end
data/lib/super/filter.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module Super
4
4
  class Filter
5
5
  def initialize
6
- @schema_type = Filter::SchemaTypes.new
6
+ @schema_type = SchemaTypes.new
7
7
  @fields = Schema::Fields.new
8
8
 
9
9
  yield(@fields, @schema_type)
@@ -3,58 +3,95 @@
3
3
  module Super
4
4
  class Filter
5
5
  class FormObject
6
- class FilterFormField
7
- def initialize(humanized_field_name:, field_name:, type:, params:)
8
- @humanized_field_name = humanized_field_name
6
+ class AttributeForm
7
+ def initialize(model:, field_name:, operators:, params:)
8
+ @model = model
9
9
  @field_name = field_name
10
- @field_type = type
11
- @params = params
10
+ @operators = operators
11
+ @params = params || {}
12
+ end
13
+
14
+ attr_reader :model
15
+ attr_reader :field_name
16
+ attr_reader :operators
17
+ attr_reader :params
18
+
19
+ def each_operator
20
+ return enum_for(:each_operator) if !block_given?
21
+
22
+ @operators.each do |operator|
23
+ operator_form = OperatorForm.new(
24
+ operator: operator,
25
+ params: @params[operator.identifier]
26
+ )
27
+
28
+ yield(operator_form)
29
+ end
30
+ end
31
+
32
+ def humanized_attribute_name
33
+ @model.human_attribute_name(@field_name)
34
+ end
35
+ end
36
+
37
+ class OperatorForm
38
+ NULLARY = :_apply
39
+
40
+ def initialize(operator:, params:)
41
+ @operator = operator
42
+ @params = params || {}
43
+ query_parameter_keys = operator.query_parameter_keys
44
+ query_parameter_keys = [NULLARY] if query_parameter_keys.empty?
12
45
  @specified_values =
13
- type.q
14
- .map do |query_field_name|
15
- [
16
- query_field_name,
17
- (params || {})[query_field_name],
18
- ]
19
- end
20
- .to_h
46
+ query_parameter_keys
47
+ .map { |key| [key, @params[key].presence&.strip] }
48
+ .to_h
21
49
 
22
50
  @specified_values.each do |key, value|
23
51
  define_singleton_method(key) { value }
24
52
  end
25
53
  end
26
54
 
27
- attr_reader :humanized_field_name
28
- attr_reader :field_name
29
- attr_reader :field_type
55
+ def specified?
56
+ @specified_values.any? { |_key, value| value }
57
+ end
58
+
59
+ attr_reader :operator
30
60
  attr_reader :specified_values
31
61
 
32
- def op
33
- (@params || {})[:op]
62
+ def identifier
63
+ @operator.identifier
34
64
  end
35
65
 
36
- def operators
37
- @field_type.operators
38
- .map { |o| [o.name, o.identifier] }
39
- .to_h
40
- end
66
+ def each_field
67
+ return enum_for(:each_field) if !block_given?
41
68
 
42
- def to_partial_path
43
- @field_type.to_partial_path
69
+ @specified_values.each do |key, _value|
70
+ yield(key)
71
+ end
44
72
  end
45
73
  end
46
74
 
47
75
  def initialize(model:, params:, schema:)
48
76
  @model = model
49
- @params = params
77
+ @params = params || {}
50
78
  @schema = schema
51
79
 
52
80
  @form_fields = {}
53
81
  end
54
82
 
55
- def each_field
56
- @schema.fields.each do |field_name, _field_type|
57
- yield(form_field_for(field_name))
83
+ def each_attribute
84
+ return enum_for(:each_attribute) if !block_given?
85
+
86
+ @schema.fields.each do |field_name, field_operators|
87
+ attribute_form = AttributeForm.new(
88
+ model: @model,
89
+ field_name: field_name,
90
+ operators: field_operators,
91
+ params: @params[field_name]
92
+ )
93
+
94
+ yield(attribute_form)
58
95
  end
59
96
  end
60
97
 
@@ -63,32 +100,21 @@ module Super
63
100
  end
64
101
 
65
102
  def apply_changes(relation)
66
- each_field do |form_field|
67
- next if form_field.specified_values.values.map(&:to_s).map(&:strip).all? { |specified_value| specified_value == "" }
68
- next if !Super::Filter::Operator.registry.key?(form_field.op)
103
+ each_attribute do |attribute_form|
104
+ attribute_form.each_operator do |operator_form|
105
+ next if operator_form.specified_values.values.map(&:to_s).map(&:presence).none?
69
106
 
70
- operator = Super::Filter::Operator.registry[form_field.op]
71
- updated_relation = operator.filter(relation, form_field.field_name, *form_field.specified_values.values)
107
+ operator_behavior = operator_form.operator.behavior
108
+ updated_relation = operator_behavior.call(relation, attribute_form.field_name, **operator_form.specified_values)
72
109
 
73
- if updated_relation.is_a?(ActiveRecord::Relation)
74
- relation = updated_relation
110
+ if updated_relation.is_a?(ActiveRecord::Relation)
111
+ relation = updated_relation
112
+ end
75
113
  end
76
114
  end
77
115
 
78
116
  relation
79
117
  end
80
-
81
- private
82
-
83
- def form_field_for(field_name)
84
- @form_fields[field_name] ||=
85
- FilterFormField.new(
86
- humanized_field_name: @model.human_attribute_name(field_name),
87
- field_name: field_name,
88
- type: @schema.fields[field_name],
89
- params: (@params || {})[field_name]
90
- )
91
- end
92
118
  end
93
119
  end
94
120
  end
@@ -23,6 +23,8 @@ module Super
23
23
  case type
24
24
  when :datetime
25
25
  @type.timestamp
26
+ when :boolean
27
+ @type.boolean
26
28
  else
27
29
  @type.text
28
30
  end
@@ -2,100 +2,126 @@
2
2
 
3
3
  module Super
4
4
  class Filter
5
- module Operator
6
- class Definition
7
- def initialize(identifier, name, filter)
8
- @identifier = identifier
9
- @name = name
10
- @filter = filter
11
- end
12
-
13
- attr_reader :identifier
14
- attr_reader :name
15
-
16
- def filter(*args)
17
- @filter.call(args)
18
- end
19
- end
20
-
5
+ class Operator
21
6
  class << self
22
7
  def registry
23
8
  @registry ||= {}
24
9
  end
25
10
 
26
- def range_defaults
27
- [
28
- registry["between"],
29
- ]
11
+ def [](key)
12
+ registry.fetch(key.to_s)
30
13
  end
31
14
 
32
- def select_defaults
33
- [
34
- registry["eq"],
35
- registry["neq"],
36
- ]
15
+ def register(identifier, operator)
16
+ identifier = identifier.to_s
17
+ if registry.key?(identifier)
18
+ raise Error::AlreadyRegistered, "Already registered: #{identifier}"
19
+ end
20
+
21
+ registry[identifier] = operator
37
22
  end
38
23
 
39
- def text_defaults
40
- [
41
- registry["eq"],
42
- registry["neq"],
43
- registry["contain"],
44
- registry["ncontain"],
45
- registry["start"],
46
- registry["end"],
47
- ]
24
+ def define(identifier, display, &block)
25
+ operator = new(identifier, display, &block).freeze
26
+ register(identifier, operator)
27
+ operator
48
28
  end
29
+ end
49
30
 
50
- def define(identifier, name, &filter)
51
- identifier = identifier.to_s
52
- name = name.to_s
31
+ def initialize(identifier, display, &behavior)
32
+ @identifier = identifier.to_s
33
+ @humanized_operator_name = display
34
+ self.behavior = behavior
35
+ end
36
+
37
+ def behavior=(behavior)
38
+ behavior_params = behavior.parameters
39
+ if behavior_params.size < 2
40
+ raise Error::ArgumentError, "Operator behavior must include `column_name` and `relation`"
41
+ end
42
+ if behavior_params[0][0] != :req && behavior_params[0][0] != :opt
43
+ raise Error::ArgumentError, "First argument `relation` must be a required, positional argument"
44
+ end
45
+ if behavior_params[1][0] != :req && behavior_params[1][0] != :opt
46
+ raise Error::ArgumentError, "Second argument `column_name` must be a required, positional argument"
47
+ end
48
+ if !behavior_params[2..-1].all? { |(type, _name)| type == :keyreq }
49
+ raise Error::ArgumentError, "All query parameter keys must be required, keyword arguments"
50
+ end
51
+ @behavior = behavior
52
+ @query_parameter_keys = behavior_params[2..-1].map(&:last)
53
+ end
53
54
 
54
- definition = Definition.new(identifier, name, filter)
55
+ attr_reader :identifier
56
+ attr_reader :query_parameter_keys
57
+ attr_reader :humanized_operator_name
55
58
 
56
- registry[identifier] = definition
59
+ def behavior(&block)
60
+ self.behavior = block if block_given?
61
+ @behavior
62
+ end
57
63
 
58
- define_singleton_method(identifier) do
59
- registry[identifier]
60
- end
61
- end
64
+ define("eq", "Equals") do |relation, field, q:|
65
+ relation.where(field => q)
62
66
  end
63
67
 
64
- define("eq", "equals") do |relation, field, query|
65
- relation.where(field => query)
68
+ define("neq", "Doesn't equal") do |relation, field, q:|
69
+ relation.where.not(field => q)
66
70
  end
67
71
 
68
- define("neq", "doesn't equal") do |relation, field, query|
69
- relation.where.not(field => query)
72
+ define("null", "Is NULL") do |relation, field|
73
+ relation.where(field => nil)
70
74
  end
71
75
 
72
- define("contain", "contains") do |relation, field, query|
73
- query = "%#{Compatability.sanitize_sql_like(query)}%"
74
- relation.where("#{field} LIKE ?", "%#{query}%")
76
+ define("nnull", "Isn't NULL") do |relation, field|
77
+ relation.where.not(field => nil)
75
78
  end
76
79
 
77
- define("ncontain", "doesn't contain") do |relation, field, query|
78
- query = "%#{Compatability.sanitize_sql_like(query)}%"
79
- relation.where("#{field} NOT LIKE ?", query)
80
+ define("true", "Is true") do |relation, field|
81
+ relation.where(field => true)
82
+ end
83
+
84
+ define("false", "Is false") do |relation, field|
85
+ relation.where(field => false)
86
+ end
87
+
88
+ define("empty", "Is empty") do |relation, field|
89
+ relation.where(field => "")
80
90
  end
81
91
 
82
- define("start", "starts with") do |relation, field, query|
83
- query = "#{Compatability.sanitize_sql_like(query)}%"
84
- relation.where("#{field} LIKE ?", query)
92
+ define("nempty", "Isn't empty") do |relation, field|
93
+ relation.where.not(field => "")
85
94
  end
86
95
 
87
- define("end", "ends with") do |relation, field, query|
88
- query = "%#{Compatability.sanitize_sql_like(query)}"
89
- relation.where("#{field} LIKE ?", query)
96
+ define("blank", "Is blank") do |relation, field|
97
+ relation.where(field => [nil, ""])
98
+ end
99
+
100
+ define("nblank", "Isn't blank") do |relation, field|
101
+ relation.where.not(field => [nil, ""])
102
+ end
103
+
104
+ define("contain", "Contains") do |relation, field, q:|
105
+ query = "%#{Compatability.sanitize_sql_like(q)}%"
106
+ if relation.connection.adapter_name == "PostgreSQL"
107
+ relation.where("#{field} ILIKE ?", query)
108
+ else
109
+ relation.where("#{field} LIKE ?", query)
110
+ end
111
+ end
112
+
113
+ define("ncontain", "Doesn't contain") do |relation, field, q:|
114
+ query = "%#{Compatability.sanitize_sql_like(q)}%"
115
+ relation.where("#{field} NOT LIKE ?", query)
90
116
  end
91
117
 
92
- define("between", "between") do |relation, field, query0, query1|
93
- if query0.present?
94
- relation = relation.where("#{field} >= ?", query0)
118
+ define("between", "Between") do |relation, field, q0:, q1:|
119
+ if q0.present?
120
+ relation = relation.where("#{field} >= ?", q0)
95
121
  end
96
122
 
97
- if query1.present?
98
- relation = relation.where("#{field} <= ?", query1)
123
+ if q1.present?
124
+ relation = relation.where("#{field} <= ?", q1)
99
125
  end
100
126
 
101
127
  relation