super 0.0.2 → 0.0.3

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +22 -13
  3. data/app/assets/javascripts/super/application.js +10970 -64
  4. data/app/assets/stylesheets/super/application.css +33270 -14020
  5. data/app/controllers/super/application_controller.rb +1 -0
  6. data/app/views/super/application/_form.html.erb +1 -2
  7. data/app/views/super/application/_form_field__destroy.html.erb +5 -0
  8. data/app/views/super/application/{_form_generic_select.html.erb → _form_field_select.html.erb} +3 -5
  9. data/app/views/super/application/{_form_generic_text.html.erb → _form_field_text.html.erb} +1 -3
  10. data/app/views/super/application/_form_fieldset.html.erb +8 -0
  11. data/app/views/super/application/_form_has_many.html.erb +21 -0
  12. data/app/views/super/application/_form_has_one.html.erb +11 -0
  13. data/app/views/super/application/_form_inline_errors.html.erb +10 -0
  14. data/frontend/super-frontend/build.js +3 -1
  15. data/frontend/super-frontend/dist/application.css +33270 -14020
  16. data/frontend/super-frontend/dist/application.js +10970 -64
  17. data/frontend/super-frontend/package.json +5 -2
  18. data/frontend/super-frontend/src/javascripts/super/{application.js → application.ts} +3 -1
  19. data/frontend/super-frontend/src/javascripts/super/nested_attributes_controller.ts +33 -0
  20. data/frontend/super-frontend/src/javascripts/super/rails__ujs.d.ts +1 -0
  21. data/frontend/super-frontend/tsconfig.json +13 -0
  22. data/frontend/super-frontend/yarn.lock +1559 -1523
  23. data/lib/super/action_inquirer.rb +13 -0
  24. data/lib/super/assets.rb +1 -0
  25. data/lib/super/configuration.rb +59 -44
  26. data/lib/super/controls.rb +31 -15
  27. data/lib/super/display/schema_types.rb +15 -16
  28. data/lib/super/engine.rb +1 -0
  29. data/lib/super/error.rb +1 -0
  30. data/lib/super/form/schema_types.rb +89 -21
  31. data/lib/super/navigation/automatic.rb +2 -0
  32. data/lib/super/schema.rb +50 -1
  33. data/lib/super/test_support/copy_app_templates/controllers/favorite_things_controller.rb +50 -0
  34. data/lib/super/test_support/copy_app_templates/{members_controller.rb → controllers/members_controller.rb} +10 -5
  35. data/lib/super/test_support/copy_app_templates/{ships_controller.rb → controllers/ships_controller.rb} +3 -3
  36. data/lib/super/test_support/copy_app_templates/{20190216224956_create_members.rb → migrations/20190216224956_create_members.rb} +0 -0
  37. data/lib/super/test_support/copy_app_templates/{20190803143320_create_ships.rb → migrations/20190803143320_create_ships.rb} +0 -0
  38. data/lib/super/test_support/copy_app_templates/{20190806014121_add_ship_to_members.rb → migrations/20190806014121_add_ship_to_members.rb} +0 -0
  39. data/lib/super/test_support/copy_app_templates/migrations/20191126050453_create_favorite_things.rb +10 -0
  40. data/lib/super/test_support/copy_app_templates/models/favorite_thing.rb +7 -0
  41. data/lib/super/test_support/copy_app_templates/{member.rb → models/member.rb} +7 -0
  42. data/lib/super/test_support/copy_app_templates/{ship.rb → models/ship.rb} +0 -0
  43. data/lib/super/test_support/copy_app_templates/routes.rb +1 -0
  44. data/lib/super/test_support/fixtures/favorite_things.yml +9 -0
  45. data/lib/super/test_support/generate_copy_app.rb +5 -16
  46. data/lib/super/test_support/generate_dummy.rb +0 -1
  47. data/lib/super/test_support/starfleet_seeder.rb +1 -0
  48. data/lib/super/version.rb +1 -1
  49. metadata +25 -14
  50. data/app/views/super/application/_form_field.html.erb +0 -7
@@ -1,7 +1,20 @@
1
1
  module Super
2
+ # ```ruby
3
+ # action = Super::ActionInquirer.new(
4
+ # Super::ActionInquirer.default_resources,
5
+ # :index
6
+ # )
7
+ #
8
+ # action.read? # => true
9
+ # action.index? # => true
10
+ # action.show? # => false
11
+ # action.write? # => false
12
+ # ```
2
13
  class ActionInquirer
