wallaby-core 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/app/controllers/wallaby/resources_controller.rb +10 -1
  4. data/config/locales/wallaby_class.en.yml +2 -2
  5. data/lib/adaptors/wallaby/custom/default_provider.rb +1 -1
  6. data/lib/adaptors/wallaby/custom/model_decorator.rb +8 -7
  7. data/lib/adaptors/wallaby/custom/model_finder.rb +3 -2
  8. data/lib/adaptors/wallaby/custom/model_pagination_provider.rb +1 -1
  9. data/lib/adaptors/wallaby/custom/model_service_provider.rb +1 -40
  10. data/lib/authorizers/wallaby/cancancan_authorization_provider.rb +29 -24
  11. data/lib/authorizers/wallaby/default_authorization_provider.rb +6 -13
  12. data/lib/authorizers/wallaby/model_authorizer.rb +43 -67
  13. data/lib/authorizers/wallaby/pundit_authorization_provider.rb +21 -30
  14. data/lib/concerns/wallaby/application_concern.rb +1 -2
  15. data/lib/concerns/wallaby/authentication_concern.rb +74 -5
  16. data/lib/concerns/wallaby/authorizable.rb +8 -8
  17. data/lib/concerns/wallaby/baseable.rb +91 -10
  18. data/lib/concerns/wallaby/decoratable.rb +3 -3
  19. data/lib/concerns/wallaby/engineable.rb +1 -1
  20. data/lib/concerns/wallaby/fieldable.rb +4 -4
  21. data/lib/concerns/wallaby/paginatable.rb +3 -3
  22. data/lib/concerns/wallaby/resourcable.rb +0 -35
  23. data/lib/concerns/wallaby/resources_concern.rb +3 -2
  24. data/lib/concerns/wallaby/servicable.rb +4 -4
  25. data/lib/decorators/wallaby/resource_decorator.rb +53 -80
  26. data/lib/errors/wallaby/class_not_found.rb +6 -0
  27. data/lib/errors/wallaby/model_not_found.rb +2 -0
  28. data/lib/helpers/wallaby/resources_helper.rb +3 -0
  29. data/lib/helpers/wallaby/secure_helper.rb +3 -3
  30. data/lib/interfaces/wallaby/mode.rb +3 -3
  31. data/lib/interfaces/wallaby/model_authorization_provider.rb +15 -13
  32. data/lib/interfaces/wallaby/model_decorator.rb +15 -3
  33. data/lib/paginators/wallaby/model_paginator.rb +14 -45
  34. data/lib/servicers/wallaby/model_servicer.rb +31 -62
  35. data/lib/services/wallaby/map/mode_mapper.rb +14 -14
  36. data/lib/services/wallaby/map/model_class_collector.rb +1 -1
  37. data/lib/services/wallaby/map/model_class_mapper.rb +7 -26
  38. data/lib/services/wallaby/type_renderer.rb +0 -10
  39. data/lib/utils/wallaby/model_utils.rb +4 -3
  40. data/lib/utils/wallaby/utils.rb +9 -8
  41. data/lib/wallaby/class_array.rb +75 -0
  42. data/lib/wallaby/class_hash.rb +94 -0
  43. data/lib/wallaby/classifier.rb +29 -0
  44. data/lib/wallaby/configuration.rb +31 -2
  45. data/lib/wallaby/configuration/mapping.rb +33 -21
  46. data/lib/wallaby/configuration/metadata.rb +1 -1
  47. data/lib/wallaby/configuration/models.rb +5 -9
  48. data/lib/wallaby/configuration/security.rb +6 -3
  49. data/lib/wallaby/configuration/sorting.rb +1 -1
  50. data/lib/wallaby/core.rb +13 -7
  51. data/lib/wallaby/core/version.rb +1 -1
  52. data/lib/wallaby/engine.rb +9 -20
  53. data/lib/wallaby/logger.rb +35 -0
  54. data/lib/wallaby/map.rb +20 -17
  55. data/lib/wallaby/preloader.rb +77 -0
  56. metadata +8 -4
  57. data/lib/utils/wallaby/logger.rb +0 -21
  58. data/lib/utils/wallaby/preload_utils.rb +0 -44
