super 0.0.2 → 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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +11 -0
  3. data/README.md +68 -76
  4. data/app/assets/javascripts/super/application.js +1252 -316
  5. data/app/assets/stylesheets/super/application.css +102704 -17321
  6. data/app/controllers/super/application_controller.rb +49 -71
  7. data/app/views/layouts/super/application.html.erb +10 -8
  8. data/app/views/super/application/_collection_header.html.erb +15 -0
  9. data/app/views/super/application/_filter.html.erb +14 -0
  10. data/app/views/super/application/_filter_type_select.html.erb +31 -0
  11. data/app/views/super/application/_filter_type_text.html.erb +22 -0
  12. data/app/views/super/application/_filter_type_timestamp.html.erb +35 -0
  13. data/app/views/super/application/_flash.html.erb +13 -13
  14. data/app/views/super/application/_form_field__destroy.html.erb +9 -0
  15. data/app/views/super/application/_form_field_select.html.erb +23 -0
  16. data/app/views/super/application/_form_field_text.html.erb +13 -0
  17. data/app/views/super/application/_form_fieldset.html.erb +8 -0
  18. data/app/views/super/application/_form_has_many.html.erb +21 -0
  19. data/app/views/super/application/_form_has_one.html.erb +11 -0
  20. data/app/views/super/application/_form_inline_errors.html.erb +10 -0
  21. data/app/views/super/application/_member_header.html.erb +16 -0
  22. data/app/views/super/application/_super_layout.html.erb +29 -0
  23. data/app/views/super/application/_super_pagination.html.erb +16 -0
  24. data/app/views/super/application/_super_panel.html.erb +7 -0
  25. data/app/views/super/application/_super_schema_display_actions.html.erb +5 -0
  26. data/app/views/super/application/_super_schema_display_index.html.erb +24 -0
  27. data/app/views/super/application/_super_schema_display_show.html.erb +8 -0
  28. data/app/views/super/application/_super_schema_form.html.erb +15 -0
  29. data/app/views/super/application/edit.html.erb +1 -6
  30. data/app/views/super/application/index.html.erb +1 -1
  31. data/app/views/super/application/new.html.erb +1 -6
  32. data/app/views/super/application/show.html.erb +1 -1
  33. data/app/views/super/feather/{_chevron_down.svg → _chevron_down.html} +0 -0
  34. data/config/locales/en.yml +5 -0
  35. data/docs/README.md +6 -0
  36. data/docs/cheat.md +41 -0
  37. data/docs/faq.md +44 -0
  38. data/docs/quick_start.md +45 -0
  39. data/docs/webpacker.md +17 -0
  40. data/docs/yard_customizations.rb +41 -0
  41. data/frontend/super-frontend/build.js +14 -12
  42. data/frontend/super-frontend/dist/application.css +102704 -17321
  43. data/frontend/super-frontend/dist/application.js +1252 -316
  44. data/frontend/super-frontend/package.json +11 -4
  45. data/frontend/super-frontend/postcss.config.js +4 -4
  46. data/frontend/super-frontend/src/javascripts/super/application.ts +18 -0
  47. data/frontend/super-frontend/src/javascripts/super/apply_template_controller.ts +19 -0
  48. data/frontend/super-frontend/src/javascripts/super/rails__ujs.d.ts +1 -0
  49. data/frontend/super-frontend/src/javascripts/super/toggle_pending_destruction_controller.ts +15 -0
  50. data/frontend/super-frontend/src/stylesheets/super/application.css +63 -0
  51. data/frontend/super-frontend/tailwind.config.js +12 -4
  52. data/frontend/super-frontend/tsconfig.json +13 -0
  53. data/frontend/super-frontend/yarn.lock +1891 -1798
  54. data/lib/generators/super/install/install_generator.rb +16 -0
  55. data/lib/generators/super/webpacker/webpacker_generator.rb +8 -0
  56. data/lib/super.rb +16 -6
  57. data/lib/super/action_inquirer.rb +14 -1
  58. data/lib/super/assets.rb +1 -0
  59. data/lib/super/client_error.rb +43 -0
  60. data/lib/super/compatibility.rb +25 -0
  61. data/lib/super/configuration.rb +66 -44
  62. data/lib/super/controls.rb +26 -15
  63. data/lib/super/controls/optional.rb +54 -0
  64. data/lib/super/controls/required.rb +41 -0
  65. data/lib/super/controls/steps.rb +128 -0
  66. data/lib/super/display/schema_types.rb +90 -18
  67. data/lib/super/engine.rb +7 -0
  68. data/lib/super/error.rb +9 -8
  69. data/lib/super/filter.rb +137 -0
  70. data/lib/super/filter/operator.rb +103 -0
  71. data/lib/super/filter/schema_types.rb +118 -0
  72. data/lib/super/form.rb +48 -0
  73. data/lib/super/form/schema_types.rb +96 -21
  74. data/lib/super/layout.rb +47 -0
  75. data/lib/super/link.rb +110 -0
  76. data/lib/super/navigation/automatic.rb +2 -0
  77. data/lib/super/pagination.rb +80 -8
  78. data/lib/super/panel.rb +30 -0
  79. data/lib/super/partial.rb +23 -0
  80. data/lib/super/partial/resolving.rb +24 -0
  81. data/lib/super/plugin.rb +34 -63
  82. data/lib/super/schema.rb +65 -1
  83. data/lib/super/version.rb +1 -1
  84. data/lib/super/view_helper.rb +43 -0
  85. metadata +132 -33
  86. data/app/views/super/application/_form.html.erb +0 -15
  87. data/app/views/super/application/_form_field.html.erb +0 -7
  88. data/app/views/super/application/_form_generic_select.html.erb +0 -19
  89. data/app/views/super/application/_form_generic_text.html.erb +0 -7
  90. data/app/views/super/application/_index.html.erb +0 -60
  91. data/app/views/super/application/_show.html.erb +0 -12
  92. data/frontend/super-frontend/src/javascripts/super/application.js +0 -11
  93. data/lib/super/display.rb +0 -9
  94. data/lib/super/inline_callback.rb +0 -82
  95. data/lib/super/test_support/copy_app_templates/20190216224956_create_members.rb +0 -11
  96. data/lib/super/test_support/copy_app_templates/20190803143320_create_ships.rb +0 -11
  97. data/lib/super/test_support/copy_app_templates/20190806014121_add_ship_to_members.rb +0 -5
  98. data/lib/super/test_support/copy_app_templates/member.rb +0 -16
  99. data/lib/super/test_support/copy_app_templates/members_controller.rb +0 -52
  100. data/lib/super/test_support/copy_app_templates/routes.rb +0 -10
  101. data/lib/super/test_support/copy_app_templates/seeds.rb +0 -2
  102. data/lib/super/test_support/copy_app_templates/ship.rb +0 -3
  103. data/lib/super/test_support/copy_app_templates/ships_controller.rb +0 -47
  104. data/lib/super/test_support/fixtures/members.yml +0 -336
  105. data/lib/super/test_support/fixtures/ships.yml +0 -10
  106. data/lib/super/test_support/generate_copy_app.rb +0 -52
  107. data/lib/super/test_support/generate_dummy.rb +0 -94
  108. data/lib/super/test_support/starfleet_seeder.rb +0 -49
  109. data/lib/super/view.rb +0 -25
  110. data/lib/tasks/super_tasks.rake +0 -4
