super 0.0.6 → 0.0.11

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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -0
  3. data/CONTRIBUTING.md +56 -0
  4. data/README.md +55 -57
  5. data/Rakefile +16 -14
  6. data/STABILITY.md +50 -0
  7. data/app/assets/javascripts/super/application.js +297 -97
  8. data/app/assets/stylesheets/super/application.css +5600 -0
  9. data/app/controllers/super/application_controller.rb +15 -6
  10. data/app/helpers/super/form_builder_helper.rb +25 -0
  11. data/app/views/layouts/super/application.html.erb +22 -6
  12. data/app/views/super/application/_display_rich_text.html.erb +1 -0
  13. data/app/views/super/application/_filter.html.erb +6 -0
  14. data/app/views/super/application/_filter_type_select.html.erb +21 -0
  15. data/app/views/super/application/_filter_type_text.html.erb +18 -0
  16. data/app/views/super/application/_filter_type_timestamp.html.erb +24 -0
  17. data/app/views/super/application/_form_field__destroy.html.erb +1 -9
  18. data/app/views/super/application/_form_field_checkbox.html.erb +1 -0
  19. data/app/views/super/application/_form_field_rich_text_area.html.erb +1 -0
  20. data/app/views/super/application/_form_field_select.html.erb +1 -23
  21. data/app/views/super/application/_form_field_text.html.erb +1 -13
  22. data/app/views/super/application/_query.html.erb +18 -0
  23. data/app/views/super/application/_sort.html.erb +18 -0
  24. data/app/views/super/application/_sort_expression.html.erb +25 -0
  25. data/app/views/super/application/_super_layout.html.erb +5 -5
  26. data/app/views/super/application/_super_pagination.html.erb +16 -0
  27. data/app/views/super/application/_super_panel.html.erb +2 -2
  28. data/app/views/super/application/_super_schema_display_index.html.erb +9 -24
  29. data/app/views/super/application/_super_schema_display_show.html.erb +4 -4
  30. data/app/views/super/application/_super_schema_form.html.erb +3 -3
  31. data/app/views/super/application/edit.html.erb +2 -6
  32. data/app/views/super/application/index.html.erb +2 -6
  33. data/app/views/super/application/new.html.erb +2 -6
  34. data/app/views/super/application/show.html.erb +2 -6
  35. data/app/views/super/feather/README.md +1 -0
  36. data/app/views/super/feather/_x.html +15 -0
  37. data/config/routes.rb +2 -0
  38. data/docs/README.md +4 -2
  39. data/docs/action_text.md +48 -0
  40. data/docs/cheat.md +8 -8
  41. data/docs/faq.md +3 -3
  42. data/docs/installation.md +21 -0
  43. data/docs/quick_start.md +1 -16
  44. data/docs/webpacker.md +13 -5
  45. data/docs/yard_customizations.rb +2 -0
  46. data/frontend/super-frontend/dist/application.css +5600 -0
  47. data/frontend/super-frontend/dist/application.js +297 -97
  48. data/lib/generators/super/action_text/USAGE +23 -0
  49. data/lib/generators/super/action_text/action_text_generator.rb +32 -0
  50. data/lib/generators/super/action_text/templates/pack_super_action_text.css +23 -0
  51. data/lib/generators/super/action_text/templates/pack_super_action_text.js +4 -0
  52. data/lib/generators/super/install/install_generator.rb +2 -0
  53. data/lib/generators/super/resource/resource_generator.rb +2 -0
  54. data/lib/generators/super/resource/templates/resources_controller.rb.tt +1 -31
  55. data/lib/generators/super/webpacker/USAGE +5 -4
  56. data/lib/generators/super/webpacker/webpacker_generator.rb +5 -2
  57. data/lib/super.rb +23 -2
  58. data/lib/super/action_inquirer.rb +2 -0
  59. data/lib/super/assets.rb +114 -38
  60. data/lib/super/client_error.rb +2 -0
  61. data/lib/super/compatibility.rb +15 -1
  62. data/lib/super/configuration.rb +22 -69
  63. data/lib/super/controls.rb +6 -25
  64. data/lib/super/controls/optional.rb +71 -24
  65. data/lib/super/controls/required.rb +3 -29
  66. data/lib/super/controls/steps.rb +44 -53
  67. data/lib/super/controls/view.rb +55 -0
  68. data/lib/super/display.rb +80 -0
  69. data/lib/super/display/guesser.rb +36 -0
  70. data/lib/super/display/schema_types.rb +28 -33
  71. data/lib/super/engine.rb +11 -1
  72. data/lib/super/error.rb +14 -0
  73. data/lib/super/filter.rb +14 -0
  74. data/lib/super/filter/form_object.rb +94 -0
  75. data/lib/super/filter/guesser.rb +32 -0
  76. data/lib/super/filter/operator.rb +105 -0
  77. data/lib/super/filter/schema_types.rb +114 -0
  78. data/lib/super/form.rb +29 -40
  79. data/lib/super/form/builder.rb +206 -0
  80. data/lib/super/form/guesser.rb +29 -0
  81. data/lib/super/form/inline_errors.rb +28 -0
  82. data/lib/super/form/schema_types.rb +31 -29
  83. data/lib/super/form/strong_params.rb +31 -0
  84. data/lib/super/layout.rb +2 -0
  85. data/lib/super/link.rb +2 -0
  86. data/lib/super/navigation/automatic.rb +2 -0
  87. data/lib/super/pagination.rb +57 -0
  88. data/lib/super/panel.rb +2 -0
  89. data/lib/super/partial.rb +14 -0
  90. data/lib/super/partial/resolving.rb +2 -0
  91. data/lib/super/plugin.rb +36 -63
  92. data/lib/super/query/form_object.rb +48 -0
  93. data/lib/super/schema.rb +2 -24
  94. data/lib/super/schema/common.rb +27 -0
  95. data/lib/super/schema/guesser.rb +79 -0
  96. data/lib/super/sort.rb +110 -0
  97. data/lib/super/version.rb +3 -1
  98. data/lib/super/view_helper.rb +2 -19
  99. metadata +74 -22
  100. data/app/helpers/super/application_helper.rb +0 -39
  101. data/app/views/super/application/_form_inline_errors.html.erb +0 -10
  102. data/frontend/super-frontend/build.js +0 -36
  103. data/frontend/super-frontend/package.json +0 -21
  104. data/frontend/super-frontend/postcss.config.js +0 -6
  105. data/frontend/super-frontend/src/javascripts/super/application.ts +0 -18
  106. data/frontend/super-frontend/src/javascripts/super/apply_template_controller.ts +0 -19
  107. data/frontend/super-frontend/src/javascripts/super/rails__ujs.d.ts +0 -1
  108. data/frontend/super-frontend/src/javascripts/super/toggle_pending_destruction_controller.ts +0 -15
  109. data/frontend/super-frontend/src/stylesheets/super/application.css +0 -77
  110. data/frontend/super-frontend/tailwind.config.js +0 -15
  111. data/frontend/super-frontend/tsconfig.json +0 -13
  112. data/frontend/super-frontend/yarn.lock +0 -5448
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Super
4
+ class Display
5
+ class Guesser
6
+ def initialize(model:, action:, fields:, type:)
7
+ @model = model
8
+ @action_inquirer = action
9
+ @fields = fields
10
+ @type = type
11
+ end
12
+
13
+ def call
14
+ Schema::Guesser
15
+ .new(model: @model, fields: @fields, type: @type)
16
+ .ignore_foreign_keys
17
+ .limit { 5 if @action_inquirer.index? }
18
+ .assign_type { |attribute_name| attribute_type_for(attribute_name) }
19
+ .call
20
+ end
21
+
22
+ private
23
+
24
+ def attribute_type_for(attribute_name)
25
+ type = @model.type_for_attribute(attribute_name).type
26
+
27
+ case type
28
+ when :datetime
29
+ @type.timestamp
30
+ else
31
+ @type.text
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,33 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Super
2
4
  class Display
