super 0.0.6 → 0.0.7

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -6
  3. data/app/controllers/super/application_controller.rb +6 -3
  4. data/app/views/super/application/_filter.html.erb +14 -0
  5. data/app/views/super/application/_filter_type_select.html.erb +31 -0
  6. data/app/views/super/application/_filter_type_text.html.erb +22 -0
  7. data/app/views/super/application/_filter_type_timestamp.html.erb +35 -0
  8. data/app/views/super/application/_super_layout.html.erb +5 -5
  9. data/app/views/super/application/_super_pagination.html.erb +16 -0
  10. data/app/views/super/application/_super_panel.html.erb +1 -1
  11. data/app/views/super/application/_super_schema_display_index.html.erb +8 -23
  12. data/app/views/super/application/_super_schema_display_show.html.erb +1 -1
  13. data/app/views/super/application/edit.html.erb +1 -6
  14. data/app/views/super/application/index.html.erb +1 -6
  15. data/app/views/super/application/new.html.erb +1 -6
  16. data/app/views/super/application/show.html.erb +1 -6
  17. data/lib/super.rb +8 -2
  18. data/lib/super/compatibility.rb +13 -1
  19. data/lib/super/configuration.rb +7 -0
  20. data/lib/super/controls/optional.rb +0 -11
  21. data/lib/super/controls/steps.rb +44 -31
  22. data/lib/super/display/schema_types.rb +31 -1
  23. data/lib/super/engine.rb +6 -0
  24. data/lib/super/filter.rb +137 -0
  25. data/lib/super/filter/operator.rb +103 -0
  26. data/lib/super/filter/schema_types.rb +118 -0
  27. data/lib/super/pagination.rb +61 -0
  28. data/lib/super/partial.rb +12 -0
  29. data/lib/super/plugin.rb +34 -63
  30. data/lib/super/schema.rb +1 -0
  31. data/lib/super/version.rb +1 -1
  32. data/lib/super/view_helper.rb +1 -1
  33. metadata +25 -4
  34. data/app/helpers/super/application_helper.rb +0 -39
@@ -15,37 +15,6 @@ module Super
15
15
  end
16
16
  end
17
17
 
18
- # Sets up pagination
19
- #
20
- # @param action [ActionInquirer]
21
- # @param records [ActiveRecord::Relation]
22
- # @param query_params [Hash]
23
- # @return [Pagination]
24
- def initialize_pagination(action:, records:, query_params:)
25
- default_for(:initialize_pagination, action: action, records: records, query_params: query_params) do
26
- Pagination.new(
27
- total_count: records.size,
28
- limit: records_per_page(action: action, query_params: query_params),
29
- query_params: query_params,
30
- page_query_param: :page
31
- )
32
- end
33
- end
34
-
35
- # Paginates
36
- #
37
- # @param action [ActionInquirer]
38
- # @param records [ActiveRecord::Relation]
39
- # @param pagination [Pagination]
40
- # @return [ActiveRecord::Relation]
41
- def paginate_records(action:, records:, pagination:)
42
- default_for(:paginate_records, action: action, records: records, pagination: pagination) do
43
- records
44
- .limit(pagination.limit)
45
- .offset(pagination.offset)
46
- end
47
- end
48
-
49
18
  # Loads a record using `#scope`
50
19
  #
51
20
  # @param action [ActionInquirer]
@@ -110,6 +79,50 @@ module Super
110
79
  record.destroy
111
80
  end
112
81
  end
82
+
83
+ def build_index_view
84
+ Super::Layout.new(
85
+ mains: [
86
+ Super::Panel.new(
87
+ Super::Partial.new("collection_header"),
88
+ :@display
89
+ ),
90
+ ]
91
+ )
92
+ end
93
+
94
+ def build_show_view
95
+ Super::Layout.new(
96
+ mains: [
97
+ Super::Panel.new(
98
+ Super::Partial.new("member_header"),
99
+ :@display
100
+ ),
101
+ ]
102
+ )
103
+ end
104
+
105
+ def build_new_view
106
+ Super::Layout.new(
107
+ mains: [
108
+ Super::Panel.new(
109
+ Super::Partial.new("collection_header"),
110
+ :@form
111
+ ),
112
+ ]
113
+ )
114
+ end
115
+
116
+ def build_edit_view
117
+ Super::Layout.new(
118
+ mains: [
119
+ Super::Panel.new(
120
+ Super::Partial.new("member_header"),
121
+ :@form
122
+ ),
123
+ ]
124
+ )
125
+ end
113
126
  end