@@ -1,83 +1,74 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Wallaby
4
- # Pundit base authorization provider
4
+ # @note This authorization provider DOES NOT use the
5
+ # {https://github.com/varvet/pundit#customize-pundit-user pundit_user} helper.
6
+ # It uses the one from {Wallaby::AuthenticationConcern#wallaby_user #wallaby_user} instead.
7
+ # {https://github.com/varvet/pundit Pundit} base authorization provider.
5
8
  class PunditAuthorizationProvider < ModelAuthorizationProvider
6
9
  # Detect and see if Pundit is in use.
7
- # @param context [ActionController::Base]
8
- # @return [true] if Pundit is in use.
9
- # @return [false] if Pundit is not in use.
10
+ # @param context [ActionController::Base, ActionView::Base]
11
+ # @return [true] if Pundit is in use
12
+ # @return [false] otherwise
10
13
  def self.available?(context)
11
14
  defined?(Pundit) && context.respond_to?(:pundit_user)
12
15
  end
13
16
 
14
- # This will pull out the args required for contruction from context
15
- # @param context [ActionController::Base]
16
- # @return [Hash] args for initialize
17
- def self.args_from(context)
18
- { user: context.pundit_user }
19
- end
20
-
21
- # @param user [Object]
22
- def initialize(user:)
23
- @user = user
24
- end
25
-
26
17
  # Check user's permission for an action on given subject.
27
18
  #
28
- # This method will be used in controller.
19
+ # This method will be mostly used in controller.
29
20
  # @param action [Symbol, String]
30
21
  # @param subject [Object, Class]
31
22
  # @raise [Wallaby::Forbidden] when user is not authorized to perform the action.
32
23
  def authorize(action, subject)
33
24
  Pundit.authorize(user, subject, normalize(action)) && subject
34
25
  rescue ::Pundit::NotAuthorizedError
35
- Logger.info Locale.t('errors.unauthorized', user: user, action: action, subject: subject)
26
+ Logger.error <<~MESSAGE
27
+ #{Utils.inspect user} is forbidden to perform #{action} on #{Utils.inspect subject}
28
+ MESSAGE
36
29
  raise Forbidden
37
30
  end
38
31
 
39
32
  # Check and see if user is allowed to perform an action on given subject
40
33
  # @param action [Symbol, String]
41
34
  # @param subject [Object, Class]
42
- # @return [Boolean]
35
+ # @return [true] if user is allowed to perform the action
36
+ # @return [false] otherwise
43
37
  def authorized?(action, subject)
44
38
  policy = Pundit.policy! user, subject
45
- ModuleUtils.try_to policy, normalize(action)
39
+ policy.try normalize(action)
46
40
  end
47
41
 
48
42
  # Restrict user to assign certain values.
49
43
  #
50
44
  # It will do a lookup in policy's methods and pick the first available method:
51
45
  #
52
- # - attributes\_for\_#\{ action \}
53
- # - attributes\_for
46
+ # - `attributes_for_#{action}`
47
+ # - `attributes_for`
54
48
  # @param action [Symbol, String]
55
49
  # @param subject [Object]
56
50
  # @return [Hash] field value paired hash that user's allowed to assign
57
51
  def attributes_for(action, subject)
58
52
  policy = Pundit.policy! user, subject
59
- value = ModuleUtils.try_to(policy, "attributes_for_#{action}") || ModuleUtils.try_to(policy, 'attributes_for')
60
- Logger.warn Locale.t('error.pundit.not_found.attributes_for', subject: subject) unless value
61
- value || {}
53
+ policy.try("attributes_for_#{action}") || policy.try('attributes_for') || {}
62
54
  end
63
55
 
64
56
  # Restrict user for mass assignment.
65
57
  #
66
58
  # It will do a lookup in policy's methods and pick the first available method:
67
59
  #
68
- # - permitted\_attributes\_for\_#\{ action \}
69
- # - permitted\_attributes
60
+ # - `permitted_attributes_for_#{ action }`
61
+ # - `permitted_attributes`
70
62
  # @param action [Symbol, String]