3
- # This schema type is meant to be used for +#index+ or +#show+ actions to
4
- # transform database fields into something that is human friendly.
5
- #
6
- # ```
7
- # class MembersController::Controls
8
- # # ...
9
- #
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 }
18
- # end
19
- # end
20
- #
21
- # # ...
22
- # end
23
- # ```
24
5
  class SchemaTypes
25
6
  class Dynamic
26
- def initialize(transform_block)
7
+ def initialize(ignore_nil: true, &transform_block)
27
8
  @transform_block = transform_block
9
+ @ignore_nil = ignore_nil
28
10
  end
29
11
 
30
12
  def present(value)
13
+ return nil if value.nil? && @ignore_nil
14
+
31
15
  @transform_block.call(value)
32
16
  end
33
17
 
@@ -51,23 +35,33 @@ module Super
51
35
  end
52
36
  end
53
37
 
54
- def initialize(action_inquirer)
55
- @action_inquirer = action_inquirer
38
+ def initialize(fields:)
56
39
  @actions_called = false
40
+ @fields = fields
57
41
  end
58
42
 
59
- def before_yield(fields:)
60
- @fields = fields
43
+ def string
44
+ Dynamic.new(&:to_s)
45
+ end
46
+
47
+ alias text string
48
+
49
+ def timestamp
50
+ Dynamic.new(&:iso8601)
51
+ end
52
+
53
+ def rich_text
54
+ Dynamic.new do |value|
55
+ Partial.new("display_rich_text", locals: { rich_text: value })
56
+ end
61
57
  end