114
127
  end
115
128
  end
@@ -76,7 +76,37 @@ module Super
76
76
  end
77
77
 
78
78
  def to_partial_path
79
- "super_schema_display_#{@action_inquirer.action}"
79
+ if @action_inquirer.index?
80
+ "super_schema_display_index"
81
+ elsif @action_inquirer.show?
82
+ "super_schema_display_show"
83
+ else
84
+ "super_schema_display_#{@action_inquirer.action}"
85
+ end
86
+ end
87
+
88
+ # @private
89
+ def render_field(template:, record:, column:)
90
+ formatter = @fields[column]
91
+
92
+ formatted =
93
+ if formatter.real?
94
+ value = record.public_send(column)
95
+ formatter.present(value)
96
+ else
97
+ formatter.present
98
+ end
99
+
100
+ if formatted.respond_to?(:to_partial_path)
101
+ if formatted.respond_to?(:locals)
102
+ formatted.locals[:record] ||= record
103
+ template.render(formatted, formatted.locals)
104
+ else
105
+ template.render(formatted)
106
+ end
107
+ else
108
+ formatted
109
+ end
80
110
  end
81
111
  end
82
112
  end
@@ -6,5 +6,11 @@ module Super
6
6
  app.config.assets.precompile << "config/super_manifest.js"
7
7
  end
8
8
  end
9
+
10
+ config.to_prepare do
11
+ Super::Plugin::Registry.controller.ordered do |klass, method_name|
12
+ Super::ApplicationController.public_send(method_name, klass)
13
+ end
14
+ end
9
15
  end
10
16
  end