3
14
  attr_reader :action
4
15
 
16
+ # @return [Hash<Symbol, Array<Symbol>>] default settings for initialization
17
+ #
5
18
  def self.default_resources
6
19
  {
7
20
  read: %i[index show new edit],
data/lib/super/assets.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  module Super
2
+ # Utilities for determining whether to use Sprockets or Webpacker
2
3
  class Assets
3
4
  def self.sprockets_available?
4
5
  Gem::Dependency.new("sprockets").matching_specs.any?
@@ -1,4 +1,6 @@
1
1
  module Super
2
+ # @yield [Configuration]
3
+ # @return [Configuration]
2
4
  def self.configuration
3
5
  @configuration ||= Configuration.new
4
6
 
@@ -9,78 +11,91 @@ module Super
9
11
  @configuration
10
12
  end
11
13
 
12
- module ConfigurationLogic
13
- def self.included(base)
14
- base.extend(ClassMethods)
15
- end
16
-
17
- def initialize
18
- self.class.defaults.each do |key, value|
19
- if value.respond_to?(:call)
20
- value = value.call
21
- end
22
-
23
- public_send("#{key}=", value)
14
+ # Allows setting global configuration
15
+ #
16
+ # ```ruby
17
+ # Super.configuration do |c|
18
+ # c.title = "My Admin Site"
19
+ # end
20
+ # ```
21
+ class Configuration
22
+ module ConfigurationLogic # @api private
23
+ def self.included(base)
24
+ base.extend(ClassMethods)
24
25
  end
25
- end
26
26
 
27
- def configured?(attr)
28
- instance_variable_defined?("@#{attr}")
29
- end
27
+ def initialize
28
+ self.class.defaults.each do |key, value|
29
+ if value.respond_to?(:call)
30
+ value = value.call
31
+ end
30
32
 
31
- module ClassMethods
32
- def defaults
33
- @defaults ||= {}
33
+ public_send("#{key}=", value)
34
+ end
34
35
  end
35
36
 
36
- def wraps
37
- @wraps ||= {}
37
+ def configured?(attr)
38
+ instance_variable_defined?("@#{attr}")
38
39
  end
39
40
 
40
- def configure(attr, wrap: nil, enum: nil, **kwargs)
41
- if kwargs.key?(:default)
42
- defaults[attr] = kwargs[:default]
41
+ module ClassMethods
42
+ def defaults
43
+ @defaults ||= {}
43
44
  end
44
45
 
45
- define_method(attr) do
46
- if !configured?(attr)
47
- raise Error::UnconfiguredConfiguration, "unconfigured: #{attr}"
46
+ def wraps
47
+ @wraps ||= {}
48
+ end
49
+
50
+ def configure(attr, wrap: nil, enum: nil, **kwargs)
51
+ if kwargs.key?(:default)
52
+ defaults[attr] = kwargs[:default]
48
53
  end
49
54
 
50
- result = instance_variable_get("@#{attr}")
55
+ define_method(attr) do
56
+ if !configured?(attr)
57
+ raise Error::UnconfiguredConfiguration, "unconfigured: #{attr}"
58
+ end
51
59
 
52
- if wrap.nil?
53
- result
54
- else
55
- wrap.call(result)
56
- end
57
- end
60
+ result = instance_variable_get("@#{attr}")
58
61
 
59
- define_method("#{attr}=") do |value|
60
- if enum.is_a?(Array)
61
- if !enum.include?(value)
62
- raise Error::InvalidConfiguration,
63
- "tried to set `#{attr}` to `#{value.inspect}`, " \
64
- "expected: #{enum.join(", ")}"
62
+ if wrap.nil?
63
+ result
64
+ else
65
+ wrap.call(result)
65
66
  end
66
67
  end
67
68
 
68
- instance_variable_set("@#{attr}", value)
69
- value
69
+ define_method("#{attr}=") do |value|
70
+ if enum.is_a?(Array)
71
+ if !enum.include?(value)
72
+ raise Error::InvalidConfiguration,
73
+ "tried to set `#{attr}` to `#{value.inspect}`, " \
74
+ "expected: #{enum.join(", ")}"
75
+ end
76
+ end
77
+
78
+ instance_variable_set("@#{attr}", value)
79
+ value
80
+ end
70
81
  end
71
82
  end
72
83
  end
73
- end
74
84
 
75
- class Configuration
76
85
  include ConfigurationLogic
77
86
 
87
+ # @!attribute [rw]
78
88
  configure :title
89
+ # @!attribute [rw]
79
90
  configure :index_resources_per_page, default: 20
91
+ # @!attribute [rw]
80
92
  configure :controller_namespace, default: "admin"
93
+ # @!attribute [rw]
81
94
  configure :route_namespace, default: :admin, wrap: -> (val) { [val].flatten }
95
+ # @!attribute [rw]
82
96
  configure :asset_handler, default: -> { Super::Assets.auto }
83
97
 
98
+ # @api private
84
99
  def path_parts(*parts)
85
100
  route_namespace + parts
86
101
  end
@@ -1,25 +1,41 @@
1
1
  module Super
2
+ # A wrapper around the per-controller Controls classes. This class often
3
+ # directly delegates to the per-controller classes, but it can also provide
4
+ # some default implementation.
2
5
  class Controls
3
- def initialize(dashboard)
4
- @dashboard = dashboard
6
+ def initialize(actual)
7
+ @actual = actual
5
8
  end
6
9
 
7
- attr_reader :dashboard
10
+ attr_reader :actual
8
11
 
9
- def method_missing(method_name, *args)
10
- if @dashboard.respond_to?(method_name)
11
- @dashboard.public_send(method_name, *args)
12
- else
13
- super
14
- end
12
+ def title
13
+ @actual.title
15
14
  end
16
15
 
17
- def respond_to_missing?(method_name, _ = false)
18
- if @dashboard.respond_to?(method_name)
19
- true
20
- else
21
- super
22
- end
16
+ def model
17
+ @actual.model
18
+ end
19
+
20
+ # @param action [ActionInquirer]
21
+ def scope(action:)
22
+ @actual.scope(action: action)
23
+ end
24
+
25
+ # @param params [ActionController::Parameters]
26
+ # @param action [ActionInquirer]
27
+ def permitted_params(params, action:)
28
+ @actual.permitted_params(params, action: action)
29
+ end
30
+
31
+ # @param action [ActionInquirer]
32
+ def display_schema(action:)
33
+ @actual.display_schema(action: action)
34
+ end
35
+
36
+ # @param action [ActionInquirer]
37
+ def form_schema(action:)
38
+ @actual.form_schema(action: action)
23
39
  end
24
40
  end
25
41
  end
@@ -3,25 +3,24 @@ 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)
data/lib/super/engine.rb CHANGED
@@ -1,4 +1,5 @@
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?
data/lib/super/error.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  module Super
2
+ # A container class for all custom errors thrown by this library
2
3
  class Error < StandardError