71
63
  # @param subject [Object]
72
64
  # @return [Array] field list that user's allowed to change.
73
65
  def permit_params(action, subject)
74
66
  policy = Pundit.policy! user, subject
75
67
  # @see https://github.com/varvet/pundit/blob/master/lib/pundit.rb#L258
76
- ModuleUtils.try_to(policy, "permitted_attributes_for_#{action}") \
77
- || ModuleUtils.try_to(policy, 'permitted_attributes')
68
+ policy.try("permitted_attributes_for_#{action}") || policy.try('permitted_attributes')
78
69
  end
79
70
 
80
- private
71
+ protected
81
72
 
82
73
  # Convert action to pundit method name
83
74
  # @param action [Symbol, String]
@@ -50,7 +50,6 @@ module Wallaby
50
50
  extend Engineable::ClassMethods
51
51
  include Engineable
52
52
  include SharedHelpers
53
- helper ApplicationHelper
54
53
 
55
54
  rescue_from NotFound, with: :not_found
56
55
  rescue_from ::ActionController::ParameterMissing, with: :bad_request
@@ -99,7 +98,7 @@ module Wallaby
99
98
 
100
99
  # (see #render_error)
101
100
  def render_error(exception, symbol)
102
- Logger.error exception, sourcing: false
101
+ Logger.error exception
103
102
 
104
103
  @exception = exception
105
104
  @symbol = symbol
@@ -5,6 +5,7 @@ module Wallaby
5
5
  module AuthenticationConcern
6
6
  extend ActiveSupport::Concern
7
7
 
8
+ # @deprecated Use {#wallaby_user} instead
8
9
  # @!method current_user
9
10
  # @note This is a template method that can be overridden by subclasses
10
11
  # This {current_user} method will try to looking up the actual implementation from the following
@@ -22,6 +23,7 @@ module Wallaby
22
23
  # end
23
24
  # @return [Object] a user object
24
25
 
26
+ # @deprecated Use {#authenticate_wallaby_user!} instead
25
27
  # @!method authenticate_user!
26
28
  # @note This is a template method that can be overridden by subclasses
27
29
  # This {authenticate_user!} method will try to looking up the actual implementation from the following
@@ -41,40 +43,107 @@ module Wallaby
41
43
  # @return [true] when user is authenticated successfully
42
44
  # @raise [Wallaby::NotAuthenticated] when user fails to authenticate
43
45
 
46
+ # @!method wallaby_user
47
+ # @note This is a template method that can be overridden by subclasses
48
+ # This method will try to call {#current_user} from superclass.
49
+ # @example It can be overridden in subclasses:
50
+ # def wallaby_user
51
+ # # NOTE: better to assign user to `@wallaby_user` for better performance:
52
+ # @wallaby_user ||= User.new params.slice(:email)
53
+ # end
54
+ # @return [Object] a user object
55
+
56
+ # @!method pundit_user
57
+ # @note This is a template method that can be overridden by subclasses
58
+ # This overridden method of {#original_pundit_user} will try to call {#wallaby_user} instead of {#current_user}.
59
+ # @example It can be overridden in subclasses:
60
+ # def pundit_user
61
+ # @pundit_user ||= User.new params.slice(:email)
62
+ # end
63
+ # @return [Object] a user object
64
+
65
+ # @!parse alias :override_pundit_user :pundit_user
66
+
67
+ # @!method original_pundit_user
68
+ # This method is the original version of {#pundit_user} which calls the {#current_user}.
69
+ # @return [Object] a user object
70
+
71
+ # @!method authenticate_wallaby_user!
72
+ # @note This is a template method that can be overridden by subclasses
73
+ # This method will try to call {#authenticate_user!} from superclass.
74
+ # And it will be run as the first callback before an action.
75
+ # @example It can be overridden in subclasses:
76
+ # def authenticate_wallaby_user!
77
+ # authenticate_or_request_with_http_basic do |username, password|
78
+ # username == 'too_simple' && password == 'too_naive'
79
+ # end
80
+ # end
81
+ # @return [true] when user is authenticated successfully
82
+ # @raise [Wallaby::NotAuthenticated] when user fails to authenticate
83
+
44
84
  # @!method unauthorized(exception = nil)
