super 0.0.3 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +11 -0
  3. data/CONTRIBUTING.md +56 -0
  4. data/README.md +65 -76
  5. data/STABILITY.md +50 -0
  6. data/app/assets/javascripts/super/application.js +1186 -11151
  7. data/app/assets/stylesheets/super/application.css +93514 -27381
  8. data/app/controllers/super/application_controller.rb +49 -72
  9. data/app/views/layouts/super/application.html.erb +24 -14
  10. data/app/views/super/application/_collection_header.html.erb +15 -0
  11. data/app/views/super/application/_filter.html.erb +14 -0
  12. data/app/views/super/application/_filter_type_select.html.erb +31 -0
  13. data/app/views/super/application/_filter_type_text.html.erb +22 -0
  14. data/app/views/super/application/_filter_type_timestamp.html.erb +35 -0
  15. data/app/views/super/application/_flash.html.erb +13 -13
  16. data/app/views/super/application/_form_field__destroy.html.erb +7 -3
  17. data/app/views/super/application/_form_field_select.html.erb +14 -8
  18. data/app/views/super/application/_form_field_text.html.erb +13 -5
  19. data/app/views/super/application/_form_fieldset.html.erb +1 -1
  20. data/app/views/super/application/_form_has_many.html.erb +5 -5
  21. data/app/views/super/application/_form_inline_errors.html.erb +1 -1
  22. data/app/views/super/application/_member_header.html.erb +16 -0
  23. data/app/views/super/application/_super_layout.html.erb +29 -0
  24. data/app/views/super/application/_super_pagination.html.erb +16 -0
  25. data/app/views/super/application/_super_panel.html.erb +7 -0
  26. data/app/views/super/application/_super_schema_display_actions.html.erb +5 -0
  27. data/app/views/super/application/_super_schema_display_index.html.erb +24 -0
  28. data/app/views/super/application/_super_schema_display_show.html.erb +8 -0
  29. data/app/views/super/application/_super_schema_form.html.erb +15 -0
  30. data/app/views/super/application/edit.html.erb +1 -6
  31. data/app/views/super/application/index.html.erb +1 -1
  32. data/app/views/super/application/new.html.erb +1 -6
  33. data/app/views/super/application/show.html.erb +1 -1
  34. data/app/views/super/feather/{_chevron_down.svg → _chevron_down.html} +0 -0
  35. data/config/locales/en.yml +5 -0
  36. data/docs/README.md +6 -0
  37. data/docs/cheat.md +41 -0
  38. data/docs/faq.md +44 -0
  39. data/docs/quick_start.md +45 -0
  40. data/docs/webpacker.md +17 -0
  41. data/docs/yard_customizations.rb +41 -0
  42. data/frontend/super-frontend/build.js +12 -12
  43. data/frontend/super-frontend/dist/application.css +93514 -27381
  44. data/frontend/super-frontend/dist/application.js +1186 -11151
  45. data/frontend/super-frontend/package.json +8 -4
  46. data/frontend/super-frontend/postcss.config.js +4 -4
  47. data/frontend/super-frontend/src/javascripts/super/application.ts +11 -9
  48. data/frontend/super-frontend/src/javascripts/super/apply_template_controller.ts +19 -0
  49. data/frontend/super-frontend/src/javascripts/super/rails__ujs.d.ts +1 -1
  50. data/frontend/super-frontend/src/javascripts/super/toggle_pending_destruction_controller.ts +15 -0
  51. data/frontend/super-frontend/src/stylesheets/super/application.css +63 -0
  52. data/frontend/super-frontend/tailwind.config.js +12 -4
  53. data/frontend/super-frontend/yarn.lock +1429 -1372
  54. data/lib/generators/super/install/install_generator.rb +16 -0
  55. data/lib/generators/super/resource/templates/resources_controller.rb.tt +1 -31
  56. data/lib/generators/super/webpacker/webpacker_generator.rb +10 -1
  57. data/lib/super.rb +26 -5
  58. data/lib/super/action_inquirer.rb +2 -2
  59. data/lib/super/assets.rb +108 -38
  60. data/lib/super/client_error.rb +43 -0
  61. data/lib/super/compatibility.rb +25 -0
  62. data/lib/super/configuration.rb +21 -69
  63. data/lib/super/controls.rb +9 -37
  64. data/lib/super/controls/optional.rb +79 -0
  65. data/lib/super/controls/required.rb +13 -0
  66. data/lib/super/controls/steps.rb +114 -0
  67. data/lib/super/display.rb +66 -3
  68. data/lib/super/display/guesser.rb +34 -0
  69. data/lib/super/display/schema_types.rb +55 -25
  70. data/lib/super/engine.rb +7 -1
  71. data/lib/super/error.rb +9 -9
  72. data/lib/super/filter.rb +12 -0
  73. data/lib/super/filter/form_object.rb +97 -0
  74. data/lib/super/filter/guesser.rb +30 -0
  75. data/lib/super/filter/operator.rb +103 -0
  76. data/lib/super/filter/plugin.rb +47 -0
  77. data/lib/super/filter/schema_types.rb +112 -0
  78. data/lib/super/form.rb +35 -0
  79. data/lib/super/form/builder.rb +48 -0
  80. data/lib/super/form/guesser.rb +27 -0
  81. data/lib/super/form/schema_types.rb +20 -23
  82. data/lib/super/form/strong_params.rb +29 -0
  83. data/lib/super/layout.rb +47 -0
  84. data/lib/super/link.rb +110 -0
  85. data/lib/super/pagination.rb +74 -8
  86. data/lib/super/panel.rb +30 -0
  87. data/lib/super/partial.rb +23 -0
  88. data/lib/super/partial/resolving.rb +24 -0
  89. data/lib/super/plugin.rb +34 -63
  90. data/lib/super/schema.rb +12 -22
  91. data/lib/super/schema/common.rb +25 -0
  92. data/lib/super/schema/guesser.rb +77 -0
  93. data/lib/super/version.rb +1 -1
  94. data/lib/super/view_helper.rb +43 -0
  95. metadata +133 -33
  96. data/app/views/super/application/_form.html.erb +0 -14
  97. data/app/views/super/application/_index.html.erb +0 -60
  98. data/app/views/super/application/_show.html.erb +0 -12
  99. data/frontend/super-frontend/src/javascripts/super/nested_attributes_controller.ts +0 -33
  100. data/lib/super/inline_callback.rb +0 -82
  101. data/lib/super/test_support/copy_app_templates/controllers/favorite_things_controller.rb +0 -50
  102. data/lib/super/test_support/copy_app_templates/controllers/members_controller.rb +0 -57
  103. data/lib/super/test_support/copy_app_templates/controllers/ships_controller.rb +0 -47
  104. data/lib/super/test_support/copy_app_templates/migrations/20190216224956_create_members.rb +0 -11
  105. data/lib/super/test_support/copy_app_templates/migrations/20190803143320_create_ships.rb +0 -11
  106. data/lib/super/test_support/copy_app_templates/migrations/20190806014121_add_ship_to_members.rb +0 -5
  107. data/lib/super/test_support/copy_app_templates/migrations/20191126050453_create_favorite_things.rb +0 -10
  108. data/lib/super/test_support/copy_app_templates/models/favorite_thing.rb +0 -7
  109. data/lib/super/test_support/copy_app_templates/models/member.rb +0 -23
  110. data/lib/super/test_support/copy_app_templates/models/ship.rb +0 -3
  111. data/lib/super/test_support/copy_app_templates/routes.rb +0 -11
  112. data/lib/super/test_support/copy_app_templates/seeds.rb +0 -2
  113. data/lib/super/test_support/fixtures/favorite_things.yml +0 -9
  114. data/lib/super/test_support/fixtures/members.yml +0 -336
  115. data/lib/super/test_support/fixtures/ships.yml +0 -10
  116. data/lib/super/test_support/generate_copy_app.rb +0 -41
  117. data/lib/super/test_support/generate_dummy.rb +0 -93
  118. data/lib/super/test_support/starfleet_seeder.rb +0 -50
  119. data/lib/super/view.rb +0 -25
  120. data/lib/tasks/super_tasks.rake +0 -4
