super 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
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