45
85
  # Unauthorized page.
46
- # @param exception [Exception] exception comes from `rescue_from`
86
+ # @param exception [Exception] comes from **rescue_from**
47
87
 
48
88
  # @!method forbidden(exception = nil)
49
89
  # Forbidden page.
50
- # @param exception [Exception] exception comes from `rescue_from`
90
+ # @param exception [Exception] comes from **rescue_from**
51
91
 
52
92
  included do # rubocop:disable Metrics/BlockLength
53
- helper SecureHelper
54
- helper_method :current_user
93
+ helper_method :wallaby_user
55
94
 
56
95
  rescue_from NotAuthenticated, with: :unauthorized
57
96
  rescue_from Forbidden, with: :forbidden
58
97
 
59
98
  # (see #current_user)
99
+ # TODO: remove this from 6.2
60
100
  def current_user
61
101
  @current_user ||=
62
102
  if security.current_user? || !defined? super
63
103
  instance_exec(&security.current_user)
64
104
  else
105
+ Logger.deprecated 'Wallaby will use `wallaby_user` instead of `current_user` from 6.2.'
65
106
  super
66
107
  end
67
108
  end
68
109
 
69
110
  # (see #authenticate_user!)
111
+ # TODO: remove this from 6.2
70
112
  def authenticate_user!
71
113
  authenticated =
72
114
  if security.authenticate? || !defined? super
73
115
  instance_exec(&security.authenticate)
74
116
  else
117
+ Logger.deprecated 'Wallaby will use `authenticate_wallaby_user!`' \
118
+ 'instead of `authenticate_user!` from 6.2.'
75
119
  super
76
120
  end
77
- raise NotAuthenticated unless authenticated
121
+ raise NotAuthenticated if authenticated == false
122
+
123
+ true
124
+ end
125
+
126
+ # (see #wallaby_user)
127
+ def wallaby_user
128
+ @wallaby_user ||= try :current_user
129
+ end
130
+
131
+ if defined?(::Pundit) && instance_methods.include?(:pundit_user)
132
+ # (see #override_pundit_user)
133
+ def override_pundit_user
134
+ wallaby_user
135
+ end
136
+
137
+ # (see #original_pundit_user)
138
+ alias_method :original_pundit_user, :pundit_user
139
+ # (see #pundit_user)
140
+ alias_method :pundit_user, :override_pundit_user
141
+ end
142
+
143
+ # (see #authenticate_wallaby_user!)
144
+ def authenticate_wallaby_user!
145
+ authenticated = try :authenticate_user!
146
+ raise NotAuthenticated if authenticated == false
78
147
 
79
148
  true
80
149
  end
@@ -20,7 +20,7 @@ module Wallaby
20
20
  # @return [Class] model authorizer
21
21
  # @raise [ArgumentError] when **model_authorizer** doesn't inherit from **application_authorizer**
22
22
  # @see Wallaby::ModelAuthorizer
23
- # @since 5.2.0
23
+ # @since wallaby-5.2.0
24
24
  attr_reader :model_authorizer
25
25
 
26
26
  # @!attribute [w] application_authorizer
@@ -38,7 +38,7 @@ module Wallaby
38
38
  # @return [Class] application decorator
39
39
  # @raise [ArgumentError] when **model_authorizer** doesn't inherit from **application_authorizer**
40
40
  # @see Wallaby::ModelAuthorizer
41
- # @since 5.2.0
41
+ # @since wallaby-5.2.0
42
42
  def application_authorizer
43
43
  @application_authorizer ||= ModuleUtils.try_to superclass, :application_authorizer
44
44
  end
@@ -52,11 +52,11 @@ module Wallaby
52
52
  # - a generic authorizer based on
53
53
  # {Wallaby::Authorizable::ClassMethods#application_authorizer .application_authorizer}
54
54
  # @return [Wallaby::ModelAuthorizer] model authorizer
55
- # @since 5.2.0
55
+ # @since wallaby-5.2.0
56
56
  def current_authorizer
57
57
  @current_authorizer ||=