@@ -0,0 +1,35 @@
1
+ module Super
2
+ # This schema type is used on your +#edit+ and +#new+ forms
3
+ #
4
+ # ```ruby
5
+ # class MembersController::Controls
6
+ # # ...
7
+ #
8
+ # def new_schema
9
+ # Super::Form.new do |fields, type|
10
+ # fields[:name] = type.string
11
+ # fields[:rank] = type.select
12
+ # fields[:position] = type.string
13
+ # fields[:ship_id] = type.select(
14
+ # collection: Ship.all.map { |s| ["#{s.name} (Ship ##{s.id})", s.id] },
15
+ # )
16
+ # end
17
+ # end
18
+ #
19
+ # # ...
20
+ # end
21
+ # ```
22
+ class Form
23
+ include Schema::Common
24
+
25
+ def initialize
26
+ @fields = Schema::Fields.new
27
+ @schema_types = SchemaTypes.new(fields: @fields)
28
+ yield(@fields, @schema_types)
29
+ end
30
+
31
+ def to_partial_path
32
+ "super_schema_form"
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,48 @@
1
+ module Super
2
+ class Form
3
+ module FieldErrorProc
4
+ def error_wrapping(html_tag)
5
+ if Thread.current[:super_form_builder]
6
+ return html_tag
7
+ end
8
+
9
+ super
10
+ end
11
+ end
12
+
13
+ class Builder < ActionView::Helpers::FormBuilder
14
+ # These methods were originally defined in the following files
15
+ #
16
+ # * actionview/lib/action_view/helpers/form_helper.rb
17
+ # * actionview/lib/action_view/helpers/form_options_helper.rb
18
+ # * actionview/lib/action_view/helpers/date_helper.rb
19
+ %w[
20
+ label text_field password_field hidden_field file_field text_area
21
+ check_box radio_button color_field search_field telephone_field
22
+ date_field time_field datetime_field month_field week_field url_field
23
+ email_field number_field range_field
24
+
25
+ select collection_select grouped_collection_select time_zone_select
26
+ collection_radio_buttons collection_check_boxes
27
+
28
+ time_select datetime_select date_select
29
+ ].each do |field_type_method|
30
+ class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
31
+ def #{field_type_method}(*)
32
+ Thread.current[:super_form_builder] = true
33
+ super
34
+ ensure
35
+ Thread.current[:super_form_builder] = nil
36
+ end
37
+ RUBY
38
+ end
39
+
40
+ alias datetime_local_field datetime_field
41
+ alias phone_field telephone_field
42
+ end
43
+ end
44
+ end
45
+
46
+ ActionView::Helpers::Tags::Base.class_eval do
47
+ prepend Super::Form::FieldErrorProc
48
+ end
@@ -0,0 +1,27 @@
1
+ module Super
2
+ class Form
3
+ class Guesser
4
+ def initialize(model:, fields:, type:)
5
+ @model = model
6
+ @fields = fields
7
+ @type = type
8
+ end
9
+
10
+ def call
11
+ Schema::Guesser
12
+ .new(model: @model, fields: @fields, type: @type)
13
+ .assign_type { |attribute_name| attribute_type_for(attribute_name) }
14
+ .reject { |attribute_name| attribute_name == "id" }
15
+ .reject { |attribute_name| attribute_name == "created_at" }
16
+ .reject { |attribute_name| attribute_name == "updated_at" }
17
+ .call
18
+ end
19
+
20
+ private
21
+
22
+ def attribute_type_for(attribute_name)
23
+ @type.string
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,26 +1,5 @@
1
1
  module Super
