super 0.0.2 → 0.0.3

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