58
58
  authorizer_of(current_model_class, controller_to_get(:model_authorizer)).tap do |authorizer|
59
- Logger.debug %(Current authorizer: #{authorizer.try(:class)})
59
+ Logger.debug %(Current authorizer: #{authorizer.try(:class)}), sourcing: false
60
60
  end
61
61
  end
62
62
 
@@ -65,7 +65,7 @@ module Wallaby
65
65
  # @param subject [Object, Class]
66
66
  # @return [true] if allowed
67
67
  # @return [false] if not allowed
68
- # @since 5.2.0
68
+ # @since wallaby-5.2.0
69
69
  def authorized?(action, subject)
70
70
  return false unless subject
71
71
 
@@ -78,7 +78,7 @@ module Wallaby
78
78
  # @param subject [Object, Class]
79
79
  # @return [true] if not allowed
80
80
  # @return [false] if allowed
81
- # @since 5.2.0
81
+ # @since wallaby-5.2.0
82
82
  def unauthorized?(action, subject)
83
83
  !authorized? action, subject
84
84
  end
@@ -88,10 +88,10 @@ module Wallaby
88
88
  # @param model_class [Class]
89
89
  # @param authorizer_class [Class, nil]
90
90
  # @return [Wallaby::ModelAuthorizer] model authorizer for given model
91
- # @since 5.2.0
91
+ # @since wallaby-5.2.0
92
92
  def authorizer_of(model_class, authorizer_class = nil)
93
93
  authorizer_class ||= Map.authorizer_map(model_class, controller_to_get(:application_authorizer))
94
- authorizer_class.try :create, self, model_class
94
+ authorizer_class.new model_class, self
95
95
  end
96
96
  end
97
97
  end
@@ -3,33 +3,114 @@
3
3
  module Wallaby
4
4
  # Abstract related class methods
5
5
  module Baseable
6
- # Configurable attributes and class methods for marking a class the base class
7
- # and skipping {Wallaby::Map Wallaby mapping}
6
+ SUFFIX = /(Controller|Decorator|Servicer|Authorizer|Paginator)$/.freeze
7
+ ATTR_NAME = 'model_class'
8
+
9
+ # @param class_name [String]
10
+ # @param attr_name [String]
11
+ # @param suffix [String]
12
+ # @param plural [String]
13
+ # @return [Class] found associated class
14
+ # @raise [Wallaby::ClassNotFound] if associated class isn't found
15
+ def self.guess_associated_class_of(class_name, attr_name: ATTR_NAME, suffix: EMPTY_STRING, plural: false)
16
+ base_name = class_name.gsub(SUFFIX, EMPTY_STRING).try(plural ? :pluralize : :singularize) << suffix
17
+ parts = base_name.split(COLONS)
18
+ parts.each_with_index do |_, index|
19
+ begin
20
+ # NOTE: DO NOT try to use const_defined? and const_get EVER.
21
+ # This is Rails, use constantize
22
+ return parts[index..-1].join(COLONS).constantize
23
+ rescue NameError # rubocop:disable Lint/SuppressedException
24
+ end
25
+ end
26
+
27
+ raise ClassNotFound, <<~INSTRUCTION
28
+ The `#{attr_name}` hasn't been provided for Class `#{class_name}` and Wallaby cannot guess it right.
29
+ If `#{class_name}` is supposed to be a base class, add the following line to its class declaration:
30
+
31
+ class #{class_name}
32
+ base_class!
33
+ end
34
+
35
+ Otherwise, please specify the `#{attr_name}` in `#{class_name}`'s declaration as follows:
36
+
37
+ class #{class_name}
38
+ self.#{attr_name} = CorrectClass
39
+ end
40
+ INSTRUCTION
41
+ end
42
+
43
+ # Configurable attributes:
44
+ # 1. mark a class as a base class
45
+ # 2. guess the model class if model class isn't given
8
46
  module ClassMethods
9
47
  # @return [true] if class is a base class
10
48
  # @return [false] if class is not a base class
11
49
  def base_class?
12
- @base_class ||= false
50
+ @base_class == self
13
51
  end
14
52
 
15
- # Mark class a base class
16
- # @return [true]
53
+ # Mark the current class as the base class
17
54
  def base_class!
18
- @base_class = true
55
+ @base_class = self
56
+ end
57
+
58
+ # @!attribute [r] base_class
59
+ # @return [Class] The base class or the one from super class
60
+ def base_class
61
+ @base_class || superclass.try(:base_class)
62
+ end
63
+
64
+ # @!attribute [w] model_class
65
+ def model_class=(model_class)
66
+ raise ArgumentError 'Please provide a Class for `model_class`.' unless model_class.is_a? Class
67
+
68
+ @model_class = model_class
69
+ end
70
+
71
+ # @!attribute [r] model_class
72
+ # @example To configure the model class
73
+ # class Admin::ProductAuthorizer < Admin::ApplicationAuthorizer
74
+ # self.model_class = Product
75
+ # end
76
+ # @example To configure the model class for version below 5.2.0
77
+ # class Admin::ProductAuthorizer < Admin::ApplicationAuthorizer
78
+ # def self.model_class
79
+ # Product
80
+ # end
81
+ # end
82
+ # @return [Class] assigned model class or Wallaby will guess it
83
+ # (see {Wallaby::Baseable.guess_associated_class_of .guess_associated_class_of})
84
+ # @return [nil] if current class is marked as base class
85
+ # @raise [Wallaby::ModelNotFound] if model class isn't found
86
+ # @raise [ArgumentError] if base class is empty
87
+ def model_class
88
+ return if base_class?
89
+
90
+ @model_class ||= Baseable.guess_associated_class_of name
91
+ rescue TypeError
92
+ raise ArgumentError, <<~INSTRUCTION
93
+ Please specify the base class for class `#{name}`
94
+ by marking one of its parents `base_class!`, for example:
95
+
96
+ class ParentClass
97
+ base_class!
98
+ end
99
+ INSTRUCTION
19
100
  end
20
101
 
21
102
  # @!attribute [w] namespace
22
103
  # Used by `model_class`
23
- # @since 5.2.0
104
+ # @since wallaby-5.2.0
24
105
  attr_writer :namespace
25
106
 
26
107
  # @!attribute [r] namespace
27
108
  # @return [String] namespace
28
- # @since 5.2.0
109
+ # @since wallaby-5.2.0
29
110
  def namespace
30
111
  @namespace ||=
31
- ModuleUtils.try_to(superclass, :namespace) \
32
- || name.deconstantize.gsub(/Wallaby(::)?/, EMPTY_STRING).presence
112
+ superclass.try(:namespace) \
113
+ || name.deconstantize.gsub(/Wallaby(::)?/, EMPTY_STRING).presence
33
114
  end
34
115
  end
35
116
  end
@@ -22,7 +22,7 @@ module Wallaby
22
22
  # @return [Class] resource decorator
23
23
  # @raise [ArgumentError] when **resource_decorator** doesn't inherit from **application_decorator**
24
24
  # @see Wallaby::ResourceDecorator
25
- # @since 5.2.0
25
+ # @since wallaby-5.2.0
26
26
  attr_reader :resource_decorator
27
27
 
28
28
  # @!attribute [w] application_decorator
@@ -40,7 +40,7 @@ module Wallaby
40
40
  # @raise [ArgumentError] when **resource_decorator** doesn't inherit from **application_decorator**
41
41
  # @return [Class] application decorator
42
42
  # @see Wallaby::ResourceDecorator
43
- # @since 5.2.0
43
+ # @since wallaby-5.2.0
44
44
  def application_decorator
45
45
  @application_decorator ||= ModuleUtils.try_to superclass, :application_decorator
46
46
  end
@@ -68,7 +68,7 @@ module Wallaby
68
68
  @current_decorator ||=
69
69
  (controller_to_get(:resource_decorator) || \
70
70
  Map.resource_decorator_map(current_model_class, controller_to_get(:application_decorator))).tap do |decorator|
71
- Logger.debug %(Current decorator: #{decorator})
71
+ Logger.debug %(Current decorator: #{decorator}), sourcing: false
72
72
  end
73
73
  end
74
74