wallaby-core 0.2.9 → 0.3.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/controllers/wallaby/resources_controller.rb +20 -7
- data/app/security/ability.rb +1 -1
- data/config/routes.rb +21 -14
- data/lib/adaptors/wallaby/custom/default_provider.rb +1 -1
- data/lib/adaptors/wallaby/custom/model_decorator.rb +5 -17
- data/lib/adaptors/wallaby/custom/model_finder.rb +3 -3
- data/lib/adaptors/wallaby/custom/model_pagination_provider.rb +1 -1
- data/lib/adaptors/wallaby/custom/model_service_provider.rb +1 -1
- data/lib/authorizers/wallaby/cancancan_authorization_provider.rb +12 -5
- data/lib/authorizers/wallaby/default_authorization_provider.rb +10 -1
- data/lib/authorizers/wallaby/model_authorizer.rb +41 -16
- data/lib/authorizers/wallaby/pundit_authorization_provider.rb +22 -8
- data/lib/concerns/wallaby/application_concern.rb +41 -71
- data/lib/concerns/wallaby/authentication_concern.rb +29 -127
- data/lib/concerns/wallaby/authorizable.rb +14 -57
- data/lib/concerns/wallaby/baseable.rb +24 -57
- data/lib/concerns/wallaby/configurable.rb +416 -0
- data/lib/concerns/wallaby/decoratable.rb +24 -60
- data/lib/concerns/wallaby/engineable.rb +29 -46
- data/lib/concerns/wallaby/fieldable.rb +45 -56
- data/lib/concerns/wallaby/paginatable.rb +20 -51
- data/lib/concerns/wallaby/prefixable.rb +24 -4
- data/lib/concerns/wallaby/resourcable.rb +130 -72
- data/lib/concerns/wallaby/resources_concern.rb +205 -305
- data/lib/concerns/wallaby/servicable.rb +8 -48
- data/lib/concerns/wallaby/urlable.rb +69 -0
- data/lib/decorators/wallaby/resource_decorator.rb +72 -34
- data/lib/errors/wallaby/class_not_found.rb +1 -2
- data/lib/errors/wallaby/forbidden.rb +1 -2
- data/lib/errors/wallaby/general_error.rb +1 -1
- data/lib/errors/wallaby/invalid_error.rb +1 -2
- data/lib/errors/wallaby/method_removed.rb +5 -0
- data/lib/errors/wallaby/model_not_found.rb +1 -2
- data/lib/errors/wallaby/not_authenticated.rb +1 -2
- data/lib/errors/wallaby/not_found.rb +1 -2
- data/lib/errors/wallaby/not_implemented.rb +1 -2
- data/lib/errors/wallaby/resource_not_found.rb +1 -2
- data/lib/errors/wallaby/unprocessable_entity.rb +1 -2
- data/lib/fields/wallaby/all_fields.rb +63 -0
- data/lib/forms/wallaby/form_builder.rb +2 -2
- data/lib/generators/wallaby/engine/application_generator.rb +33 -0
- data/lib/generators/wallaby/engine/authorizer/USAGE +20 -0
- data/lib/generators/wallaby/engine/authorizer/authorizer_generator.rb +19 -0
- data/lib/generators/wallaby/engine/authorizer/templates/authorizer.rb.erb +35 -0
- data/lib/generators/wallaby/engine/controller/USAGE +20 -0
- data/lib/generators/wallaby/engine/controller/controller_generator.rb +23 -0
- data/lib/generators/wallaby/engine/controller/templates/controller.rb.erb +130 -0
- data/lib/generators/wallaby/engine/decorator/USAGE +20 -0
- data/lib/generators/wallaby/engine/decorator/decorator_generator.rb +19 -0
- data/lib/generators/wallaby/engine/decorator/templates/decorator.rb.erb +5 -0
- data/lib/generators/wallaby/engine/install/USAGE +19 -0
- data/lib/generators/wallaby/engine/install/install_generator.rb +91 -0
- data/lib/generators/wallaby/engine/install/templates/application_authorizer.rb.erb +37 -0
- data/lib/generators/wallaby/engine/install/templates/application_controller.rb.erb +173 -0
- data/lib/generators/wallaby/engine/install/templates/application_decorator.rb.erb +7 -0
- data/lib/generators/wallaby/engine/install/templates/application_paginator.rb.erb +27 -0
- data/lib/generators/wallaby/engine/install/templates/application_servicer.rb.erb +47 -0
- data/lib/generators/wallaby/engine/install/templates/initializer.rb.erb +16 -0
- data/lib/generators/wallaby/engine/paginator/USAGE +20 -0
- data/lib/generators/wallaby/engine/paginator/paginator_generator.rb +19 -0
- data/lib/generators/wallaby/engine/paginator/templates/paginator.rb.erb +25 -0
- data/lib/generators/wallaby/engine/servicer/USAGE +20 -0
- data/lib/generators/wallaby/engine/servicer/servicer_generator.rb +19 -0
- data/lib/generators/wallaby/engine/servicer/templates/servicer.rb.erb +45 -0
- data/lib/helpers/wallaby/application_helper.rb +10 -59
- data/lib/helpers/wallaby/base_helper.rb +11 -11
- data/lib/helpers/wallaby/configuration_helper.rb +36 -4
- data/lib/helpers/wallaby/form_helper.rb +1 -1
- data/lib/helpers/wallaby/index_helper.rb +19 -9
- data/lib/helpers/wallaby/links_helper.rb +13 -80
- data/lib/helpers/wallaby/resources_helper.rb +39 -7
- data/lib/helpers/wallaby/secure_helper.rb +20 -19
- data/lib/interfaces/wallaby/mode.rb +8 -8
- data/lib/interfaces/wallaby/model_authorization_provider.rb +23 -22
- data/lib/interfaces/wallaby/model_decorator.rb +36 -48
- data/lib/interfaces/wallaby/model_finder.rb +3 -3
- data/lib/interfaces/wallaby/model_pagination_provider.rb +2 -6
- data/lib/interfaces/wallaby/model_service_provider.rb +4 -4
- data/lib/paginators/wallaby/model_paginator.rb +1 -1
- data/lib/responders/wallaby/json_api_responder.rb +10 -5
- data/lib/responders/wallaby/resources_responder.rb +7 -2
- data/lib/routes/wallaby/engines/base_route.rb +78 -0
- data/lib/routes/wallaby/engines/custom_app_route.rb +92 -0
- data/lib/routes/wallaby/engines/engine_route.rb +77 -0
- data/lib/routes/wallaby/resources_router.rb +100 -45
- data/lib/servicers/wallaby/model_servicer.rb +13 -13
- data/lib/services/wallaby/authorizer_finder.rb +23 -0
- data/lib/services/wallaby/class_finder.rb +42 -0
- data/lib/services/wallaby/controller_finder.rb +29 -0
- data/lib/services/wallaby/decorator_finder.rb +34 -0
- data/lib/services/wallaby/default_models_excluder.rb +45 -0
- data/lib/services/wallaby/engine_name_finder.rb +14 -11
- data/lib/services/wallaby/engine_url_for.rb +82 -37
- data/lib/services/wallaby/fields_regulator.rb +34 -0
- data/lib/services/wallaby/map/mode_mapper.rb +4 -4
- data/lib/services/wallaby/map/model_class_mapper.rb +1 -1
- data/lib/services/wallaby/model_class_filter.rb +29 -0
- data/lib/services/wallaby/paginator_finder.rb +24 -0
- data/lib/services/wallaby/prefixes_builder.rb +49 -8
- data/lib/services/wallaby/servicer_finder.rb +31 -0
- data/lib/services/wallaby/sorting/hash_builder.rb +9 -0
- data/lib/services/wallaby/sorting/link_builder.rb +7 -10
- data/lib/services/wallaby/sorting/next_builder.rb +1 -12
- data/lib/services/wallaby/sorting/single_builder.rb +1 -1
- data/lib/support/action_dispatch/routing/mapper.rb +29 -4
- data/lib/utils/wallaby/field_utils.rb +9 -8
- data/lib/utils/wallaby/inflector.rb +94 -0
- data/lib/utils/wallaby/locale.rb +2 -2
- data/lib/utils/wallaby/module_utils.rb +3 -10
- data/lib/utils/wallaby/utils.rb +21 -14
- data/lib/wallaby/class_array.rb +18 -13
- data/lib/wallaby/class_hash.rb +16 -14
- data/lib/wallaby/classifier.rb +4 -2
- data/lib/wallaby/configuration/features.rb +8 -2
- data/lib/wallaby/configuration/mapping.rb +66 -112
- data/lib/wallaby/configuration/metadata.rb +15 -12
- data/lib/wallaby/configuration/models.rb +27 -25
- data/lib/wallaby/configuration/pagination.rb +15 -19
- data/lib/wallaby/configuration/security.rb +88 -80
- data/lib/wallaby/configuration/sorting.rb +15 -17
- data/lib/wallaby/configuration.rb +58 -23
- data/lib/wallaby/constants.rb +21 -13
- data/lib/wallaby/core/version.rb +1 -1
- data/lib/wallaby/core.rb +34 -10
- data/lib/wallaby/deprecator.rb +81 -0
- data/lib/wallaby/engine.rb +1 -19
- data/lib/wallaby/guesser.rb +45 -0
- data/lib/wallaby/logger.rb +35 -13
- data/lib/wallaby/map.rb +14 -87
- data/lib/wallaby/preloader.rb +13 -25
- metadata +120 -15
- data/config/locales/wallaby_class.en.yml +0 -9
- data/lib/concerns/wallaby/defaultable.rb +0 -38
- data/lib/concerns/wallaby/shared_helpers.rb +0 -22
- data/lib/services/wallaby/map/model_class_collector.rb +0 -49
- data/lib/services/wallaby/type_renderer.rb +0 -40
- data/lib/utils/wallaby/model_utils.rb +0 -52
- data/lib/utils/wallaby/test_utils.rb +0 -34
@@ -4,32 +4,32 @@ module Wallaby
|
|
4
4
|
# This is the interface that all ORM modes should inherit from and implement.
|
5
5
|
class Mode
|
6
6
|
class << self
|
7
|
-
# @see
|
7
|
+
# @see ModelDecorator
|
8
8
|
# @return [Class] model decorator for the mode
|
9
9
|
def model_decorator
|
10
10
|
check_and_constantize __callee__
|
11
11
|
end
|
12
12
|
|
13
|
-
# @see
|
13
|
+
# @see ModelFinder
|
14
14
|
# @return [Class] model finder for the mode
|
15
15
|
def model_finder
|
16
16
|
check_and_constantize __callee__
|
17
17
|
end
|
18
18
|
|
19
|
-
# @see
|
19
|
+
# @see ModelServiceProvider
|
20
20
|
# @return [Class] service provider for the mode
|
21
21
|
def model_service_provider
|
22
22
|
check_and_constantize __callee__
|
23
23
|
end
|
24
24
|
|
25
|
-
# @see
|
25
|
+
# @see ModelPaginationProvider
|
26
26
|
# @return [Class] pagination provider for the mode
|
27
27
|
# @since wallaby-5.2.0
|
28
28
|
def model_pagination_provider
|
29
29
|
check_and_constantize __callee__
|
30
30
|
end
|
31
31
|
|
32
|
-
# @see
|
32
|
+
# @see ModelPaginationProvider
|
33
33
|
# @return [Class] pagination provider for the mode
|
34
34
|
# @since wallaby-5.2.0
|
35
35
|
def default_authorization_provider
|
@@ -37,7 +37,7 @@ module Wallaby
|
|
37
37
|
end
|
38
38
|
|
39
39
|
# Return a list of authorization providers for authorizer to detect which one to use.
|
40
|
-
# @see
|
40
|
+
# @see ModelAuthorizationProvider
|
41
41
|
# @return [ActiveSupport::HashWithIndifferentAccess<String, Class>] authorization provider hash
|
42
42
|
# @since wallaby-5.2.0
|
43
43
|
def model_authorization_providers(classes = ModelAuthorizationProvider.descendants)
|
@@ -51,7 +51,7 @@ module Wallaby
|
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
-
|
54
|
+
protected
|
55
55
|
|
56
56
|
# @return [Class] constantized class
|
57
57
|
def check_and_constantize(method_id)
|
@@ -61,7 +61,7 @@ module Wallaby
|
|
61
61
|
class_name.constantize.tap do |klass|
|
62
62
|
next if klass < parent_class
|
63
63
|
|
64
|
-
raise InvalidError,
|
64
|
+
raise InvalidError, "#{class_name} must inherit from #{parent_class}."
|
65
65
|
end
|
66
66
|
rescue NameError => e
|
67
67
|
Logger.error e
|
@@ -9,8 +9,8 @@ module Wallaby
|
|
9
9
|
attr_writer :provider_name
|
10
10
|
|
11
11
|
# @!attribute [r] provider_name
|
12
|
-
# This is the provider name
|
13
|
-
#
|
12
|
+
# This is the provider name (e.g. `:default`/`:cancancan`/`:pundit`)
|
13
|
+
# that can be set in {ModelAuthorizer} subclasses's {ModelAuthorizer.provider_name}.
|
14
14
|
# @return [String/Symbol] provider name
|
15
15
|
def provider_name
|
16
16
|
@provider_name ||= name.demodulize.gsub(/(Authorization)?Provider/, EMPTY_STRING).underscore
|
@@ -18,37 +18,38 @@ module Wallaby
|
|
18
18
|
|
19
19
|
# @note Template method to check and see if current provider is in used.
|
20
20
|
# @param _context [ActionController::Base, ActionView::Base]
|
21
|
-
# @raise [
|
21
|
+
# @raise [NotImplemented]
|
22
22
|
def available?(_context)
|
23
23
|
raise NotImplemented
|
24
24
|
end
|
25
|
-
end
|
26
|
-
|
27
|
-
# @!attribute [r] context
|
28
|
-
# @return [ActionController::Base, ActionView::Base]
|
29
|
-
attr_reader :context
|
30
25
|
|
31
|
-
|
32
|
-
|
33
|
-
|
26
|
+
# @note Template method to get the required data from context.
|
27
|
+
# @param _context [ActionController::Base, ActionView::Base]
|
28
|
+
# @raise [NotImplemented]
|
29
|
+
def options_from(_context)
|
30
|
+
raise NotImplemented
|
31
|
+
end
|
32
|
+
end
|
34
33
|
|
35
34
|
# @!attribute [r] options
|
36
35
|
# @return [Hash]
|
37
36
|
attr_reader :options
|
38
37
|
|
39
|
-
# @param context [ActionController::Base, ActionView::Base]
|
40
38
|
# @param options [Hash]
|
41
|
-
def initialize(
|
42
|
-
@
|
43
|
-
|
44
|
-
|
39
|
+
def initialize(options = {})
|
40
|
+
@options = options || {}
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [Object, nil] user object
|
44
|
+
def user
|
45
|
+
options[:user]
|
45
46
|
end
|
46
47
|
|
47
48
|
# @note It can be overridden in subclasses for customization purpose.
|
48
49
|
# This is the template method to check user's permission for given action on given subject.
|
49
50
|
# @param _action [Symbol, String]
|
50
51
|
# @param _subject [Object, Class]
|
51
|
-
# @raise [
|
52
|
+
# @raise [NotImplemented]
|
52
53
|
def authorize(_action, _subject)
|
53
54
|
raise NotImplemented
|
54
55
|
end
|
@@ -57,7 +58,7 @@ module Wallaby
|
|
57
58
|
# This is the template method to check if user has permission for given action on given subject.
|
58
59
|
# @param _action [Symbol, String]
|
59
60
|
# @param _subject [Object, Class]
|
60
|
-
# @raise [
|
61
|
+
# @raise [NotImplemented]
|
61
62
|
def authorized?(_action, _subject)
|
62
63
|
raise NotImplemented
|
63
64
|
end
|
@@ -66,7 +67,7 @@ module Wallaby
|
|
66
67
|
# This is the template method to check if user has no permission for given action on given subject.
|
67
68
|
# @param action [Symbol, String]
|
68
69
|
# @param subject [Object, Class]
|
69
|
-
# @raise [
|
70
|
+
# @raise [NotImplemented]
|
70
71
|
def unauthorized?(action, subject)
|
71
72
|
!authorized?(action, subject)
|
72
73
|
end
|
@@ -75,7 +76,7 @@ module Wallaby
|
|
75
76
|
# This is the template method to restrict user's access to certain scope.
|
76
77
|
# @param _action [Symbol, String]
|
77
78
|
# @param _scope [Object]
|
78
|
-
# @raise [
|
79
|
+
# @raise [NotImplemented]
|
79
80
|
def accessible_for(_action, _scope)
|
80
81
|
raise NotImplemented
|
81
82
|
end
|
@@ -84,7 +85,7 @@ module Wallaby
|
|
84
85
|
# This is the template method to restrict user's modification to certain fields of given subject.
|
85
86
|
# @param _action [Symbol, String]
|
86
87
|
# @param _subject [Object]
|
87
|
-
# @raise [
|
88
|
+
# @raise [NotImplemented]
|
88
89
|
def attributes_for(_action, _subject)
|
89
90
|
raise NotImplemented
|
90
91
|
end
|
@@ -93,7 +94,7 @@ module Wallaby
|
|
93
94
|
# This is the template method to restrict user's mass assignment to certain fields of given subject.
|
94
95
|
# @param _action [Symbol, String]
|
95
96
|
# @param _subject [Object]
|
96
|
-
# @raise [
|
97
|
+
# @raise [NotImplemented]
|
97
98
|
def permit_params(_action, _subject)
|
98
99
|
raise NotImplemented
|
99
100
|
end
|
@@ -1,11 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Wallaby
|
4
|
-
# Model Decorator interface, designed to maintain metadata for all fields from the data source (database/api)
|
5
|
-
# @see
|
4
|
+
# Model Decorator interface, designed to hold and maintain metadata for all fields from the data source (database/api)
|
5
|
+
# @see {ResourceDecorator} for more information on how to customize metadata
|
6
6
|
class ModelDecorator
|
7
7
|
include Fieldable
|
8
8
|
|
9
|
+
MISSING_METHODS_RELATED_TO_FIELDS =
|
10
|
+
/\A(?<prefix>[a-zA-Z]\w*_)(?<method_partial>fields=?|field_names=?|metadata_of|label_of|type_of)\Z/.freeze
|
11
|
+
|
9
12
|
# Initialize with model class
|
10
13
|
# @param model_class [Class]
|
11
14
|
def initialize(model_class)
|
@@ -16,6 +19,12 @@ module Wallaby
|
|
16
19
|
# @return [Class]
|
17
20
|
attr_reader :model_class
|
18
21
|
|
22
|
+
# @see AllFields
|
23
|
+
# @!attribute [r] all_fields
|
24
|
+
def all_fields
|
25
|
+
AllFields.new self
|
26
|
+
end
|
27
|
+
|
19
28
|
# @!attribute [r] fields
|
20
29
|
# @note to be implemented in subclasses.
|
21
30
|
# Origin fields metadata.
|
@@ -39,21 +48,6 @@ module Wallaby
|
|
39
48
|
raise NotImplemented
|
40
49
|
end
|
41
50
|
|
42
|
-
# @!attribute [w] index_fields
|
43
|
-
def index_fields=(fields)
|
44
|
-
@index_fields = fields.with_indifferent_access
|
45
|
-
end
|
46
|
-
|
47
|
-
# @!attribute [w] index_field_names
|
48
|
-
attr_writer :index_field_names
|
49
|
-
|
50
|
-
# @!attribute [r] index_field_names
|
51
|
-
# A list of field names for `index` page
|
52
|
-
# @return [Array<String, Symbol>]
|
53
|
-
def index_field_names
|
54
|
-
@index_field_names ||= reposition index_fields.keys, primary_key
|
55
|
-
end
|
56
|
-
|
57
51
|
# @!attribute [r] show_fields
|
58
52
|
# @note to be implemented in subclasses.
|
59
53
|
# Fields metadata for `show` page.
|
@@ -62,21 +56,6 @@ module Wallaby
|
|
62
56
|
raise NotImplemented
|
63
57
|
end
|
64
58
|
|
65
|
-
# @!attribute [w] show_fields
|
66
|
-
def show_fields=(fields)
|
67
|
-
@show_fields = fields.with_indifferent_access
|
68
|
-
end
|
69
|
-
|
70
|
-
# @!attribute [w] show_field_names
|
71
|
-
attr_writer :show_field_names
|
72
|
-
|
73
|
-
# @!attribute [r] show_field_names
|
74
|
-
# A list of field names for `show` page
|
75
|
-
# @return [Array<String, Symbol>]
|
76
|
-
def show_field_names
|
77
|
-
@show_field_names ||= reposition show_fields.keys, primary_key
|
78
|
-
end
|
79
|
-
|
80
59
|
# @!attribute [r] form_fields
|
81
60
|
# @note to be implemented in subclasses.
|
82
61
|
# Fields metadata for form (`new`/`edit`) page.
|
@@ -85,19 +64,11 @@ module Wallaby
|
|
85
64
|
raise NotImplemented
|
86
65
|
end
|
87
66
|
|
88
|
-
# @!attribute [w] form_fields
|
89
|
-
def form_fields=(fields)
|
90
|
-
@form_fields = fields.with_indifferent_access
|
91
|
-
end
|
92
|
-
|
93
|
-
# @!attribute [w] form_field_names
|
94
|
-
attr_writer :form_field_names
|
95
|
-
|
96
67
|
# @!attribute [r] form_field_names
|
97
68
|
# A list of field names for form (`new`/`edit`) page
|
98
69
|
# @return [Array<String, Symbol>]
|
99
70
|
def form_field_names
|
100
|
-
@form_field_names ||=
|
71
|
+
@form_field_names ||= form_fields.keys - [primary_key.to_s]
|
101
72
|
end
|
102
73
|
|
103
74
|
# @!attribute [r] filters
|
@@ -141,11 +112,26 @@ module Wallaby
|
|
141
112
|
end
|
142
113
|
|
143
114
|
# @return [String]
|
144
|
-
# @see
|
115
|
+
# @see Map.resources_name_map
|
145
116
|
def resources_name
|
146
117
|
Map.resources_name_map model_class
|
147
118
|
end
|
148
119
|
|
120
|
+
# Delegate missing methods to {Fieldable} if possible
|
121
|
+
def method_missing(method_id, *args, &block)
|
122
|
+
method_name = method_id.to_s
|
123
|
+
return super unless method_name.match?(MISSING_METHODS_RELATED_TO_FIELDS)
|
124
|
+
|
125
|
+
matched = MISSING_METHODS_RELATED_TO_FIELDS.match(method_name)
|
126
|
+
try("prefix_#{matched[:method_partial]}", *args, matched[:prefix], &block)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Check if method looks like: `_fields`, `_field_names` that can be processed by {Fieldable}
|
130
|
+
def respond_to_missing?(method_id, _include_private)
|
131
|
+
method_name = method_id.to_s
|
132
|
+
method_name.match?(MISSING_METHODS_RELATED_TO_FIELDS) || super
|
133
|
+
end
|
134
|
+
|
149
135
|
protected
|
150
136
|
|
151
137
|
# Move primary key to the front for given field names.
|
@@ -153,7 +139,9 @@ module Wallaby
|
|
153
139
|
# @param primary_key [String, Symbol] primary key name
|
154
140
|
# @return [Array<String, Symbol>]
|
155
141
|
# a new list of field names that primary key goes first
|
156
|
-
def reposition(field_names, primary_key)
|
142
|
+
def reposition(field_names, primary_key = self.primary_key)
|
143
|
+
return field_names if primary_key.blank?
|
144
|
+
|
157
145
|
field_names.unshift(primary_key.to_s).uniq
|
158
146
|
end
|
159
147
|
|
@@ -161,17 +149,17 @@ module Wallaby
|
|
161
149
|
# @param type [String, Symbol, nil]
|
162
150
|
# @return [String, Symbol] type
|
163
151
|
# @raise [ArgumentError] when type is nil
|
164
|
-
def ensure_type_is_present(field_name, type,
|
152
|
+
def ensure_type_is_present(field_name, type, prefix = '')
|
165
153
|
type || raise(::ArgumentError, <<~INSTRUCTION
|
166
|
-
The type for field `#{field_name}` is missing in metadata `#{
|
154
|
+
The type for field `#{field_name}` is missing in metadata `#{prefix}_fields`.
|
167
155
|
The possible causes are:
|
168
156
|
|
169
|
-
1. Check type's value from metadata `#{
|
157
|
+
1. Check type's value from metadata `#{prefix}_fields[:#{field_name}][:type]`.
|
170
158
|
If it is missing, specify the type as below:
|
171
159
|
|
172
|
-
#{
|
160
|
+
#{prefix}fields[:#{field_name}][:type] = 'string'
|
173
161
|
|
174
|
-
2. If metadata `#{
|
162
|
+
2. If metadata `#{prefix}_fields` is blank, maybe table hasn't be created yet
|
175
163
|
or there is some error in the decorator class declaration.
|
176
164
|
INSTRUCTION
|
177
165
|
)
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Wallaby
|
4
|
-
#
|
4
|
+
# see {#all}
|
5
5
|
class ModelFinder
|
6
|
-
#
|
7
|
-
# @return [Array<Class>] a list of model
|
6
|
+
# @note Template method to return all the available models for a {Mode}
|
7
|
+
# @return [Array<Class>] a list of model classes
|
8
8
|
def all
|
9
9
|
raise NotImplemented
|
10
10
|
end
|
@@ -5,13 +5,9 @@ module Wallaby
|
|
5
5
|
class ModelPaginationProvider
|
6
6
|
# @param collection [#to_a]
|
7
7
|
# @param params [ActionController::Parameters]
|
8
|
-
|
9
|
-
# @param model_decorator [Wallaby::ModelDecorator, nil]
|
10
|
-
def initialize(collection, params, options: {}, model_decorator: nil)
|
8
|
+
def initialize(collection, params)
|
11
9
|
@collection = collection
|
12
10
|
@params = params
|
13
|
-
@options = options
|
14
|
-
@model_decorator = model_decorator
|
15
11
|
end
|
16
12
|
|
17
13
|
# If a collection has pagination feature
|
@@ -47,7 +43,7 @@ module Wallaby
|
|
47
43
|
# Find out the offset `from`
|
48
44
|
# @return [Integer]
|
49
45
|
def from
|
50
|
-
total.zero? ? total : (page_number - 1) * page_size + 1
|
46
|
+
total.zero? ? total : ((page_number - 1) * page_size) + 1
|
51
47
|
end
|
52
48
|
|
53
49
|
# Find out the offset `to`
|
@@ -4,7 +4,7 @@ module Wallaby
|
|
4
4
|
# Model service provider interface
|
5
5
|
class ModelServiceProvider
|
6
6
|
# @param model_class [Class]
|
7
|
-
# @param model_decorator [
|
7
|
+
# @param model_decorator [ModelDecorator, nil] model decorator
|
8
8
|
def initialize(model_class, model_decorator)
|
9
9
|
raise ::ArgumentError, 'model class required' unless model_class
|
10
10
|
|
@@ -12,11 +12,11 @@ module Wallaby
|
|
12
12
|
@model_decorator = model_decorator
|
13
13
|
end
|
14
14
|
|
15
|
-
# To
|
15
|
+
# To allowlist params for a model class
|
16
16
|
# @param _params [ActionController::Parameters]
|
17
17
|
# @param _action [String, Symbol]
|
18
|
-
# @param _authorizer [
|
19
|
-
# @return [ActionController::Parameters]
|
18
|
+
# @param _authorizer [ModelAuthorizer]
|
19
|
+
# @return [ActionController::Parameters] allowlisted params
|
20
20
|
def permit(_params, _action, _authorizer)
|
21
21
|
raise NotImplemented
|
22
22
|
end
|
@@ -18,7 +18,7 @@ module Wallaby
|
|
18
18
|
attr_reader :model_class
|
19
19
|
|
20
20
|
# @!attribute [r] provider
|
21
|
-
# @return [
|
21
|
+
# @return [ModelPaginationProvider] the instance that does the job
|
22
22
|
# @since wallaby-5.2.0
|
23
23
|
attr_reader :provider
|
24
24
|
|
@@ -35,14 +35,14 @@ module Wallaby
|
|
35
35
|
def single(resource)
|
36
36
|
{
|
37
37
|
id: resource.id,
|
38
|
-
type: params[:resources],
|
38
|
+
type: params[:resources] || Inflector.to_resources_name(resource.class),
|
39
39
|
attributes: attributes_of(resource)
|
40
40
|
}
|
41
41
|
end
|
42
42
|
|
43
43
|
def collection_data
|
44
44
|
{
|
45
|
-
data: resource.map(
|
45
|
+
data: resource.map { |r| single(r) },
|
46
46
|
links: {
|
47
47
|
self: controller.url_for(resources: params[:resources], action: 'index')
|
48
48
|
}
|
@@ -64,8 +64,8 @@ module Wallaby
|
|
64
64
|
errors: decorated.errors.each_with_object([]) do |(field, message), json|
|
65
65
|
json.push(
|
66
66
|
status: 422,
|
67
|
-
source: { pointer: "/data/attributes/#{field}" },
|
68
|
-
detail: message
|
67
|
+
source: { pointer: "/data/attributes/#{field.try(:attribute) || field}" },
|
68
|
+
detail: field.try(:message) || message
|
69
69
|
)
|
70
70
|
end
|
71
71
|
}
|
@@ -75,7 +75,12 @@ module Wallaby
|
|
75
75
|
decorated = controller.decorate resource
|
76
76
|
field_names = index? ? decorated.index_field_names : decorated.show_field_names
|
77
77
|
field_names.each_with_object({}) do |name, attributes|
|
78
|
-
attributes[name] = decorated.
|
78
|
+
attributes[name] = decorated.try(name).try do |value|
|
79
|
+
is_attached = defined?(::ActiveStorage::Attached) && value.is_a?(::ActiveStorage::Attached)
|
80
|
+
# NOTE: 19/04/20 `ActiveStorage::Attached#as_json` causes a dead loop.
|
81
|
+
# Therefore, it's better to render the filename instead of letting it call `#as_json`
|
82
|
+
is_attached ? value.attachment.try(:blob).try(:filename) : value
|
83
|
+
end
|
79
84
|
end
|
80
85
|
end
|
81
86
|
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'csv'
|
4
|
+
|
3
5
|
module Wallaby
|
4
6
|
# Resources responder
|
5
7
|
class ResourcesResponder < ActionController::Responder
|
@@ -16,12 +18,15 @@ module Wallaby
|
|
16
18
|
# @return [String] file name with export timestamp
|
17
19
|
def file_name_to_export
|
18
20
|
timestamp = Time.zone.now.to_s(:number)
|
19
|
-
|
21
|
+
filename =
|
22
|
+
(request.params[:resources] || controller.controller_path)
|
23
|
+
.gsub(/#{SLASH}|#{COLONS}/o, HYPHEN)
|
24
|
+
"#{filename}-exported-#{timestamp}.#{format}"
|
20
25
|
end
|
21
26
|
|
22
27
|
# @return [Boolean] true if there is exception or resource has errors
|
23
28
|
# @return [Boolean] false otherwise
|
24
|
-
def has_errors?
|
29
|
+
def has_errors?
|
25
30
|
resource.nil? || resource.is_a?(Exception) || controller.decorate(resource).errors.present?
|
26
31
|
end
|
27
32
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wallaby
|
4
|
+
module Engines
|
5
|
+
class BaseRoute
|
6
|
+
include ActiveModel::Model
|
7
|
+
|
8
|
+
# @!attribute context
|
9
|
+
# @return [ActionController::Base, ActionView::Base]
|
10
|
+
attr_accessor :context
|
11
|
+
# @!attribute params
|
12
|
+
# @return [Hash, ActionController::Parameters]
|
13
|
+
attr_accessor :params
|
14
|
+
# @!attribute params
|
15
|
+
# @return [Hash, ActionController::Parameters]
|
16
|
+
attr_accessor :options
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
# @param route [ActionDispatch::Journey::Route]
|
21
|
+
def params_for(route)
|
22
|
+
# ensure all required keys (e.g. `id`) are included
|
23
|
+
required_params_from_recall = recall.slice(*route.required_keys)
|
24
|
+
required_resources_name =
|
25
|
+
route.required_keys.include?(:resources) ? { resources: resources_name } : {}
|
26
|
+
|
27
|
+
route
|
28
|
+
.requirements
|
29
|
+
.merge(required_params_from_recall)
|
30
|
+
.merge(required_resources_name)
|
31
|
+
.merge(complete_params)
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [String] given action param or current request's action
|
35
|
+
def action_name
|
36
|
+
@action_name ||= (params[:action] || recall[:action]).try(:to_s)
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Class] model class option or converted model class from recall resources name
|
40
|
+
def model_class
|
41
|
+
@model_class ||= options[:model_class] || Map.model_class_map(resources_name)
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [String] resources name for given model
|
45
|
+
def resources_name
|
46
|
+
@resources_name ||=
|
47
|
+
params[:resources] ||
|
48
|
+
(options[:model_class] && Inflector.to_resources_name(options[:model_class])) ||
|
49
|
+
recall[:resources]
|
50
|
+
end
|
51
|
+
|
52
|
+
# Recall is the path params of current request
|
53
|
+
# @see https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/metal/url_for.rb#L35
|
54
|
+
# @return [Hash]
|
55
|
+
def recall
|
56
|
+
@recall ||= context.url_options[:_recall] || {}
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [Hash] query params from the request path
|
60
|
+
def with_query_params
|
61
|
+
options[:with_query] ? context.request.query_parameters : {}
|
62
|
+
end
|
63
|
+
|
64
|
+
# Symbolize param keys and normalize action
|
65
|
+
# @return [Hash]
|
66
|
+
def normalize_params(*args)
|
67
|
+
ParamsUtils
|
68
|
+
.presence(*args)
|
69
|
+
.deep_symbolize_keys
|
70
|
+
.tap do |normalized_params|
|
71
|
+
# convert :action (e.g. 'index') to string for requirements comparison
|
72
|
+
# :action value in route requirements is string (e.g. action: 'index')
|
73
|
+
normalized_params[:action] = action_name
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wallaby
|
4
|
+
module Engines
|
5
|
+
# The general route of {Engine} looks like as follow:
|
6
|
+
#
|
7
|
+
# /admin/order::items
|
8
|
+
#
|
9
|
+
# Therefore, to override this route, dev needs to define a resources as below
|
10
|
+
# before mounting {Engine}:
|
11
|
+
#
|
12
|
+
# namespace :admin do
|
13
|
+
# # NOTE: in order for the route to work properly,
|
14
|
+
# # the colon before words need to be escaped in the path option
|
15
|
+
# resources :items, path: 'order:\:item', module: :order
|
16
|
+
# end
|
17
|
+
# wallaby_mount at: '/admin'
|
18
|
+
#
|
19
|
+
# So to find out if any route has been overriden with current request, e.g. `/admin/order::items/1/edit`,
|
20
|
+
# we will look into the following conditions:
|
21
|
+
#
|
22
|
+
# - begins with `/admin`
|
23
|
+
# - same **action** as the given **action**
|
24
|
+
# - default **controller** exists (as {ResourcesRouter} does not define static **controller**)
|
25
|
+
#
|
26
|
+
# Then we use this route's params and pass it to the origin `url_for`.
|
27
|
+
class CustomAppRoute < BaseRoute
|
28
|
+
# @return [true] if the route matches the condition exists
|
29
|
+
# @return [false] otherwise
|
30
|
+
def exist?
|
31
|
+
route.present?
|
32
|
+
end
|
33
|
+
|
34
|
+
def url
|
35
|
+
context.url_for(params_for(route), super: true)
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
delegate :script_name, to: :context
|
41
|
+
|
42
|
+
# @return [ActionDispatch::Journey::Route]
|
43
|
+
# the application route that overrides the route handled by {Engine}
|
44
|
+
def route
|
45
|
+
@route ||=
|
46
|
+
Rails.application.routes.routes.find do |route|
|
47
|
+
prefix_matched?(route) && requirements_matched?(route)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [true] if route's path begins with the prefix as where {Engine} route to,
|
52
|
+
# e.g. `/admin/order::items`
|
53
|
+
# @return [false] otherwise
|
54
|
+
def prefix_matched?(route)
|
55
|
+
path = route.path.spec.to_s
|
56
|
+
path == script_name || path.start_with?(script_name + SLASH)
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [true] if the params matches route's requirements
|
60
|
+
# @return [false] otherwise
|
61
|
+
def requirements_matched?(route)
|
62
|
+
return false if route.requirements.blank?
|
63
|
+
|
64
|
+
route_params =
|
65
|
+
params_for(route).tap do |params|
|
66
|
+
params[:controller] = same_controller? ? context.controller_path : possible_controller_path
|
67
|
+
end
|
68
|
+
|
69
|
+
route.requirements <= route_params
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return [true] if the given params is under the same controller as current request
|
73
|
+
# @return [false] otherwise
|
74
|
+
def same_controller?
|
75
|
+
context.current_model_class == model_class || context.current_resources_name == resources_name
|
76
|
+
end
|
77
|
+
|
78
|
+
# @return [String] possible controller path
|
79
|
+
def possible_controller_path
|
80
|
+
"#{script_name}/#{resources_name}"
|
81
|
+
end
|
82
|
+
|
83
|
+
# @return [Hash]
|
84
|
+
def complete_params
|
85
|
+
@complete_params ||=
|
86
|
+
normalize_params(with_query_params, params).tap do |normalized_params|
|
87
|
+
normalized_params.delete(:resources) if same_controller?
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|