2
2
  class Form
3
- # This schema type is used on your +#edit+ and +#new+ forms
4
- #
5
- # ```ruby
6
- # class MembersController::Controls
7
- # # ...
8
- #
9
- # def new_schema
10
- # Super::Schema.new(Super::Form::SchemaTypes.new) do |fields, type|
11
- # fields[:name] = type.generic("form_field_text")
12
- # fields[:rank] = type.generic("form_field_select", collection: Member.ranks.keys)
13
- # fields[:position] = type.generic("form_field_text")
14
- # fields[:ship_id] = type.generic(
15
- # "form_field_select",
16
- # collection: Ship.all.map { |s| ["#{s.name} (Ship ##{s.id})", s.id] },
17
- # )
18
- # end
19
- # end
20
- #
21
- # # ...
22
- # end
23
- # ```
24
3
  class SchemaTypes
25
4
  class Generic
26
5
  def initialize(partial_path:, extras:, nested:)
@@ -31,6 +10,16 @@ module Super
31
10
 
32
11
  attr_reader :nested_fields
33
12
 
13
+ def each_attribute
14
+ if block_given?
15
+ @nested_fields.each do |key, value|
16
+ yield(key, value)
17
+ end
18
+ end
19
+
20
+ enum_for(:each_attribute)
21
+ end
22
+
34
23
  # This takes advantage of a feature of Rails. If the value of
35
24
  # `#to_partial_path` is `my_form_field`, Rails renders
36
25
  # `app/views/super/application/_my_form_field.html.erb`, and this
@@ -63,13 +52,13 @@ module Super
63
52
  return false if other.class != self.class
64
53
  return false if other.instance_variable_get(:@partial_path) != @partial_path
