wallaby-core 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +31 -0
  4. data/app/controllers/wallaby/application_controller.rb +84 -0
  5. data/app/controllers/wallaby/resources_controller.rb +381 -0
  6. data/app/controllers/wallaby/secure_controller.rb +81 -0
  7. data/app/security/ability.rb +13 -0
  8. data/config/locales/wallaby.en.yml +140 -0
  9. data/config/locales/wallaby_class.en.yml +30 -0
  10. data/config/routes.rb +39 -0
  11. data/lib/adaptors/wallaby/custom.rb +7 -0
  12. data/lib/adaptors/wallaby/custom/default_provider.rb +9 -0
  13. data/lib/adaptors/wallaby/custom/model_decorator.rb +71 -0
  14. data/lib/adaptors/wallaby/custom/model_finder.rb +13 -0
  15. data/lib/adaptors/wallaby/custom/model_pagination_provider.rb +14 -0
  16. data/lib/adaptors/wallaby/custom/model_service_provider.rb +48 -0
  17. data/lib/authorizers/wallaby/cancancan_authorization_provider.rb +72 -0
  18. data/lib/authorizers/wallaby/default_authorization_provider.rb +58 -0
  19. data/lib/authorizers/wallaby/model_authorizer.rb +100 -0
  20. data/lib/authorizers/wallaby/pundit_authorization_provider.rb +89 -0
  21. data/lib/concerns/wallaby/authorizable.rb +103 -0
  22. data/lib/concerns/wallaby/baseable.rb +36 -0
  23. data/lib/concerns/wallaby/decoratable.rb +101 -0
  24. data/lib/concerns/wallaby/defaultable.rb +38 -0
  25. data/lib/concerns/wallaby/engineable.rb +61 -0
  26. data/lib/concerns/wallaby/fieldable.rb +78 -0
  27. data/lib/concerns/wallaby/paginatable.rb +72 -0
  28. data/lib/concerns/wallaby/rails_overridden_methods.rb +42 -0
  29. data/lib/concerns/wallaby/resourcable.rb +149 -0
  30. data/lib/concerns/wallaby/servicable.rb +68 -0
  31. data/lib/concerns/wallaby/shared_helpers.rb +22 -0
  32. data/lib/concerns/wallaby/themeable.rb +40 -0
  33. data/lib/decorators/wallaby/resource_decorator.rb +189 -0
  34. data/lib/errors/wallaby/cell_handling.rb +6 -0
  35. data/lib/errors/wallaby/forbidden.rb +6 -0
  36. data/lib/errors/wallaby/general_error.rb +6 -0
  37. data/lib/errors/wallaby/invalid_error.rb +6 -0
  38. data/lib/errors/wallaby/model_not_found.rb +11 -0
  39. data/lib/errors/wallaby/not_authenticated.rb +6 -0
  40. data/lib/errors/wallaby/not_found.rb +6 -0
  41. data/lib/errors/wallaby/not_implemented.rb +6 -0
  42. data/lib/errors/wallaby/resource_not_found.rb +11 -0
  43. data/lib/errors/wallaby/unprocessable_entity.rb +6 -0
  44. data/lib/forms/wallaby/form_builder.rb +60 -0
  45. data/lib/helpers/wallaby/application_helper.rb +79 -0
  46. data/lib/helpers/wallaby/base_helper.rb +65 -0
  47. data/lib/helpers/wallaby/configuration_helper.rb +18 -0
  48. data/lib/helpers/wallaby/form_helper.rb +62 -0
  49. data/lib/helpers/wallaby/index_helper.rb +84 -0
  50. data/lib/helpers/wallaby/links_helper.rb +213 -0
  51. data/lib/helpers/wallaby/resources_helper.rb +52 -0
  52. data/lib/helpers/wallaby/secure_helper.rb +54 -0
  53. data/lib/helpers/wallaby/styling_helper.rb +82 -0
  54. data/lib/interfaces/wallaby/mode.rb +72 -0
  55. data/lib/interfaces/wallaby/model_authorization_provider.rb +99 -0
  56. data/lib/interfaces/wallaby/model_decorator.rb +168 -0
  57. data/lib/interfaces/wallaby/model_finder.rb +12 -0
  58. data/lib/interfaces/wallaby/model_pagination_provider.rb +107 -0
  59. data/lib/interfaces/wallaby/model_service_provider.rb +84 -0
  60. data/lib/paginators/wallaby/model_paginator.rb +115 -0
  61. data/lib/paginators/wallaby/resource_paginator.rb +12 -0
  62. data/lib/parsers/wallaby/parser.rb +34 -0
  63. data/lib/renderers/wallaby/cell.rb +137 -0
  64. data/lib/renderers/wallaby/cell_resolver.rb +89 -0
  65. data/lib/renderers/wallaby/custom_lookup_context.rb +64 -0
  66. data/lib/renderers/wallaby/custom_partial_renderer.rb +33 -0
  67. data/lib/renderers/wallaby/custom_renderer.rb +16 -0
  68. data/lib/responders/wallaby/json_api_responder.rb +101 -0
  69. data/lib/responders/wallaby/resources_responder.rb +28 -0
  70. data/lib/routes/wallaby/resources_router.rb +72 -0
  71. data/lib/servicers/wallaby/model_servicer.rb +154 -0
  72. data/lib/services/wallaby/engine_name_finder.rb +22 -0
  73. data/lib/services/wallaby/engine_url_for.rb +46 -0
  74. data/lib/services/wallaby/link_options_normalizer.rb +19 -0
  75. data/lib/services/wallaby/map/mode_mapper.rb +27 -0
  76. data/lib/services/wallaby/map/model_class_collector.rb +49 -0
  77. data/lib/services/wallaby/map/model_class_mapper.rb +38 -0
  78. data/lib/services/wallaby/prefixes_builder.rb +66 -0
  79. data/lib/services/wallaby/sorting/hash_builder.rb +19 -0
  80. data/lib/services/wallaby/sorting/link_builder.rb +69 -0
  81. data/lib/services/wallaby/sorting/next_builder.rb +63 -0
  82. data/lib/services/wallaby/sorting/single_builder.rb +20 -0
  83. data/lib/services/wallaby/type_renderer.rb +50 -0
  84. data/lib/support/action_dispatch/routing/mapper.rb +75 -0
  85. data/lib/tree/wallaby/node.rb +25 -0
  86. data/lib/utils/wallaby/cell_utils.rb +34 -0
  87. data/lib/utils/wallaby/field_utils.rb +43 -0
  88. data/lib/utils/wallaby/filter_utils.rb +20 -0
  89. data/lib/utils/wallaby/model_utils.rb +51 -0
  90. data/lib/utils/wallaby/module_utils.rb +46 -0
  91. data/lib/utils/wallaby/params_utils.rb +14 -0
  92. data/lib/utils/wallaby/preload_utils.rb +44 -0
  93. data/lib/utils/wallaby/test_utils.rb +34 -0
  94. data/lib/utils/wallaby/utils.rb +27 -0
  95. data/lib/wallaby/configuration.rb +103 -0
  96. data/lib/wallaby/configuration/features.rb +24 -0
  97. data/lib/wallaby/configuration/mapping.rb +140 -0
  98. data/lib/wallaby/configuration/metadata.rb +23 -0
  99. data/lib/wallaby/configuration/models.rb +46 -0
  100. data/lib/wallaby/configuration/pagination.rb +30 -0
  101. data/lib/wallaby/configuration/security.rb +98 -0
  102. data/lib/wallaby/configuration/sorting.rb +28 -0
  103. data/lib/wallaby/constants.rb +45 -0
  104. data/lib/wallaby/core.rb +117 -0
  105. data/lib/wallaby/core/version.rb +7 -0
  106. data/lib/wallaby/engine.rb +43 -0
  107. data/lib/wallaby/map.rb +170 -0
  108. metadata +222 -0
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Servicer related attributes
5
+ module Servicable
6
+ # Configurable attribute
7
+ module ClassMethods
8
+ # @!attribute [w] model_servicer
9
+ def model_servicer=(model_servicer)
10
+ ModuleUtils.inheritance_check model_servicer, application_servicer
11
+ @model_servicer = model_servicer
12
+ end
13
+
14
+ # @!attribute [r] model_servicer
15
+ # If Wallaby doesn't get it right, please specify the **model_servicer**.
16
+ # @example To set model servicer
17
+ # class Admin::ProductionsController < Admin::ApplicationController
18
+ # self.model_servicer = ProductServicer
19
+ # end
20
+ # @return [Class] model servicer
21
+ # @raise [ArgumentError] when **model_servicer** doesn't inherit from **application_servicer**
22
+ # @see Wallaby::ModelServicer
23
+ # @since 5.2.0
24
+ attr_reader :model_servicer
25
+
26
+ # @!attribute [w] application_servicer
27
+ def application_servicer=(application_servicer)
28
+ ModuleUtils.inheritance_check model_servicer, application_servicer
29
+ @application_servicer = application_servicer
30
+ end
31
+
32
+ # @!attribute [r] application_servicer
33
+ # The **application_servicer** is as the base class of {#model_servicer}.
34
+ # @example To set application decorator:
35
+ # class Admin::ApplicationController < Wallaby::ResourcesController
36
+ # self.application_servicer = AnotherApplicationServicer
37
+ # end
38
+ # @return [Class] application decorator
39
+ # @raise [ArgumentError] when **model_servicer** doesn't inherit from **application_servicer**
40
+ # @see Wallaby::ModelServicer
41
+ # @since 5.2.0
42
+ def application_servicer
43
+ @application_servicer ||= ModuleUtils.try_to superclass, :application_servicer
44
+ end
45
+ end
46
+
47
+ # Model servicer for current modal class. It comes from:
48
+ #
49
+ # - controller configuration {Wallaby::Servicable::ClassMethods#model_servicer .model_servicer}
50
+ # - a generic servicer based on {Wallaby::Servicable::ClassMethods#application_servicer .application_servicer}
51
+ # @return [Wallaby::ModelServicer] model servicer
52
+ # @since 5.2.0
53
+ def current_servicer
54
+ @current_servicer ||=
55
+ (controller_to_get(:model_servicer) \
56
+ || Map.servicer_map(current_model_class, controller_to_get(:application_servicer))).try do |klass|
57
+ Rails.logger.info %( - Current servicer: #{klass})
58
+ klass.new current_model_class, current_authorizer, current_model_decorator
59
+ end
60
+ end
61
+
62
+ # @deprecated Use {#current_servicer} instead. It will be removed from 5.3.*
63
+ def current_model_service
64
+ Utils.deprecate 'deprecation.current_model_service', caller: caller
65
+ current_servicer
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Shared helpers
5
+ module SharedHelpers
6
+ protected
7
+
8
+ # Fetch value for given attribute.
9
+ #
10
+ # If it's used in controller, it will fetch it from class attribute.
11
+ # If it's used in view, it will fetch it from controller.
12
+ # @param attribute_name [String, Symbol] instance attribute name
13
+ # @param class_attribute_name [String, Symbol] class attribute name
14
+ # @return [Object] the value
15
+ def controller_to_get(attribute_name, class_attribute_name = nil)
16
+ class_attribute_name ||= attribute_name
17
+ return ModuleUtils.try_to self.class, class_attribute_name if is_a? ::ActionController::Base # controller?
18
+
19
+ ModuleUtils.try_to controller, attribute_name # view?
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Theme related methods
5
+ module Themeable
6
+ # Configurable attributes
7
+ module ClassMethods
8
+ # @!attribute [w] theme_name
9
+ def theme_name=(theme_name)
10
+ layout theme_name
11
+ @theme_name = theme_name
12
+ end
13
+
14
+ # @!attribute [r] theme_name
15
+ # The theme name is used to apply a set of frontend (html/css/javascript) implementation.
16
+ #
17
+ # When theme name is set to e.g. `custom_theme`, the following changes will be made:
18
+ #
19
+ # - layout will be set to the same name `custom_theme`
20
+ # - it will be added to the partial lookup prefixes right on top of `wallaby/resources` prefix.
21
+ #
22
+ # Once theme name is set, all its controller subclasses will inherit the same theme name
23
+ # @example To set an theme name:
24
+ # class Admin::ApplicationController < Wallaby::ResourcesController
25
+ # self.theme_name = 'admin_theme'
26
+ # end
27
+ # @return [String, Symbol, nil] theme name
28
+ # @since 5.2.0
29
+ def theme_name
30
+ @theme_name ||= ModuleUtils.try_to superclass, :theme_name
31
+ end
32
+ end
33
+
34
+ # @return [String, Symbol, nil] theme name
35
+ # @since 5.2.0
36
+ def current_theme_name
37
+ controller_to_get __callee__, :theme_name
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Resource Decorator base class, designed for decorator pattern.
5
+ # @see Wallaby::ModelDecorator
6
+ class ResourceDecorator
7
+ extend Baseable::ClassMethods
8
+
9
+ DELEGATE_METHODS =
10
+ ModelDecorator.public_instance_methods(false) + Fieldable.public_instance_methods(false) - %i(model_class)
11
+
12
+ class << self
13
+ delegate(*DELEGATE_METHODS, to: :model_decorator, allow_nil: true)
14
+
15
+ # @!attribute [w] model_class
16
+ attr_writer :model_class
17
+
18
+ # @!attribute [r] model_class
19
+ # Return associated model class, e.g. return **Product** for **ProductDecorator**.
20
+ #
21
+ # If Wallaby can't recognise the model class for Decorator, it's required to be configured as below example:
22
+ # @example To configure model class
23
+ # class Admin::ProductDecorator < Admin::ApplicationDecorator
24
+ # self.model_class = Product
25
+ # end
26
+ # @example To configure model class for version below 5.2.0
27
+ # class Admin::ProductDecorator < Admin::ApplicationDecorator
28
+ # def self.model_class
29
+ # Product
30
+ # end
31
+ # end
32
+ # @return [Class] assoicated model class
33
+ # @return [nil] if current class is marked as base class
34
+ # @return [nil] if current class is the same as the value of {Wallaby::Configuration::Mapping#resource_decorator}
35
+ # @return [nil] if current class is {Wallaby::ResourceDecorator}
36
+ # @return [nil] if assoicated model class is not found
37
+ def model_class
38
+ return unless self < ResourceDecorator
39
+ return if base_class? || self == Wallaby.configuration.mapping.resource_decorator
40
+
41
+ @model_class ||= Map.model_class_map(name.gsub(/(^#{namespace}::)|(Decorator$)/, EMPTY_STRING))
42
+ end
43
+
44
+ # @!attribute [w] application_decorator
45
+ def application_decorator=(application_decorator)
46
+ ModuleUtils.inheritance_check self, application_decorator
47
+ @application_decorator = application_decorator
48
+ end
49
+
50
+ # @note This attribute have to be the same as the one defined in the controller in order to make things working.
51
+ # see {Wallaby::Decoratable::ClassMethods#application_decorator}
52
+ # @!attribute [r] application_decorator
53
+ # Assoicated base class.
54
+ #
55
+ # Wallaby will try to get the application decorator class from current class's ancestors chain.
56
+ # For instance, if current class is **ProductDecorator**, and it inherits from **CoreDecorator**,
57
+ # then Wallaby will return application decorator class **CoreDecorator**.
58
+ #
59
+ # However, there is a chance that Wallaby doesn't get it right.
60
+ # For instance, if **CoreDecorator** in the above example inherits from **Admin::ApplicationDecorator**, and
61
+ # the controller that needs **ProductDecorator** has set its **application_decorator** to
62
+ # **Admin::ApplicationDecorator**, then it's needed to configure **application_decorator** as the example
63
+ # describes
64
+ # @example To set application decorator class
65
+ # class ProductController < Admin::ApplicationController
66
+ # self.application_decorator = Admin::ApplicationDecorator
67
+ # end
68
+ #
69
+ # class CoreDecorator < Admin::ApplicationDecorator
70
+ # base_class!
71
+ # end
72
+ #
73
+ # class ProductDecorator < CoreDecorator
74
+ # self.application_decorator = Admin::ApplicationDecorator
75
+ # end
76
+ # @return [Class] assoicated base class.
77
+ # @return [nil] if assoicated base class is not found from its ancestors chain
78
+ # @raise [ArgumentError] when current class doesn't inherit from given value
79
+ # @since 5.2.0
80
+ def application_decorator
81
+ @application_decorator ||= ancestors.find { |parent| parent < ResourceDecorator && !parent.model_class }
82
+ end
83
+
84
+ # Return associated model decorator.
85
+ #
86
+ # Fetch model decorator instance from cached map using keys {.model_class} and {.application_decorator}
87
+ # so that model decorator can be used in its sub classes declaration/scope.
88
+ # @param model_class [Class]
89
+ # @return [Wallaby::ModelDecorator]
90
+ def model_decorator(model_class = self.model_class)
91
+ return unless self < ResourceDecorator || model_class
92
+
93
+ Map.model_decorator_map model_class, application_decorator
94
+ end
95
+
96
+ # @!attribute [w] h
97
+ attr_writer :h
98
+
99
+ # @!attribute [r] h
100
+ # @return [ActionView::Base]
101
+ # {Wallaby::Configuration::Mapping#resources_controller resources controller}'s helpers
102
+ def h
103
+ @h ||= Wallaby.configuration.mapping.resources_controller.helpers
104
+ end
105
+ end
106
+
107
+ # @!attribute [r] resource
108
+ # @return [Object]
109
+ attr_reader :resource
110
+
111
+ # @!attribute [r] model_decorator
112
+ # @return [Wallaby::ModelDecorator]
113
+ attr_reader :model_decorator
114
+
115
+ # @return [ActionView::Base]
116
+ # {Wallaby::Configuration::Mapping#resources_controller resources controller}'s helpers
117
+ # @see .h
118
+ def h
119
+ self.class.h
120
+ end
121
+
122
+ delegate(*DELEGATE_METHODS, to: :model_decorator)
123
+ # NOTE: this delegation is to make url helper method working properly with resource decorator instance
124
+ delegate :to_s, :to_param, to: :resource
125
+
126
+ # @param resource [Object]
127
+ def initialize(resource)
128
+ @resource = resource
129
+ @model_decorator = self.class.model_decorator(model_class)
130
+ end
131
+
132
+ # @return [Class] resource's class
133
+ def model_class
134
+ resource.class
135
+ end
136
+
137
+ # @param field_name [String, Symbol]
138
+ # @return [Object] value of given field name
139
+ def value_of(field_name)
140
+ return unless field_name
141
+
142
+ resource.try field_name
143
+ end
144
+
145
+ # Guess the title for given resource.
146
+ #
147
+ # It falls back to primary key value when no text field is found.
148
+ # @return [String] a label
149
+ def to_label
150
+ # NOTE: `.to_s` at the end is to ensure String is returned that won't cause any
151
+ # issue when `#to_label` is used in a link_to block. Coz integer is ignored.
152
+ (model_decorator.guess_title(resource) || primary_key_value).to_s
153
+ end
154
+
155
+ # @return [Hash, Array] validation/result errors
156
+ def errors
157
+ model_decorator.form_active_errors(resource)
158
+ end
159
+
160
+ # @return [Object] primary key value
161
+ def primary_key_value
162
+ resource.try primary_key
163
+ end
164
+
165
+ # @return [ActiveModel::Name]
166
+ def model_name
167
+ ModuleUtils.try_to(resource, :model_name) || ActiveModel::Name.new(model_class)
168
+ end
169
+
170
+ # @return [nil] if no primary key
171
+ # @return [Array<String>] primary key
172
+ def to_key
173
+ key = ModuleUtils.try_to(resource, primary_key)
174
+ key ? [key] : nil
175
+ end
176
+
177
+ # Delegate missing method to {#resource}
178
+ def method_missing(method_id, *args, &block)
179
+ return super unless resource.respond_to? method_id
180
+
181
+ resource.try method_id, *args, &block
182
+ end
183
+
184
+ # Delegate missing method check to context
185
+ def respond_to_missing?(method_id, _include_private)
186
+ resource.respond_to?(method_id) || super
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ class CellHandling < ::StandardError
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ class Forbidden < GeneralError
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ class GeneralError < ::StandardError
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ class InvalidError < GeneralError
5
+ end
6
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Model not found error
5
+ class ModelNotFound < NotFound
6
+ # @return [String] not found error message
7
+ def message
8
+ I18n.t 'errors.not_found.model', model: super
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ class NotAuthenticated < GeneralError
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ class NotFound < GeneralError
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ class NotImplemented < GeneralError
5
+ end
6
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Resource not found error
5
+ class ResourceNotFound < NotFound
6
+ # @return [String] resource not found error message
7
+ def message
8
+ I18n.t 'errors.not_found.resource', resource: super
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ class UnprocessableEntity < GeneralError
5
+ end
6
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Custom form builder to add more helper functions
5
+ class FormBuilder < ::ActionView::Helpers::FormBuilder
6
+ # Return error class if there is error
7
+ # @param field_name [String/Symbol]
8
+ # @return [String]
9
+ def error_class(field_name)
10
+ 'has-error' if object.errors[field_name].present?
11
+ end
12
+
13
+ # Build up the HTML for displaying error messages
14
+ # @param field_name [String/Symbol]
15
+ # @return [String] HTML
16
+ def error_messages(field_name)
17
+ errors = Array object.errors[field_name]
18
+ return if errors.blank?
19
+
20
+ content_tag :ul, class: 'errors' do
21
+ errors.each do |message|
22
+ concat content_tag :li, content_tag(
23
+ :small, raw(message) # rubocop:disable Rails/OutputSafety
24
+ )
25
+ end
26
+ end
27
+ end
28
+
29
+ # Extend label to accept proc type `text` argument
30
+ # @see ActionView::Helpers::FormBuilder#label
31
+ def label(method, text = nil, options = {}, &block)
32
+ text = instance_exec(&text) if text.is_a?(Proc)
33
+ super
34
+ end
35
+
36
+ # Extend select to accept proc type `choices` argument
37
+ # @see ActionView::Helpers::FormBuilder#select
38
+ def select(method, choices = nil, options = {}, html_options = {}, &block)
39
+ choices = instance_exec(&choices) if choices.is_a?(Proc)
40
+ super
41
+ end
42
+
43
+ protected
44
+
45
+ # Delegate missing method to `@template`
46
+ def method_missing(method, *args, &block)
47
+ return super unless @template.respond_to? method
48
+
49
+ # Delegate the method so that we don't come in here the next time
50
+ # when same method is called
51
+ self.class.delegate method, to: :@template
52
+ @template.public_send method, *args, &block
53
+ end
54
+
55
+ # Delegate missing method check to `@template`
56
+ def respond_to_missing?(method, _include_private)
57
+ @template.respond_to?(method) || super
58
+ end
59
+ end
60
+ end