3
4
  class UnconfiguredConfiguration < Error; end
4
5
  class InvalidConfiguration < Error; end
@@ -2,33 +2,41 @@ module Super
2
2
  class Form
3
3
  # This schema type is used on your +#edit+ and +#new+ forms
4
4
  #
5
- # Note: The constants under "Defined Under Namespace" are considered
6
- # private.
5
+ # ```ruby
6
+ # class MembersController::Controls
7
+ # # ...
7
8
  #
8
- # class MemberDashboard
9
- # # ...
10
- #
11
- # def new_schema
12
- # Super::Schema.new(Super::Form::SchemaTypes.new) do |fields, type|
13
- # fields[:name] = type.generic("write_type_text")
14
- # fields[:rank] = type.generic("write_type_select", collection: Member.ranks.keys)
15
- # fields[:position] = type.generic("write_type_text")
16
- # fields[:ship_id] = type.generic(
17
- # "write_type_select",
18
- # collection: Ship.all.map { |s| ["#{s.name} (Ship ##{s.id})", s.id] },
19
- # )
20
- # end
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
+ # )
21
18
  # end
22
- #
23
- # # ...
24
19
  # end
20
+ #
21
+ # # ...
22
+ # end
23
+ # ```
25
24
  class SchemaTypes
26
25
  class Generic
27
- def initialize(partial_path:, extras:)
26
+ def initialize(partial_path:, extras:, nested:)
28
27
  @partial_path = partial_path
29
28
  @extras = extras
29
+ @nested_fields = nested
30
30
  end
31
31
 
