super 0.0.12 → 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.
- checksums.yaml +4 -4
- data/.yardopts +0 -1
- data/README.md +38 -46
- data/app/assets/javascripts/super/application.js +5747 -3803
- data/app/assets/stylesheets/super/application.css +114686 -71486
- data/app/controllers/super/application_controller.rb +44 -37
- data/app/controllers/super/substructure_controller.rb +276 -0
- data/app/helpers/super/form_builder_helper.rb +7 -0
- data/app/views/layouts/super/application.html.erb +4 -19
- data/app/views/super/application/_collection_header.html.erb +2 -2
- data/app/views/super/application/_display_actions.html.erb +1 -1
- data/app/views/super/application/_display_index.html.erb +2 -2
- data/app/views/super/application/_display_show.html.erb +1 -1
- data/app/views/super/application/_filter.html.erb +62 -2
- data/app/views/super/application/_form_field.html.erb +5 -0
- data/app/views/super/application/_layout.html.erb +1 -1
- data/app/views/super/application/_member_header.html.erb +2 -2
- data/app/views/super/application/_pagination.html.erb +1 -1
- data/app/views/super/application/_site_footer.html.erb +3 -0
- data/app/views/super/application/_site_header.html.erb +17 -0
- data/app/views/super/application/_sort_expression.html.erb +2 -2
- data/app/views/super/feather/README.md +0 -1
- data/frontend/super-frontend/dist/application.css +114686 -71486
- data/frontend/super-frontend/dist/application.js +5747 -3803
- data/lib/generators/super/install/install_generator.rb +0 -16
- data/lib/generators/super/install/templates/base_controller.rb.tt +0 -8
- data/lib/generators/super/resource/templates/resources_controller.rb.tt +4 -4
- data/lib/generators/super/webpacker/webpacker_generator.rb +9 -5
- data/lib/super.rb +5 -2
- data/lib/super/action_inquirer.rb +18 -3
- data/lib/super/assets.rb +44 -23
- data/lib/super/badge.rb +60 -0
- data/lib/super/cheat.rb +17 -0
- data/lib/super/compatibility.rb +19 -0
- data/lib/super/display/guesser.rb +3 -1
- data/lib/super/display/schema_types.rb +51 -2
- data/lib/super/error.rb +2 -0
- data/lib/super/filter.rb +1 -1
- data/lib/super/filter/form_object.rb +74 -48
- data/lib/super/filter/guesser.rb +2 -0
- data/lib/super/filter/operator.rb +90 -64
- data/lib/super/filter/schema_types.rb +63 -80
- data/lib/super/form/builder.rb +110 -27
- data/lib/super/form/field_transcript.rb +43 -0
- data/lib/super/form/guesser.rb +10 -1
- data/lib/super/form/schema_types.rb +73 -16
- data/lib/super/link.rb +38 -32
- data/lib/super/link_builder.rb +58 -0
- data/lib/super/navigation.rb +164 -0
- data/lib/super/pagination.rb +2 -44
- data/lib/super/reset.rb +22 -0
- data/lib/super/schema.rb +4 -0
- data/lib/super/useful/builder.rb +4 -4
- data/lib/super/version.rb +1 -1
- data/lib/tasks/super/cheat.rake +9 -0
- metadata +14 -19
- data/CONTRIBUTING.md +0 -56
- data/Rakefile +0 -36
- data/app/views/super/application/_filter_type_select.html.erb +0 -21
- data/app/views/super/application/_filter_type_text.html.erb +0 -18
- data/app/views/super/application/_filter_type_timestamp.html.erb +0 -24
- data/app/views/super/application/_form_field_checkbox.html.erb +0 -1
- data/app/views/super/application/_form_field_rich_text_area.html.erb +0 -1
- data/app/views/super/application/_form_field_select.html.erb +0 -1
- data/app/views/super/application/_form_field_text.html.erb +0 -1
- data/app/views/super/feather/_chevron_down.html +0 -1
- data/docs/cheat.md +0 -41
- data/lib/super/controls.rb +0 -22
- data/lib/super/controls/optional.rb +0 -113
- data/lib/super/controls/steps.rb +0 -106
- data/lib/super/controls/view.rb +0 -55
- data/lib/super/navigation/automatic.rb +0 -73
data/lib/super/error.rb
CHANGED
data/lib/super/filter.rb
CHANGED
@@ -3,58 +3,95 @@
|
|
3
3
|
module Super
|
4
4
|
class Filter
|
5
5
|
class FormObject
|
6
|
-
class
|
7
|
-
def initialize(
|
8
|
-
@
|
6
|
+
class AttributeForm
|
7
|
+
def initialize(model:, field_name:, operators:, params:)
|
8
|
+
@model = model
|
9
9
|
@field_name = field_name
|
10
|
-
@
|
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
|
-
|
14
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
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
|
33
|
-
|
62
|
+
def identifier
|
63
|
+
@operator.identifier
|
34
64
|
end
|
35
65
|
|
36
|
-
def
|
37
|
-
|
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
|
-
|
43
|
-
|
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
|
56
|
-
|
57
|
-
|
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
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
71
|
-
|
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
|
-
|
74
|
-
|
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
|
data/lib/super/filter/guesser.rb
CHANGED
@@ -2,100 +2,126 @@
|
|
2
2
|
|
3
3
|
module Super
|
4
4
|
class Filter
|
5
|
-
|
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
|
27
|
-
|
28
|
-
registry["between"],
|
29
|
-
]
|
11
|
+
def [](key)
|
12
|
+
registry.fetch(key.to_s)
|
30
13
|
end
|
31
14
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
55
|
+
attr_reader :identifier
|
56
|
+
attr_reader :query_parameter_keys
|
57
|
+
attr_reader :humanized_operator_name
|
55
58
|
|
56
|
-
|
59
|
+
def behavior(&block)
|
60
|
+
self.behavior = block if block_given?
|
61
|
+
@behavior
|
62
|
+
end
|
57
63
|
|
58
|
-
|
59
|
-
|
60
|
-
end
|
61
|
-
end
|
64
|
+
define("eq", "Equals") do |relation, field, q:|
|
65
|
+
relation.where(field => q)
|
62
66
|
end
|
63
67
|
|
64
|
-
define("
|
65
|
-
relation.where(field =>
|
68
|
+
define("neq", "Doesn't equal") do |relation, field, q:|
|
69
|
+
relation.where.not(field => q)
|
66
70
|
end
|
67
71
|
|
68
|
-
define("
|
69
|
-
relation.where
|
72
|
+
define("null", "Is NULL") do |relation, field|
|
73
|
+
relation.where(field => nil)
|
70
74
|
end
|
71
75
|
|
72
|
-
define("
|
73
|
-
|
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("
|
78
|
-
|
79
|
-
|
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("
|
83
|
-
|
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("
|
88
|
-
|
89
|
-
|
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", "
|
93
|
-
if
|
94
|
-
relation = relation.where("#{field} >= ?",
|
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
|
98
|
-
relation = relation.where("#{field} <= ?",
|
123
|
+
if q1.present?
|
124
|
+
relation = relation.where("#{field} <= ?", q1)
|
99
125
|
end
|
100
126
|
|
101
127
|
relation
|
@@ -2,112 +2,95 @@
|
|
2
2
|
|
3
3
|
module Super
|
4
4
|
class Filter
|
5
|
-
# This schema type is used to configure the filtering form on your +#index+
|
6
|
-
# action.
|
7
|
-
#
|
8
|
-
# The +operators:+ keyword argument can be left out in each case. There is
|
9
|
-
# a default set of operators that are provided.
|
10
|
-
#
|
11
5
|
# Note: The constants under "Defined Under Namespace" are considered
|
12
6
|
# private.
|
13
|
-
#
|
14
|
-
# class MemberDashboard
|
15
|
-
# # ...
|
16
|
-
#
|
17
|
-
# def filter_schema
|
18
|
-
# Super::Filter.new do |fields, type|
|
19
|
-
# fields[:name] = type.text(operators: [
|
20
|
-
# Super::Filter::Operator.eq,
|
21
|
-
# Super::Filter::Operator.contain,
|
22
|
-
# Super::Filter::Operator.ncontain,
|
23
|
-
# Super::Filter::Operator.start,
|
24
|
-
# Super::Filter::Operator.end,
|
25
|
-
# ])
|
26
|
-
# fields[:rank] = type.select(collection: Member.ranks.values)
|
27
|
-
# fields[:position] = type.text(operators: [
|
28
|
-
# Super::Filter::Operator.eq,
|
29
|
-
# Super::Filter::Operator.neq,
|
30
|
-
# Super::Filter::Operator.contain,
|
31
|
-
# Super::Filter::Operator.ncontain,
|
32
|
-
# ])
|
33
|
-
# fields[:ship_id] = type.select(
|
34
|
-
# collection: Ship.all.map { |s| ["#{s.name} (Ship ##{s.id})", s.id] },
|
35
|
-
# )
|
36
|
-
# fields[:created_at] = type.timestamp
|
37
|
-
# fields[:updated_at] = type.timestamp
|
38
|
-
# end
|
39
|
-
# end
|
40
|
-
#
|
41
|
-
# # ...
|
42
|
-
# end
|
43
7
|
class SchemaTypes
|
44
|
-
class
|
45
|
-
|
46
|
-
@partial_path = partial_path
|
47
|
-
@operators = operators
|
48
|
-
end
|
8
|
+
class OperatorList
|
9
|
+
include Enumerable
|
49
10
|
|
50
|
-
|
11
|
+
def initialize(*new_operators)
|
12
|
+
@operators = {}
|
13
|
+
@operator_transcript = {}
|
14
|
+
@fallback_transcript = nil
|
51
15
|
|
52
|
-
|
53
|
-
@partial_path
|
16
|
+
push(*new_operators)
|
54
17
|
end
|
55
18
|
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
|
19
|
+
def push(*new_operators)
|
20
|
+
new_operators.flatten.map(&:dup).each do |new_operator|
|
21
|
+
new_identifier = new_operator.identifier.to_s
|
22
|
+
|
23
|
+
raise Error::AlreadyRegistered if @operators.key?(new_identifier)
|
60
24
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
25
|
+
@operators[new_identifier] = new_operator
|
26
|
+
end
|
27
|
+
|
28
|
+
nil
|
65
29
|
end
|
66
30
|
|
67
|
-
|
68
|
-
|
31
|
+
alias add push
|
32
|
+
|
33
|
+
def each
|
34
|
+
return enum_for(:each) if !block_given?
|
69
35
|
|
70
|
-
|
71
|
-
|
36
|
+
@operators.each do |identifier, operator|
|
37
|
+
yield(
|
38
|
+
OperatorWithFieldTranscript.new(
|
39
|
+
operator,
|
40
|
+
@operator_transcript[identifier] || @fallback_transcript
|
41
|
+
)
|
42
|
+
)
|
43
|
+
end
|
72
44
|
end
|
73
45
|
|
74
|
-
def
|
75
|
-
|
46
|
+
def transcribe(operator_identifier = nil)
|
47
|
+
transcript = Form::FieldTranscript.new
|
48
|
+
yield transcript
|
49
|
+
|
50
|
+
if operator_identifier.nil?
|
51
|
+
@fallback_transcript = transcript
|
52
|
+
else
|
53
|
+
@operator_transcript[operator_identifier.to_s] = transcript
|
54
|
+
end
|
55
|
+
|
56
|
+
self
|
76
57
|
end
|
77
58
|
end
|
78
59
|
|
79
|
-
class
|
80
|
-
def initialize(
|
81
|
-
@
|
60
|
+
class OperatorWithFieldTranscript
|
61
|
+
def initialize(operator, field_transcript)
|
62
|
+
@operator = operator
|
63
|
+
@field_transcript = field_transcript
|
82
64
|
end
|
83
65
|
|
84
|
-
|
85
|
-
|
86
|
-
def to_partial_path
|
87
|
-
"filter_type_timestamp"
|
66
|
+
Super::Filter::Operator.instance_methods(false).each do |name|
|
67
|
+
delegate name, to: :@operator
|
88
68
|
end
|
89
69
|
|
90
|
-
|
91
|
-
|
92
|
-
|
70
|
+
attr_reader :field_transcript
|
71
|
+
end
|
72
|
+
|
73
|
+
def use(*identifiers)
|
74
|
+
found_operators = identifiers.flatten.map { |id| Operator[id] }
|
75
|
+
OperatorList.new(*found_operators)
|
76
|
+
end
|
77
|
+
|
78
|
+
def select(collection)
|
79
|
+
use("eq", "null", "nnull")
|
80
|
+
.transcribe { |f| f.super.select(collection) }
|
93
81
|
end
|
94
82
|
|
95
|
-
def
|
96
|
-
|
97
|
-
collection: collection,
|
98
|
-
operators: operators
|
99
|
-
)
|
83
|
+
def text
|
84
|
+
use("contain", "ncontain", "blank", "nblank")
|
100
85
|
end
|
101
86
|
|
102
|
-
def
|
103
|
-
|
104
|
-
|
105
|
-
operators: operators
|
106
|
-
)
|
87
|
+
def timestamp
|
88
|
+
use("between", "null", "nnull")
|
89
|
+
.transcribe { |f| f.super.datetime_flatpickr }
|
107
90
|
end
|
108
91
|
|
109
|
-
def
|
110
|
-
|
92
|
+
def boolean
|
93
|
+
use("true", "false", "null", "nnull")
|
111
94
|
end
|
112
95
|
end
|
113
96
|
end
|