62
58
 
63
- def after_yield
64
- return if !@action_inquirer.index?
65
- return if @actions_called
66
- @fields[:actions] = actions
59
+ def manual(&transform_block)
60
+ Dynamic.new(&transform_block)
67
61
  end
68
62
 
69
63
  def dynamic(&transform_block)
70
- Dynamic.new(transform_block)
64
+ Dynamic.new(&transform_block)
71
65
  end
72
66
 
73
67
  def actions
@@ -75,8 +69,9 @@ module Super
75
69
  Bypass.new(partial: "super_schema_display_actions", real: false)
76
70
  end
77
71
 
78
- def to_partial_path
79
- "super_schema_display_#{@action_inquirer.action}"
72
+ # @private
73
+ def actions_called?
74
+ @actions_called
80
75
  end
81
76
  end
82
77
  end
data/lib/super/engine.rb CHANGED
@@ -1,10 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Super
2
4
  # Configures the host Rails app to work with Super
3
5
  class Engine < ::Rails::Engine
6
+ isolate_namespace Super
7
+
4
8
  initializer "super.assets.precompile" do |app|
5
- if Super::Assets.sprockets_available?
9
+ if Super::Assets::Handler.sprockets_available?
6
10
  app.config.assets.precompile << "config/super_manifest.js"
7
11
  end
8
12
  end
13
+
14
+ config.to_prepare do
15
+ Super::Plugin::Registry.controller.ordered do |klass, method_name|
16
+ Super::ApplicationController.public_send(method_name, klass)
17
+ end
18
+ end
9
19
  end
10
20
  end
data/lib/super/error.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Super
2
4
  # A container class for all internal errors thrown by this library
3
5
  #
@@ -13,5 +15,17 @@ module Super
13
15
  class ActionInquirerError < Error; end
14
16
  # Error raised when a `Super::Link` couldn't be found
15
17
  class LinkNotRegistered < Error; end
18
+ # Error raised when rendering if `@view` wasn't set by the controller
19
+ class NothingToRender < Error
20
+ def initialize(basename)
21
+ super(
22
+ "Super's built-in `#{basename}.html.erb` requires `@view` to be set " \
23
+ "by the controller, but it wasn't set"
24
+ )
25
+ end
26
+ end
27
+ # Error raised when something wasn't initalized correctly, and if there isn't
28
+ # a more specific error
29
+ class Initalization < Error; end
16
30
  end