65
54
  return false if other.instance_variable_get(:@extras) != @extras
66
- return false if other.instance_variable_get(:@nested) != @nested
55
+ return false if other.instance_variable_get(:@nested_fields) != @nested_fields
67
56
 
68
57
  true
69
58
  end
70
59
  end
71
60
 
72
- def setup(fields:)
61
+ def initialize(fields:)
73
62
  @fields = fields
74
63
  end
75
64
 
@@ -77,6 +66,14 @@ module Super
77
66
  Generic.new(partial_path: partial_path, extras: extras, nested: {})
78
67
  end
79
68
 
69
+ def select(**extras)
70
+ Generic.new(partial_path: "form_field_select", extras: extras, nested: {})
71
+ end
72
+
73
+ def string(**extras)
74
+ Generic.new(partial_path: "form_field_text", extras: extras, nested: {})
75
+ end
76
+
80
77
  def has_many(reader, **extras)
81
78
  nested = @fields.nested do
82
79
  yield
@@ -0,0 +1,29 @@
1
+ module Super
2
+ class Form
3
+ class StrongParams
4
+ def initialize(form_schema)
5
+ @form_schema = form_schema
6
+ end
7
+
8
+ def require(model)
9
+ model.model_name.singular
10
+ end
11
+
12
+ def permit
13
+ unfurl(@form_schema)
14
+ end
15
+
16
+ private
17
+
18
+ def unfurl(responds_to_each_attribute)
19
+ responds_to_each_attribute.each_attribute.map do |name, type|
20
+ if type.nested_fields&.any?
21
+ { name => [:id, *unfurl(type)] }
22
+ else
23
+ name
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,47 @@
1
+ require "super/partial/resolving"
2
+
3
+ module Super
4
+ class Layout
5
+ include Super::Partial::Resolving
6
+
7
+ def initialize(headers: nil, asides: nil, mains: nil, footers: nil)
8
+ @headers = Array(headers).compact
9
+ @asides = Array(asides).compact
10
+ @mains = Array(mains).compact
11
+ @footers = Array(footers).compact
12
+ end
13
+
14
+ attr_reader :headers
15
+ attr_reader :asides
16
+ attr_reader :mains
17
+ attr_reader :footers
18
+
19
+ def to_partial_path
20
+ "super_layout"
21
+ end
22
+
23
+ def resolve(template)
24
+ @resolved_headers = resolve_for_rendering(template, headers, nil)
25
+ @resolved_asides = resolve_for_rendering(template, asides, nil)
26
+ @resolved_mains = resolve_for_rendering(template, mains, nil)
27
+ @resolved_footers = resolve_for_rendering(template, footers, nil)
28
+ self
29
+ end
30
+
31
+ def resolved_headers
32
+ @resolved_headers || []
33
+ end
34
+
35
+ def resolved_asides
36
+ @resolved_asides || []
37
+ end
38
+
39
+ def resolved_mains
40
+ @resolved_mains || []
41
+ end
42
+
43
+ def resolved_footers
44
+ @resolved_footers || []
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,110 @@
1
+ module Super
2
+ # Links have three required attributes that are passed directly into Rails'
3
+ # `link_to` helper
4
+ class Link
5
+ def self.find_all(*links)
6
+ links.map { |link| find(link) }
7
+ end
8
+
9
+ def self.find(link)
10
+ if link.kind_of?(self)
11
+ return link
12
+ end
13
+
14
+ if registry.key?(link)
15
+ return registry[link]
16
+ end
17
+
18
+ raise Error::LinkNotRegistered, "Unknown link `#{link}`"
19
+ end
20
+
21
+ def self.registry
22
+ @registry ||= {
23
+ new: new(
24
+ "New",
25
+ -> (params:) {
26
+ Rails.application.routes.url_for(
27
+ controller: params[:controller],
28
+ action: :new,
29
+ only_path: true
30
+ )
31
+ }
32
+ ),
33
+ index: new(
34
+ "Index",
35
+ -> (params:) {
36
+ Rails.application.routes.url_for(
37
+ controller: params[:controller],
38
+ action: :index,
39
+ only_path: true
40
+ )
41
+ }
42
+ ),
43
+ show: new(
44
+ "View",
45
+ -> (record:, params:) {
46
+ Rails.application.routes.url_for(
47
+ controller: params[:controller],
48
+ action: :show,
49
+ id: record,
50
+ only_path: true
51
+ )
52
+ }
53
+ ),
54
+ edit: new(
55
+ "Edit",
56
+ -> (record:, params:) {
57
+ Rails.application.routes.url_for(
58
+ controller: params[:controller],
59
+ action: :edit,
60
+ id: record,
61
+ only_path: true
62
+ )
63
+ }
64
+ ),
65
+ destroy: new(
66
+ "Delete",
67
+ -> (record:, params:) {
68
+ Rails.application.routes.url_for(
69
+ controller: params[:controller],
70
+ action: :destroy,
71
+ id: record,
72
+ only_path: true
73
+ )
74
+ },
75
+ method: :delete,
76
+ data: { confirm: "Really delete?" }
77
+ ),
78
+ }
79
+ end
80
+
81
+ def initialize(text, href, **options)
82
+ @text = text
83
+ @href = href
84
+ @options = options
85
+ end
86
+
87
+ def to_s(default_options: nil, **proc_arguments)
88
+ default_options ||= {}
89
+ ActionController::Base.helpers.link_to(
90
+ value(text, proc_arguments),
91
+ value(href, proc_arguments),
92
+ default_options.deep_merge(value(options, proc_arguments))
93
+ )
94
+ end
95
+
96
+ private
97
+
98
+ attr_reader :text
99
+ attr_reader :href
100
+ attr_reader :options
101
+
102
+ def value(proc_or_value, proc_arguments)
103
+ if proc_or_value.kind_of?(Proc)
104
+ proc_or_value.call(**proc_arguments)
105
+ else
106
+ proc_or_value
107
+ end
108
+ end
109
+ end
110
+ end
@@ -29,19 +29,15 @@ module Super
29
29
  end