@@ -0,0 +1,128 @@
1
+ module Super
2
+ class Controls
3
+ # Methods that are called by controller actions. All of these methods have
4
+ # a default implementation, but feel free to override as needed.
5
+ module Steps
6
+ # Tells the controller how to load records in the index action using
7
+ # `#scope`
8
+ #
9
+ # @param action [ActionInquirer]
10
+ # @param params [ActionController::Parameters]
11
+ # @return [ActiveRecord::Relation]
12
+ def load_records(action:, params:)
13
+ default_for(:load_records, action: action, params: params) do
14
+ scope(action: action)
15
+ end
16
+ end
17
+
18
+ # Loads a record using `#scope`
19
+ #
20
+ # @param action [ActionInquirer]
21
+ # @param params [ActionController::Parameters]
22
+ # @return [ActiveRecord::Base]
23
+ def load_record(action:, params:)
24
+ default_for(:load_record, action: action, params: params) do
25
+ scope(action: action).find(params[:id])
26
+ end
27
+ end
28
+
29
+ # Builds a record using `#scope`
30
+ #
31
+ # @param action [ActionInquirer]
32
+ # @return [ActiveRecord::Base]
33
+ def build_record(action:)
34
+ default_for(:build_record) do
35
+ scope(action: action).build
36
+ end
37
+ end
38
+
39
+ # Builds and populates a record using `#scope`
40
+ #
41
+ # @param action [ActionInquirer]
42
+ # @param params [ActionController::Parameters]
43
+ # @return [ActiveRecord::Base]
44
+ def build_record_with_params(action:, params:)
45
+ default_for(:build_record_with_params, action: action, params: params) do
46
+ scope(action: action).build(permitted_params(params, action: action))
47
+ end
48
+ end
49
+
50
+ # Saves a record
51
+ #
52
+ # @param action [ActionInquirer]
53
+ # @param params [ActionController::Parameters]
54
+ # @return [ActiveRecord::Base]
55
+ def save_record(action:, record:, params:)
56
+ default_for(:save_record, action: action, record: record, params: params) do
57
+ record.save
58
+ end
59
+ end
60
+
61
+ # Saves a record
62
+ #
63
+ # @param action [ActionInquirer]
64
+ # @param params [ActionController::Parameters]
65
+ # @return [ActiveRecord::Base]
66
+ def update_record(action:, record:, params:)
67
+ default_for(:update_record, action: action, record: record, params: params) do
68
+ record.update(permitted_params(params, action: action))
69
+ end
70
+ end
71
+
72
+ # Destroys a record
73
+ #
74
+ # @param action [ActionInquirer]
75
+ # @param params [ActionController::Parameters]
76
+ # @return [ActiveRecord::Base, false]
77
+ def destroy_record(action:, record:, params:)
78
+ default_for(:update_record, action: action, record: record, params: params) do
79
+ record.destroy
80
+ end
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
126
+ end
127
+ end
128
+ end
@@ -3,39 +3,111 @@ module Super
3
3
  # This schema type is meant to be used for +#index+ or +#show+ actions to
