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.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/wallaby/resources_controller.rb +20 -7
  3. data/app/security/ability.rb +1 -1
  4. data/config/routes.rb +21 -14
  5. data/lib/adaptors/wallaby/custom/default_provider.rb +1 -1
  6. data/lib/adaptors/wallaby/custom/model_decorator.rb +5 -17
  7. data/lib/adaptors/wallaby/custom/model_finder.rb +3 -3
  8. data/lib/adaptors/wallaby/custom/model_pagination_provider.rb +1 -1
  9. data/lib/adaptors/wallaby/custom/model_service_provider.rb +1 -1
  10. data/lib/authorizers/wallaby/cancancan_authorization_provider.rb +12 -5
  11. data/lib/authorizers/wallaby/default_authorization_provider.rb +10 -1
  12. data/lib/authorizers/wallaby/model_authorizer.rb +41 -16
  13. data/lib/authorizers/wallaby/pundit_authorization_provider.rb +22 -8
  14. data/lib/concerns/wallaby/application_concern.rb +41 -71
  15. data/lib/concerns/wallaby/authentication_concern.rb +29 -127
  16. data/lib/concerns/wallaby/authorizable.rb +14 -57
  17. data/lib/concerns/wallaby/baseable.rb +24 -57
  18. data/lib/concerns/wallaby/configurable.rb +416 -0
  19. data/lib/concerns/wallaby/decoratable.rb +24 -60
  20. data/lib/concerns/wallaby/engineable.rb +29 -46
  21. data/lib/concerns/wallaby/fieldable.rb +45 -56
  22. data/lib/concerns/wallaby/paginatable.rb +20 -51
  23. data/lib/concerns/wallaby/prefixable.rb +24 -4
  24. data/lib/concerns/wallaby/resourcable.rb +130 -72
  25. data/lib/concerns/wallaby/resources_concern.rb +205 -305
  26. data/lib/concerns/wallaby/servicable.rb +8 -48
  27. data/lib/concerns/wallaby/urlable.rb +69 -0
  28. data/lib/decorators/wallaby/resource_decorator.rb +72 -34
  29. data/lib/errors/wallaby/class_not_found.rb +1 -2
  30. data/lib/errors/wallaby/forbidden.rb +1 -2
  31. data/lib/errors/wallaby/general_error.rb +1 -1
  32. data/lib/errors/wallaby/invalid_error.rb +1 -2
  33. data/lib/errors/wallaby/method_removed.rb +5 -0
  34. data/lib/errors/wallaby/model_not_found.rb +1 -2
  35. data/lib/errors/wallaby/not_authenticated.rb +1 -2
  36. data/lib/errors/wallaby/not_found.rb +1 -2
  37. data/lib/errors/wallaby/not_implemented.rb +1 -2
  38. data/lib/errors/wallaby/resource_not_found.rb +1 -2
  39. data/lib/errors/wallaby/unprocessable_entity.rb +1 -2
  40. data/lib/fields/wallaby/all_fields.rb +63 -0
  41. data/lib/forms/wallaby/form_builder.rb +2 -2
  42. data/lib/generators/wallaby/engine/application_generator.rb +33 -0
  43. data/lib/generators/wallaby/engine/authorizer/USAGE +20 -0
  44. data/lib/generators/wallaby/engine/authorizer/authorizer_generator.rb +19 -0
  45. data/lib/generators/wallaby/engine/authorizer/templates/authorizer.rb.erb +35 -0
  46. data/lib/generators/wallaby/engine/controller/USAGE +20 -0
  47. data/lib/generators/wallaby/engine/controller/controller_generator.rb +23 -0
  48. data/lib/generators/wallaby/engine/controller/templates/controller.rb.erb +130 -0
  49. data/lib/generators/wallaby/engine/decorator/USAGE +20 -0
  50. data/lib/generators/wallaby/engine/decorator/decorator_generator.rb +19 -0
  51. data/lib/generators/wallaby/engine/decorator/templates/decorator.rb.erb +5 -0
  52. data/lib/generators/wallaby/engine/install/USAGE +19 -0
  53. data/lib/generators/wallaby/engine/install/install_generator.rb +91 -0
  54. data/lib/generators/wallaby/engine/install/templates/application_authorizer.rb.erb +37 -0
  55. data/lib/generators/wallaby/engine/install/templates/application_controller.rb.erb +173 -0
  56. data/lib/generators/wallaby/engine/install/templates/application_decorator.rb.erb +7 -0
  57. data/lib/generators/wallaby/engine/install/templates/application_paginator.rb.erb +27 -0
  58. data/lib/generators/wallaby/engine/install/templates/application_servicer.rb.erb +47 -0
  59. data/lib/generators/wallaby/engine/install/templates/initializer.rb.erb +16 -0
  60. data/lib/generators/wallaby/engine/paginator/USAGE +20 -0
  61. data/lib/generators/wallaby/engine/paginator/paginator_generator.rb +19 -0
  62. data/lib/generators/wallaby/engine/paginator/templates/paginator.rb.erb +25 -0
  63. data/lib/generators/wallaby/engine/servicer/USAGE +20 -0
  64. data/lib/generators/wallaby/engine/servicer/servicer_generator.rb +19 -0
  65. data/lib/generators/wallaby/engine/servicer/templates/servicer.rb.erb +45 -0
  66. data/lib/helpers/wallaby/application_helper.rb +10 -59
  67. data/lib/helpers/wallaby/base_helper.rb +11 -11
  68. data/lib/helpers/wallaby/configuration_helper.rb +36 -4
  69. data/lib/helpers/wallaby/form_helper.rb +1 -1
  70. data/lib/helpers/wallaby/index_helper.rb +19 -9
  71. data/lib/helpers/wallaby/links_helper.rb +13 -80
  72. data/lib/helpers/wallaby/resources_helper.rb +39 -7
  73. data/lib/helpers/wallaby/secure_helper.rb +20 -19
  74. data/lib/interfaces/wallaby/mode.rb +8 -8
  75. data/lib/interfaces/wallaby/model_authorization_provider.rb +23 -22
  76. data/lib/interfaces/wallaby/model_decorator.rb +36 -48
  77. data/lib/interfaces/wallaby/model_finder.rb +3 -3
  78. data/lib/interfaces/wallaby/model_pagination_provider.rb +2 -6
  79. data/lib/interfaces/wallaby/model_service_provider.rb +4 -4
  80. data/lib/paginators/wallaby/model_paginator.rb +1 -1
  81. data/lib/responders/wallaby/json_api_responder.rb +10 -5
  82. data/lib/responders/wallaby/resources_responder.rb +7 -2
  83. data/lib/routes/wallaby/engines/base_route.rb +78 -0
  84. data/lib/routes/wallaby/engines/custom_app_route.rb +92 -0
  85. data/lib/routes/wallaby/engines/engine_route.rb +77 -0
  86. data/lib/routes/wallaby/resources_router.rb +100 -45
  87. data/lib/servicers/wallaby/model_servicer.rb +13 -13
  88. data/lib/services/wallaby/authorizer_finder.rb +23 -0
  89. data/lib/services/wallaby/class_finder.rb +42 -0
  90. data/lib/services/wallaby/controller_finder.rb +29 -0
  91. data/lib/services/wallaby/decorator_finder.rb +34 -0
  92. data/lib/services/wallaby/default_models_excluder.rb +45 -0
  93. data/lib/services/wallaby/engine_name_finder.rb +14 -11
  94. data/lib/services/wallaby/engine_url_for.rb +82 -37
  95. data/lib/services/wallaby/fields_regulator.rb +34 -0
  96. data/lib/services/wallaby/map/mode_mapper.rb +4 -4
  97. data/lib/services/wallaby/map/model_class_mapper.rb +1 -1
  98. data/lib/services/wallaby/model_class_filter.rb +29 -0
  99. data/lib/services/wallaby/paginator_finder.rb +24 -0
  100. data/lib/services/wallaby/prefixes_builder.rb +49 -8
  101. data/lib/services/wallaby/servicer_finder.rb +31 -0
  102. data/lib/services/wallaby/sorting/hash_builder.rb +9 -0
  103. data/lib/services/wallaby/sorting/link_builder.rb +7 -10
  104. data/lib/services/wallaby/sorting/next_builder.rb +1 -12
  105. data/lib/services/wallaby/sorting/single_builder.rb +1 -1
  106. data/lib/support/action_dispatch/routing/mapper.rb +29 -4
  107. data/lib/utils/wallaby/field_utils.rb +9 -8
  108. data/lib/utils/wallaby/inflector.rb +94 -0
  109. data/lib/utils/wallaby/locale.rb +2 -2
  110. data/lib/utils/wallaby/module_utils.rb +3 -10
  111. data/lib/utils/wallaby/utils.rb +21 -14
  112. data/lib/wallaby/class_array.rb +18 -13
  113. data/lib/wallaby/class_hash.rb +16 -14
  114. data/lib/wallaby/classifier.rb +4 -2
  115. data/lib/wallaby/configuration/features.rb +8 -2
  116. data/lib/wallaby/configuration/mapping.rb +66 -112
  117. data/lib/wallaby/configuration/metadata.rb +15 -12
  118. data/lib/wallaby/configuration/models.rb +27 -25
  119. data/lib/wallaby/configuration/pagination.rb +15 -19
  120. data/lib/wallaby/configuration/security.rb +88 -80
  121. data/lib/wallaby/configuration/sorting.rb +15 -17
  122. data/lib/wallaby/configuration.rb +58 -23
  123. data/lib/wallaby/constants.rb +21 -13
  124. data/lib/wallaby/core/version.rb +1 -1
  125. data/lib/wallaby/core.rb +34 -10
  126. data/lib/wallaby/deprecator.rb +81 -0
  127. data/lib/wallaby/engine.rb +1 -19
  128. data/lib/wallaby/guesser.rb +45 -0
  129. data/lib/wallaby/logger.rb +35 -13
  130. data/lib/wallaby/map.rb +14 -87
  131. data/lib/wallaby/preloader.rb +13 -25
  132. metadata +120 -15
  133. data/config/locales/wallaby_class.en.yml +0 -9
  134. data/lib/concerns/wallaby/defaultable.rb +0 -38
  135. data/lib/concerns/wallaby/shared_helpers.rb +0 -22
  136. data/lib/services/wallaby/map/model_class_collector.rb +0 -49
  137. data/lib/services/wallaby/type_renderer.rb +0 -40
  138. data/lib/utils/wallaby/model_utils.rb +0 -52
  139. 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 Wallaby::ModelDecorator
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 Wallaby::ModelFinder
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 Wallaby::ModelServiceProvider
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 Wallaby::ModelPaginationProvider
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 Wallaby::ModelPaginationProvider
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 Wallaby::ModelAuthorizationProvider
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
- private
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, Locale.t('mode.inherit_required', klass: klass, parent: parent_class)
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 that can be set in Wallaby::ModelAuthorizer subclasses.
13
- # @see Wallaby::ModelAuthorizer.provider_name
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 [Wallaby::NotImplemented]
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
- # @!attribute [r] user
32
- # @return [Object]
33
- attr_reader :user
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(context, **options)
42
- @context = context
43
- @options = options
44
- @user = context.try :wallaby_user
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 [Wallaby::NotImplemented]
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 [Wallaby::NotImplemented]
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 [Wallaby::NotImplemented]
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 [Wallaby::NotImplemented]
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 [Wallaby::NotImplemented]
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 [Wallaby::NotImplemented]
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 Wallaby::ResourceDecorator for more information on how to customize metadata
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 ||= reposition form_fields.keys, primary_key
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 Wallaby::Map.resources_name_map
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, metadata_prefix = '')
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 `#{metadata_prefix}_fields`.
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 `#{metadata_prefix}_fields[:#{field_name}][:type]`.
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
- #{metadata_prefix}field_name[:#{field_name}][:type] = 'string'
160
+ #{prefix}fields[:#{field_name}][:type] = 'string'
173
161
 
174
- 2. If metadata `#{metadata_prefix}_fields` is blank, maybe table hasn't be created yet
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
- # Model finder interface
4
+ # see {#all}
5
5
  class ModelFinder
6
- # Need to implement this method to get all the available model for a mode
7
- # @return [Array<Class>] a list of model class
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
- # @param options [Hash] options
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 [Wallaby::ModelDecorator, nil] 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 whitelist params for a model class
15
+ # To allowlist params for a model class
16
16
  # @param _params [ActionController::Parameters]
17
17
  # @param _action [String, Symbol]
18
- # @param _authorizer [Wallaby::ModelAuthorizer]
19
- # @return [ActionController::Parameters] whitelisted params
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 [Wallaby::ModelPaginationProvider] the instance that does the job
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(&method(:single)),
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.public_send name
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
- "#{request.params[:resources]}-exported-#{timestamp}.#{format}"
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? # rubocop:disable Naming/PredicateName
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