30
30
  end
31
31
 
32
+ def necessary?
33
+ pages > 1
34
+ end
35
+
32
36
  def each
33
37
  if !block_given?
34
38
  return enum_for(:each)
35
39
  end
36
40
 
37
- quotient, remainder = @total_count.divmod(@limit)
38
- pages =
39
- if remainder.zero?
40
- quotient
41
- else
42
- quotient + 1
43
- end
44
-
45
41
  (1..pages).each do |pageno|
46
42
  is_current_page = pageno == current_pageno
47
43
  display = pageno.to_s
@@ -55,5 +51,75 @@ module Super
55
51
  yield(page_query_params, is_current_page, display)
56
52
  end
57
53
  end
54
+
55
+ def to_partial_path
56
+ "super_pagination"
57
+ end
58
+
59
+ private
60
+
61
+ def pages
62
+ @pages ||=
63
+ begin
64
+ quotient, remainder = @total_count.divmod(@limit)
65
+
66
+ if remainder.zero?
67
+ quotient
68
+ else
69
+ quotient + 1
70
+ end
71
+ end
72
+ end
73
+
74
+ module ControllerMethods
75
+ def index
76
+ super
77
+ @pagination = controls.initialize_pagination(action: action_inquirer, records: @records, query_params: request.GET)
78
+ @records = controls.paginate_records(action: action_inquirer, records: @records, pagination: @pagination)
79
+ @view.mains.first.parts.push(:@pagination)
80
+ end
81
+ end
82
+ end
83
+
84
+ class Controls
85
+ module Optional
86
+ # Specifies how many records to show per page
87
+ #
88
+ # @param action [ActionInquirer]
89
+ # @param query_params [Hash]
90
+ # @return [ActiveRecord::Relation]
91
+ def records_per_page(action:, query_params:)
92
+ Super.configuration.index_records_per_page
93
+ end
94
+ end
95
+
96
+ module Steps
97
+ # Sets up pagination
98
+ #
99
+ # @param action [ActionInquirer]
100
+ # @param records [ActiveRecord::Relation]
101
+ # @param query_params [Hash]
102
+ # @return [Pagination]
103
+ def initialize_pagination(action:, records:, query_params:)
104
+ Pagination.new(
105
+ total_count: records.size,
106
+ limit: records_per_page(action: action, query_params: query_params),
107
+ query_params: query_params,
108
+ page_query_param: :page
109
+ )
110
+ end
111
+
112
+ # Paginates
113
+ #
114
+ # @param action [ActionInquirer]
115
+ # @param records [ActiveRecord::Relation]
116
+ # @param pagination [Pagination]
117
+ # @return [ActiveRecord::Relation]
118
+ def paginate_records(action:, records:, pagination:)
119
+ records
120
+ .limit(pagination.limit)
121
+ .offset(pagination.offset)
122
+ end
123
+ end
58
124
  end
59
125
  end