wallaby-core 0.2.1 → 0.2.2

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