super 0.0.2 → 0.0.7

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