32
+ attr_reader :nested_fields
33
+
34
+ # This takes advantage of a feature of Rails. If the value of
35
+ # `#to_partial_path` is `my_form_field`, Rails renders
36
+ # `app/views/super/application/_my_form_field.html.erb`, and this
37
+ # instance of Generic is accessible via `my_form_field`
38
+ #
39
+ # @return [String] the filename of the partial that will be rendered.
32
40
  def to_partial_path
33
41
  @partial_path
34
42
  end
@@ -36,11 +44,71 @@ module Super
36
44
  def [](key)
37
45
  @extras[key]
38
46
  end
47
+
48
+ def reader
49
+ @extras[:reader]
50
+ end
51
+
52
+ def label
53
+ if @extras.key?(:label)
54
+ return @extras[:label]
55
+ end
56
+
57
+ if @extras.key?(:reader)
58
+ return @extras[:reader].to_s.singularize.humanize
59
+ end
60
+ end
61
+
62
+ def ==(other)
63
+ return false if other.class != self.class
64
+ return false if other.instance_variable_get(:@partial_path) != @partial_path
65
+ return false if other.instance_variable_get(:@extras) != @extras
66
+ return false if other.instance_variable_get(:@nested) != @nested
67
+
68
+ true
69
+ end
39
70
  end
40
71
 
41
- def generic(partial_path, extras = nil)
42
- extras ||= {}
43
- Generic.new(partial_path: partial_path, extras: extras)
72
+ def setup(fields:)
73
+ @fields = fields
74
+ end
75
+
76
+ def generic(partial_path, **extras)
77
+ Generic.new(partial_path: partial_path, extras: extras, nested: {})
78
+ end
79
+
80
+ def has_many(reader, **extras)
81
+ nested = @fields.nested do
82
+ yield
83
+ end
84
+
85
+ Generic.new(
86
+ partial_path: "form_has_many",
87
+ extras: extras.merge(reader: reader),
88
+ nested: nested
89
+ )
90
+ end
91
+
92
+ def has_one(reader, **extras)
93
+ nested = @fields.nested do
94
+ yield
95
+ end
96
+
97
+ Generic.new(
98
+ partial_path: "form_has_one",
99
+ extras: extras.merge(reader: reader),
100
+ nested: nested
101
+ )
102
+ end
103
+
104
+ alias_method :belongs_to, :has_one
105
+
106
+ def _destroy(**extras)
107
+ Generic.new(
108
+ partial_path: "form_field__destroy",
109
+ extras: extras,
110
+ nested: {}
111
+ )
44
112
  end
45
113
  end
46
114
  end
@@ -1,5 +1,7 @@
1
1
  module Super
2
2
  class Navigation
3
+ # Traverses the defined Rails Routes and attempts to build a list of links.
4
+ # This is used for building the nav bar on each admin page.
3
5
  class Automatic
4
6
  def initialize(route_namespace:)
5
7
  route_namespace = route_namespace.to_s
data/lib/super/schema.rb CHANGED
@@ -8,7 +8,11 @@ module Super
8
8
  # @param schema_type [Display::SchemaTypes, Form::SchemaTypes]
9
9
  def initialize(schema_type)
10
10
  @schema_type = schema_type
11
- @fields = {}
11
+ @fields = Fields.new
12
+
13
+ if @schema_type.respond_to?(:setup)
14
+ @schema_type.setup(fields: @fields)
15
+ end
12
16
 
13
17
  if block_given?
14
18
  yield(@fields, @schema_type)
@@ -20,5 +24,50 @@ module Super
20
24
  def field_keys
21
25
  fields.keys
22
26
  end
27
+
28
+ class Fields
29
+ include Enumerable
30
+
31
+ def initialize
32
+ @backing = {}
33
+ end
34
+
35
+ def [](key)
36
+ @backing[key]
37
+ end
38
+
39
+ def []=(key, value)
40
+ @backing[key] = value
41
+ end
42
+
43
+ def keys
44
+ @backing.keys
45
+ end
46
+
47
+ def each
48
+ if block_given?
49
+ return @backing.each(&Proc.new)
50
+ end
51
+
52
+ enum_for(:each)
53
+ end
54
+
55
+ def replace(other)
56
+ @backing = other
57
+ end
58
+
59
+ def to_h
60
+ @backing
61
+ end
62
+
63
+ def nested
64
+ outside = @backing
65
+ inside = {}
66
+ @backing = inside
67
+ yield
68
+ @backing = outside
69
+ inside
70
+ end
71
+ end
23
72
  end
24
73
  end