@@ -0,0 +1,137 @@
1
+ module Super
2
+ class Filter
3
+ module ControllerMethods
4
+ def index
5
+ super
6
+ @filter_form = controls.initialize_filtering(params: params, query_params: request.GET)
7
+ @records = controls.filter_records(filter_form: @filter_form, records: @records)
8
+ @view.asides.push(:@filter_form)
9
+ end
10
+ end
11
+
12
+ class FilterFormField
13
+ def initialize(humanized_field_name:, field_name:, type:, params:)
14
+ @humanized_field_name = humanized_field_name
15
+ @field_name = field_name
16
+ @field_type = type
17
+ @params = params
18
+ @specified_values =
19
+ type.q
20
+ .map do |query_field_name|
21
+ [
22
+ query_field_name,
23
+ (params || {})[query_field_name],
24
+ ]
25
+ end
26
+ .to_h
27
+
28
+ @specified_values.each do |key, value|
29
+ define_singleton_method(key) { value }
30
+ end
31
+ end
32
+
33
+ attr_reader :humanized_field_name
34
+ attr_reader :field_name
35
+ attr_reader :field_type
36
+ attr_reader :specified_values
37
+
38
+ def op
39
+ (@params || {})[:op]
40
+ end
41
+
42
+ def operators
43
+ @field_type.operators
44
+ .map { |o| [o.name, o.identifier] }
45
+ .to_h
46
+ end
47
+
48
+ def to_partial_path
49
+ @field_type.to_partial_path
50
+ end
51
+ end
52
+
53
+ def initialize(model:, url:, schema:, params:)
54
+ @model = model
55
+ @url = url
56
+ @schema = schema
57
+ @params = params
58
+
59
+ @form_fields = {}
60
+ end
61
+
62
+ def each_field
63
+ @schema.fields.each do |field_name, _field_type|
64
+ yield(form_field_for(field_name))
65
+ end
66
+ end
67
+
68
+ def url
69
+ @url
70
+ end
71
+
72
+ def to_partial_path
73
+ "filter"
74
+ end
75
+
76
+ def to_search_query(relation)
77
+ each_field do |form_field|
78
+ next if form_field.specified_values.values.map(&:to_s).map(&:strip).all? { |specified_value| specified_value == "" }
79
+ next if !Super::Filter::Operator.registry.key?(form_field.op)
80
+
81
+ operator = Super::Filter::Operator.registry[form_field.op]
82
+ updated_relation = operator.filter(relation, form_field.field_name, *form_field.specified_values.values)
83
+
84
+ if updated_relation.is_a?(ActiveRecord::Relation)
85
+ relation = updated_relation
86
+ end
87
+ end
88
+
89
+ relation
90
+ end
91
+
92
+ private
93
+
94
+ def form_field_for(field_name)
95
+ @form_fields[field_name] ||=
96
+ FilterFormField.new(
97
+ humanized_field_name: @model.human_attribute_name(field_name),
98
+ field_name: field_name,
99
+ type: @schema.fields[field_name],
100
+ params: (@params || {})[field_name]
101
+ )
102
+ end
103
+ end
104
+
105
+ class Controls
106
+ module Optional
107
+ def filters_enabled?
108
+ actual.respond_to?(:filter_schema)
109
+ end
110
+
111
+ def filter_schema
112
+ actual.filter_schema
113
+ end
114
+ end
115
+
116
+ module Steps
117
+ def initialize_filtering(params:, query_params:)
118
+ if filters_enabled?
119
+ Super::Filter.new(
120
+ model: model,
121
+ schema: filter_schema,
122
+ params: params[:q],
123
+ url: query_params
124
+ )
125
+ end
126
+ end
127
+
128
+ def filter_records(filter_form:, records:)
129
+ if filters_enabled? && records
130
+ filter_form.to_search_query(records)
131
+ else
132
+ records
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,103 @@
1
+ module Super
2
+ class Filter
3
+ module Operator
4
+ class Definition
5
+ def initialize(identifier, name, filter)
6
+ @identifier = identifier
7
+ @name = name
8
+ @filter = filter
9
+ end
10
+
11
+ attr_reader :identifier
12
+ attr_reader :name
13
+
14
+ def filter(*args)
15
+ @filter.call(args)
16
+ end
17
+ end
18
+
19
+ class << self
20
+ def registry
21
+ @registry ||= {}
22
+ end
23
+
24
+ def range_defaults
25
+ [
26
+ registry["between"],
27
+ ]
28
+ end
29
+
30
+ def select_defaults
31
+ [
32
+ registry["eq"],
33
+ registry["neq"],
34
+ ]
35
+ end
36
+
37
+ def text_defaults
38
+ [
39
+ registry["eq"],
40
+ registry["neq"],
41
+ registry["contain"],
42
+ registry["ncontain"],
43
+ registry["start"],
44
+ registry["end"],
45
+ ]
46
+ end
47
+
48
+ def define(identifier, name, &filter)
49
+ identifier = identifier.to_s
50
+ name = name.to_s
51
+
52
+ definition = Definition.new(identifier, name, filter)
53
+
54
+ registry[identifier] = definition
55
+
56
+ define_singleton_method(identifier) do
57
+ registry[identifier]
58
+ end
59
+ end
60
+ end
61
+
62
+ define("eq", "equals") do |relation, field, query|
63
+ relation.where(field => query)
64
+ end
65
+
66
+ define("neq", "doesn't equal") do |relation, field, query|
67
+ relation.where.not(field => query)
68
+ end
69
+
70
+ define("contain", "contains") do |relation, field, query|
71
+ query = "%#{Compatability.sanitize_sql_like(query)}%"
72
+ relation.where("#{field} LIKE ?", "%#{query}%")
73
+ end
74
+
75
+ define("ncontain", "doesn't contain") do |relation, field, query|
76
+ query = "%#{Compatability.sanitize_sql_like(query)}%"
77
+ relation.where("#{field} NOT LIKE ?", query)
78
+ end
79
+
80
+ define("start", "starts with") do |relation, field, query|
81
+ query = "#{Compatability.sanitize_sql_like(query)}%"
82
+ relation.where("#{field} LIKE ?", query)
83
+ end
84
+
85
+ define("end", "ends with") do |relation, field, query|
86
+ query = "%#{Compatability.sanitize_sql_like(query)}"
87
+ relation.where("#{field} LIKE ?", query)
88
+ end
89
+
90
+ define("between", "between") do |relation, field, query0, query1|
91
+ if query0.present?
92
+ relation = relation.where("#{field} >= ?", query0)
93
+ end
94
+
95
+ if query1.present?
96
+ relation = relation.where("#{field} <= ?", query1)
97
+ end
98
+
99
+ relation
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,118 @@
1
+ module Super
2
+ class Filter
3
+ # This schema type is used to configure the filtering form on your +#index+
4
+ # action.
5
+ #
6
+ # The +operators:+ keyword argument can be left out in each case. There is
7
+ # a default set of operators that are provided.
8
+ #
9
+ # Note: The constants under "Defined Under Namespace" are considered
10
+ # private.
11
+ #
12
+ # class MemberDashboard
13
+ # # ...
14
+ #
15
+ # def filter_schema
16
+ # Super::Schema.new(Super::Filter::SchemaTypes.new) do |fields, type|
17
+ # fields[:name] = type.text(operators: [
18
+ # Super::Filter::Operator.eq,
19
+ # Super::Filter::Operator.contain,
20
+ # Super::Filter::Operator.ncontain,
21
+ # Super::Filter::Operator.start,
22
+ # Super::Filter::Operator.end,
23
+ # ])
24
+ # fields[:rank] = type.select(collection: Member.ranks.values)
25
+ # fields[:position] = type.text(operators: [
26
+ # Super::Filter::Operator.eq,
27
+ # Super::Filter::Operator.neq,
28
+ # Super::Filter::Operator.contain,
29
+ # Super::Filter::Operator.ncontain,
30
+ # ])
31
+ # fields[:ship_id] = type.select(
32
+ # collection: Ship.all.map { |s| ["#{s.name} (Ship ##{s.id})", s.id] },
33
+ # )
34
+ # fields[:created_at] = type.timestamp
35
+ # fields[:updated_at] = type.timestamp
36
+ # end
37
+ # end
38
+ #
39
+ # # ...
40
+ # end
41
+ class SchemaTypes
42
+ class Text
43
+ def initialize(partial_path:, operators:)
44
+ @partial_path = partial_path
45
+ @operators = operators
46
+ end
47
+
48
+ attr_reader :operators
49
+
50
+ def to_partial_path
51
+ @partial_path
52
+ end
53
+
54
+ def q
55
+ [:q]
56
+ end
57
+ end
58
+
59
+ class Select
60
+ def initialize(collection:, operators:)
61
+ @collection = collection
62
+ @operators = operators
63
+ end
64
+
65
+ attr_reader :collection
66
+ attr_reader :operators
67
+
68
+ def to_partial_path
69
+ "filter_type_select"
70
+ end
71
+
72
+ def q
73
+ [:q]
74
+ end
75
+ end
76
+
77
+ class Timestamp
78
+ def initialize(operators:)
79
+ @operators = operators
80
+ end
81
+
82
+ attr_reader :operators
83
+
84
+ def to_partial_path
85
+ "filter_type_timestamp"
86
+ end
87
+
88
+ def q
89
+ [:q0, :q1]
90
+ end
91
+ end
92
+
93
+ def before_yield(fields:)
94
+ end
95
+
96
+ def after_yield
97
+ end
98
+
99
+ def select(collection:, operators: Filter::Operator.select_defaults)
100
+ Select.new(
101
+ collection: collection,
102
+ operators: operators
103
+ )
104
+ end
105
+
106
+ def text(operators: Filter::Operator.text_defaults)
107
+ Text.new(
108
+ partial_path: "filter_type_text",
109
+ operators: operators
110
+ )
111
+ end
112
+
113
+ def timestamp(operators: Filter::Operator.range_defaults)
114
+ Timestamp.new(operators: operators)
115
+ end
116
+ end
117
+ end
118
+ end