4
4
  # transform database fields into something that is human friendly.
5
5
  #
6
- # Note: The constants under "Defined Under Namespace" are considered
7
- # private.
6
+ # ```
7
+ # class MembersController::Controls
8
+ # # ...
8
9
  #
9
- # class MemberDashboard
10
- # # ...
11
- #
12
- # def show_schema
13
- # Super::Schema.new(Super::Display::SchemaTypes.new) do |fields, type|
14
- # fields[:name] = type.dynamic { |name| name }
15
- # fields[:rank] = type.dynamic { |rank| rank }
16
- # fields[:position] = type.dynamic { |position| position }
17
- # fields[:ship] = type.dynamic { |ship| "#{ship.name} (Ship ##{ship.id})" }
18
- # fields[:created_at] = type.dynamic { |created_at| created_at.iso8601 }
19
- # fields[:updated_at] = type.dynamic { |updated_at| updated_at.iso8601 }
20
- # end
10
+ # def show_schema
11
+ # Super::Schema.new(Super::Display::SchemaTypes.new) do |fields, type|
12
+ # fields[:name] = type.dynamic { |name| name }
13
+ # fields[:rank] = type.dynamic { |rank| rank }
14
+ # fields[:position] = type.dynamic { |position| position }
15
+ # fields[:ship] = type.dynamic { |ship| "#{ship.name} (Ship ##{ship.id})" }
16
+ # fields[:created_at] = type.dynamic { |created_at| created_at.iso8601 }
17
+ # fields[:updated_at] = type.dynamic { |updated_at| updated_at.iso8601 }
21
18
  # end
22
- #
23
- # # ...
24
19
  # end
20
+ #
21
+ # # ...
22
+ # end
23
+ # ```
25
24
  class SchemaTypes
26
25
  class Dynamic
27
26
  def initialize(transform_block)
28
27
  @transform_block = transform_block
29
28
  end
30
29
 
31
- def present(field)
32
- @transform_block.call(field)
30
+ def present(value)
31
+ @transform_block.call(value)
32
+ end
33
+
34
+ def real?
35
+ true
36
+ end
37
+ end
38
+
39
+ class Bypass
40
+ def initialize(partial:, real:)
41
+ @partial = partial
42
+ @real = real
43
+ end
44
+
45
+ def present
46
+ Partial.new(@partial)
33
47
  end
48
+
49
+ def real?
50
+ @real
51
+ end
52
+ end
53
+
54
+ def initialize(action_inquirer)
55
+ @action_inquirer = action_inquirer
56
+ @actions_called = false
57
+ end
58
+
59
+ def before_yield(fields:)
60
+ @fields = fields
61
+ end
62
+
63
+ def after_yield
64
+ return if !@action_inquirer.index?
65
+ return if @actions_called
66
+ @fields[:actions] = actions
34
67
  end
35
68
 
36
69
  def dynamic(&transform_block)
37
70
  Dynamic.new(transform_block)
38
71
  end
72
+
73
+ def actions
74
+ @actions_called = true
75
+ Bypass.new(partial: "super_schema_display_actions", real: false)
76
+ end
77
+
78
+ def to_partial_path
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
110
+ end
39
111
  end
40
112
  end
41
113
  end
@@ -1,9 +1,16 @@
1
1
  module Super
2
+ # Configures the host Rails app to work with Super
2
3
  class Engine < ::Rails::Engine
3
4
  initializer "super.assets.precompile" do |app|
4
5
  if Super::Assets.sprockets_available?
5
6
  app.config.assets.precompile << "config/super_manifest.js"
6
7
  end
7
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
8
15
  end
9
16
  end
@@ -1,16 +1,17 @@
1
1
  module Super
2
+ # A container class for all internal errors thrown by this library
3
+ #
4
+ # See also `Super::ClientError`
2
5
  class Error < StandardError
6
+ # Error raised when some configuration is not set
3
7
  class UnconfiguredConfiguration < Error; end
8
+ # Error raised when a configuration is set to a invalid value
4
9
  class InvalidConfiguration < Error; end
5
- class InvalidStyle < Error; end
10
+ # Error raised on problematic plugins, see `Super::Plugin`
6
11
  class InvalidPluginArgument < Error; end
12
+ # Error raised on problematic ActionInquirer settings, see `Super::ActionInquirer`
7
13
  class ActionInquirerError < Error; end
8
-
9
- class ClientError < Error; end
10
- class BadRequest < ClientError; end
11
- class Unauthorized < ClientError; end
12
- class Forbidden < ClientError; end
13
- class NotFound < ClientError; end
14
- class UnprocessableEntity < ClientError; end
14
+ # Error raised when a `Super::Link` couldn't be found
15
+ class LinkNotRegistered < Error; end
15
16
  end
16
17
  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