17
31
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Super
4
+ class Filter
5
+ def initialize
6
+ @schema_type = Filter::SchemaTypes.new
7
+ @fields = Schema::Fields.new
8
+
9
+ yield(@fields, @schema_type)
10
+ end
11
+
12
+ attr_reader :fields
13
+ end
14
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Super
4
+ class Filter
5
+ class FormObject
6
+ class FilterFormField
7
+ def initialize(humanized_field_name:, field_name:, type:, params:)
8
+ @humanized_field_name = humanized_field_name
9
+ @field_name = field_name
10
+ @field_type = type
11
+ @params = params
12
+ @specified_values =
13
+ type.q
14
+ .map do |query_field_name|
15
+ [
16
+ query_field_name,
17
+ (params || {})[query_field_name],
18
+ ]
19
+ end
20
+ .to_h
21
+
22
+ @specified_values.each do |key, value|
23
+ define_singleton_method(key) { value }
24
+ end
25
+ end
26
+
27
+ attr_reader :humanized_field_name
28
+ attr_reader :field_name
29
+ attr_reader :field_type
30
+ attr_reader :specified_values
31
+
32
+ def op
33
+ (@params || {})[:op]
34
+ end
35
+
36
+ def operators
37
+ @field_type.operators
38
+ .map { |o| [o.name, o.identifier] }
39
+ .to_h
40
+ end
41
+
42
+ def to_partial_path
43
+ @field_type.to_partial_path
44
+ end
45
+ end
46
+
47
+ def initialize(model:, params:, schema:)
48
+ @model = model
49
+ @params = params
50
+ @schema = schema
51
+
52
+ @form_fields = {}
53
+ end
54
+
55
+ def each_field
56
+ @schema.fields.each do |field_name, _field_type|
57
+ yield(form_field_for(field_name))
58
+ end
59
+ end
60
+
61
+ def to_partial_path
62
+ "filter"
63
+ end
64
+
65
+ def apply_changes(relation)
66
+ each_field do |form_field|
67
+ next if form_field.specified_values.values.map(&:to_s).map(&:strip).all? { |specified_value| specified_value == "" }
68
+ next if !Super::Filter::Operator.registry.key?(form_field.op)
69
+
70
+ operator = Super::Filter::Operator.registry[form_field.op]
71
+ updated_relation = operator.filter(relation, form_field.field_name, *form_field.specified_values.values)
72
+
73
+ if updated_relation.is_a?(ActiveRecord::Relation)
74
+ relation = updated_relation
75
+ end
76
+ end
77
+
78
+ relation
79
+ 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
+ end
93
+ end
94
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Super
4
+ class Filter
5
+ class Guesser
6
+ def initialize(model:, fields:, type:)
7
+ @model = model
8
+ @fields = fields
9
+ @type = type
10
+ end
11
+
12
+ def call
13
+ Schema::Guesser
14
+ .new(model: @model, fields: @fields, type: @type)
15
+ .assign_type { |attribute_name| attribute_type_for(attribute_name) }
16
+ .call
17
+ end
18
+
19
+ private
20
+
21
+ def attribute_type_for(attribute_name)
22
+ type = @model.type_for_attribute(attribute_name).type
23
+ case type
24
+ when :datetime
25
+ @type.timestamp
26
+ else
27
+ @type.text
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Super
4
+ class Filter
5
+ module Operator
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
+
21
+ class << self
22
+ def registry
23
+ @registry ||= {}
24
+ end
25
+
26
+ def range_defaults
27
+ [
28
+ registry["between"],
29
+ ]
30
+ end
31
+
32
+ def select_defaults
33
+ [
34
+ registry["eq"],
35
+ registry["neq"],
36
+ ]
37
+ end
38
+
39
+ def text_defaults
40
+ [
41
+ registry["eq"],
42
+ registry["neq"],
43
+ registry["contain"],
44
+ registry["ncontain"],
45
+ registry["start"],
46
+ registry["end"],
47
+ ]
48
+ end
49
+
50
+ def define(identifier, name, &filter)
51
+ identifier = identifier.to_s
52
+ name = name.to_s
53
+
54
+ definition = Definition.new(identifier, name, filter)
55
+
56
+ registry[identifier] = definition
57
+
58
+ define_singleton_method(identifier) do
59
+ registry[identifier]
60
+ end
61
+ end
62
+ end
63
+
64
+ define("eq", "equals") do |relation, field, query|
65
+ relation.where(field => query)
66
+ end
67
+
68
+ define("neq", "doesn't equal") do |relation, field, query|
69
+ relation.where.not(field => query)
70
+ end
71
+
72
+ define("contain", "contains") do |relation, field, query|
73
+ query = "%#{Compatability.sanitize_sql_like(query)}%"
74
+ relation.where("#{field} LIKE ?", "%#{query}%")
75
+ end
76
+
77
+ define("ncontain", "doesn't contain") do |relation, field, query|
78
+ query = "%#{Compatability.sanitize_sql_like(query)}%"
79
+ relation.where("#{field} NOT LIKE ?", query)
80
+ end
81
+
82
+ define("start", "starts with") do |relation, field, query|
83
+ query = "#{Compatability.sanitize_sql_like(query)}%"
84
+ relation.where("#{field} LIKE ?", query)
85
+ end
86
+
87
+ define("end", "ends with") do |relation, field, query|
88
+ query = "%#{Compatability.sanitize_sql_like(query)}"
89
+ relation.where("#{field} LIKE ?", query)
90
+ end
91
+
92
+ define("between", "between") do |relation, field, query0, query1|
93
+ if query0.present?
94
+ relation = relation.where("#{field} >= ?", query0)
95
+ end
96
+
97
+ if query1.present?
98
+ relation = relation.where("#{field} <= ?", query1)
99
+ end
100
+
101
+ relation
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Super
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
+ # Note: The constants under "Defined Under Namespace" are considered
12
+ # 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
+ class SchemaTypes
44
+ class Text
45
+ def initialize(partial_path:, operators:)
46
+ @partial_path = partial_path
47
+ @operators = operators
48
+ end
49
+
50
+ attr_reader :operators
51
+
52
+ def to_partial_path
53
+ @partial_path
54
+ end
55
+
56
+ def q
57
+ [:q]
58
+ end
59
+ end
60
+
61
+ class Select
62
+ def initialize(collection:, operators:)
63
+ @collection = collection
64
+ @operators = operators
65
+ end
66
+
67
+ attr_reader :collection
68
+ attr_reader :operators
69
+
70
+ def to_partial_path
71
+ "filter_type_select"
72
+ end
73
+
74
+ def q
75
+ [:q]
76
+ end
77
+ end
78
+
79
+ class Timestamp
80
+ def initialize(operators:)
81
+ @operators = operators
82
+ end
83
+
84
+ attr_reader :operators
85
+
86
+ def to_partial_path
87
+ "filter_type_timestamp"
88
+ end
89
+
90
+ def q
91
+ [:q0, :q1]
92
+ end
93
+ end
94
+
95
+ def select(collection:, operators: Filter::Operator.select_defaults)
96
+ Select.new(
97
+ collection: collection,
98
+ operators: operators
99
+ )
100
+ end
101
+
102
+ def text(operators: Filter::Operator.text_defaults)
103
+ Text.new(
104
+ partial_path: "filter_type_text",
105
+ operators: operators
106
+ )
107
+ end
108
+
109
+ def timestamp(operators: Filter::Operator.range_defaults)
110
+ Timestamp.new(operators: operators)
111
+ end
112
+ end
113
+ end
114
+ end