wallaby-core 0.1.0

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 (108) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +31 -0
  4. data/app/controllers/wallaby/application_controller.rb +84 -0
  5. data/app/controllers/wallaby/resources_controller.rb +381 -0
  6. data/app/controllers/wallaby/secure_controller.rb +81 -0
  7. data/app/security/ability.rb +13 -0
  8. data/config/locales/wallaby.en.yml +140 -0
  9. data/config/locales/wallaby_class.en.yml +30 -0
  10. data/config/routes.rb +39 -0
  11. data/lib/adaptors/wallaby/custom.rb +7 -0
  12. data/lib/adaptors/wallaby/custom/default_provider.rb +9 -0
  13. data/lib/adaptors/wallaby/custom/model_decorator.rb +71 -0
  14. data/lib/adaptors/wallaby/custom/model_finder.rb +13 -0
  15. data/lib/adaptors/wallaby/custom/model_pagination_provider.rb +14 -0
  16. data/lib/adaptors/wallaby/custom/model_service_provider.rb +48 -0
  17. data/lib/authorizers/wallaby/cancancan_authorization_provider.rb +72 -0
  18. data/lib/authorizers/wallaby/default_authorization_provider.rb +58 -0
  19. data/lib/authorizers/wallaby/model_authorizer.rb +100 -0
  20. data/lib/authorizers/wallaby/pundit_authorization_provider.rb +89 -0
  21. data/lib/concerns/wallaby/authorizable.rb +103 -0
  22. data/lib/concerns/wallaby/baseable.rb +36 -0
  23. data/lib/concerns/wallaby/decoratable.rb +101 -0
  24. data/lib/concerns/wallaby/defaultable.rb +38 -0
  25. data/lib/concerns/wallaby/engineable.rb +61 -0
  26. data/lib/concerns/wallaby/fieldable.rb +78 -0
  27. data/lib/concerns/wallaby/paginatable.rb +72 -0
  28. data/lib/concerns/wallaby/rails_overridden_methods.rb +42 -0
  29. data/lib/concerns/wallaby/resourcable.rb +149 -0
  30. data/lib/concerns/wallaby/servicable.rb +68 -0
  31. data/lib/concerns/wallaby/shared_helpers.rb +22 -0
  32. data/lib/concerns/wallaby/themeable.rb +40 -0
  33. data/lib/decorators/wallaby/resource_decorator.rb +189 -0
  34. data/lib/errors/wallaby/cell_handling.rb +6 -0
  35. data/lib/errors/wallaby/forbidden.rb +6 -0
  36. data/lib/errors/wallaby/general_error.rb +6 -0
  37. data/lib/errors/wallaby/invalid_error.rb +6 -0
  38. data/lib/errors/wallaby/model_not_found.rb +11 -0
  39. data/lib/errors/wallaby/not_authenticated.rb +6 -0
  40. data/lib/errors/wallaby/not_found.rb +6 -0
  41. data/lib/errors/wallaby/not_implemented.rb +6 -0
  42. data/lib/errors/wallaby/resource_not_found.rb +11 -0
  43. data/lib/errors/wallaby/unprocessable_entity.rb +6 -0
  44. data/lib/forms/wallaby/form_builder.rb +60 -0
  45. data/lib/helpers/wallaby/application_helper.rb +79 -0
  46. data/lib/helpers/wallaby/base_helper.rb +65 -0
  47. data/lib/helpers/wallaby/configuration_helper.rb +18 -0
  48. data/lib/helpers/wallaby/form_helper.rb +62 -0
  49. data/lib/helpers/wallaby/index_helper.rb +84 -0
  50. data/lib/helpers/wallaby/links_helper.rb +213 -0
  51. data/lib/helpers/wallaby/resources_helper.rb +52 -0
  52. data/lib/helpers/wallaby/secure_helper.rb +54 -0
  53. data/lib/helpers/wallaby/styling_helper.rb +82 -0
  54. data/lib/interfaces/wallaby/mode.rb +72 -0
  55. data/lib/interfaces/wallaby/model_authorization_provider.rb +99 -0
  56. data/lib/interfaces/wallaby/model_decorator.rb +168 -0
  57. data/lib/interfaces/wallaby/model_finder.rb +12 -0
  58. data/lib/interfaces/wallaby/model_pagination_provider.rb +107 -0
  59. data/lib/interfaces/wallaby/model_service_provider.rb +84 -0
  60. data/lib/paginators/wallaby/model_paginator.rb +115 -0
  61. data/lib/paginators/wallaby/resource_paginator.rb +12 -0
  62. data/lib/parsers/wallaby/parser.rb +34 -0
  63. data/lib/renderers/wallaby/cell.rb +137 -0
  64. data/lib/renderers/wallaby/cell_resolver.rb +89 -0
  65. data/lib/renderers/wallaby/custom_lookup_context.rb +64 -0
  66. data/lib/renderers/wallaby/custom_partial_renderer.rb +33 -0
  67. data/lib/renderers/wallaby/custom_renderer.rb +16 -0
  68. data/lib/responders/wallaby/json_api_responder.rb +101 -0
  69. data/lib/responders/wallaby/resources_responder.rb +28 -0
  70. data/lib/routes/wallaby/resources_router.rb +72 -0
  71. data/lib/servicers/wallaby/model_servicer.rb +154 -0
  72. data/lib/services/wallaby/engine_name_finder.rb +22 -0
  73. data/lib/services/wallaby/engine_url_for.rb +46 -0
  74. data/lib/services/wallaby/link_options_normalizer.rb +19 -0
  75. data/lib/services/wallaby/map/mode_mapper.rb +27 -0
  76. data/lib/services/wallaby/map/model_class_collector.rb +49 -0
  77. data/lib/services/wallaby/map/model_class_mapper.rb +38 -0
  78. data/lib/services/wallaby/prefixes_builder.rb +66 -0
  79. data/lib/services/wallaby/sorting/hash_builder.rb +19 -0
  80. data/lib/services/wallaby/sorting/link_builder.rb +69 -0
  81. data/lib/services/wallaby/sorting/next_builder.rb +63 -0
  82. data/lib/services/wallaby/sorting/single_builder.rb +20 -0
  83. data/lib/services/wallaby/type_renderer.rb +50 -0
  84. data/lib/support/action_dispatch/routing/mapper.rb +75 -0
  85. data/lib/tree/wallaby/node.rb +25 -0
  86. data/lib/utils/wallaby/cell_utils.rb +34 -0
  87. data/lib/utils/wallaby/field_utils.rb +43 -0
  88. data/lib/utils/wallaby/filter_utils.rb +20 -0
  89. data/lib/utils/wallaby/model_utils.rb +51 -0
  90. data/lib/utils/wallaby/module_utils.rb +46 -0
  91. data/lib/utils/wallaby/params_utils.rb +14 -0
  92. data/lib/utils/wallaby/preload_utils.rb +44 -0
  93. data/lib/utils/wallaby/test_utils.rb +34 -0
  94. data/lib/utils/wallaby/utils.rb +27 -0
  95. data/lib/wallaby/configuration.rb +103 -0
  96. data/lib/wallaby/configuration/features.rb +24 -0
  97. data/lib/wallaby/configuration/mapping.rb +140 -0
  98. data/lib/wallaby/configuration/metadata.rb +23 -0
  99. data/lib/wallaby/configuration/models.rb +46 -0
  100. data/lib/wallaby/configuration/pagination.rb +30 -0
  101. data/lib/wallaby/configuration/security.rb +98 -0
  102. data/lib/wallaby/configuration/sorting.rb +28 -0
  103. data/lib/wallaby/constants.rb +45 -0
  104. data/lib/wallaby/core.rb +117 -0
  105. data/lib/wallaby/core/version.rb +7 -0
  106. data/lib/wallaby/engine.rb +43 -0
  107. data/lib/wallaby/map.rb +170 -0
  108. metadata +222 -0
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Cancancan base authorization provider
5
+ class CancancanAuthorizationProvider < ModelAuthorizationProvider
6
+ # Detect and see if Cancancan is in use.
7
+ # @param context [ActionController::Base]
8
+ # @return [true] if Cancancan is in use.
9
+ # @return [false] if Cancancan is not in use.
10
+ def self.available?(context)
11
+ defined?(CanCanCan) && defined?(Ability) && context.respond_to?(:current_ability)
12
+ end
13
+
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
+ { ability: context.current_ability, user: ModuleUtils.try_to(context, :current_user) }
19
+ end
20
+
21
+ # @!attribute [r] ability
22
+ # @return [Ability]
23
+ attr_reader :ability
24
+
25
+ def initialize(ability:, user: nil)
26
+ @ability = ability
27
+ @user = user
28
+ end
29
+
30
+ # Check user's permission for an action on given subject.
31
+ # This method will be used in controller.
32
+ # @param action [Symbol, String]
33
+ # @param subject [Object, Class]
34
+ # @raise [Wallaby::Forbidden] when user is not authorized to perform the action.
35
+ def authorize(action, subject)
36
+ ability.authorize! action, subject
37
+ rescue ::CanCan::AccessDenied
38
+ Rails.logger.info I18n.t('errors.unauthorized', user: user, action: action, subject: subject)
39
+ raise Forbidden
40
+ end
41
+
42
+ # Check and see if user is allowed to perform an action on given subject.
43
+ # @param action [Symbol, String]
44
+ # @param subject [Object, Class]
45
+ # @return [Boolean]
46
+ def authorized?(action, subject)
47
+ ability.can? action, subject
48
+ end
49
+
50
+ # Restrict user to access certain scope.
51
+ # @param action [Symbol, String]
52
+ # @param scope [Object]
53
+ # @return [Object]
54
+ def accessible_for(action, scope)
55
+ ModuleUtils.try_to(scope, :accessible_by, ability, action) || scope
56
+ end
57
+
58
+ # Restrict user to assign certain values.
59
+ # @param action [Symbol, String]
60
+ # @param subject [Object]
61
+ # @return nil
62
+ delegate :attributes_for, to: :ability
63
+
64
+ # Just return nil
65
+ # @param action [Symbol, String]
66
+ # @param subject [Object]
67
+ # @return [nil]
68
+ def permit_params(action, subject)
69
+ # Do nothing
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Default authorization provider
5
+ class DefaultAuthorizationProvider < ModelAuthorizationProvider
6
+ # Always available.
7
+ # @param _context [ActionController::Base]
8
+ # @return [true]
9
+ def self.available?(_context)
10
+ true
11
+ end
12
+
13
+ # This will pull out the args required for contruction from context
14
+ # @param _context [ActionController::Base]
15
+ # @return [Hash] args for initialize
16
+ def self.args_from(_context)
17
+ {}
18
+ end
19
+
20
+ # Do nothing
21
+ # @param _action [Symbol, String]
22
+ # @param subject [Object, Class]
23
+ def authorize(_action, subject)
24
+ subject
25
+ end
26
+
27
+ # Always return true
28
+ # @param _action [Symbol, String]
29
+ # @param _subject [Object, Class]
30
+ # @return [true]
31
+ def authorized?(_action, _subject)
32
+ true
33
+ end
34
+
35
+ # Do nothing
36
+ # @param _action [Symbol, String]
37
+ # @param scope [Object]
38
+ def accessible_for(_action, scope)
39
+ scope
40
+ end
41
+
42
+ # Return empty attributes
43
+ # @param _action [Symbol, String]
44
+ # @param _subject [Object]
45
+ # @return [Hash] empty hash
46
+ def attributes_for(_action, _subject)
47
+ {}
48
+ end
49
+
50
+ # @note Please make sure to return nil when the authorization doesn't support this feature.
51
+ # @param _action [Symbol, String]
52
+ # @param _subject [Object]
53
+ # @return [nil]
54
+ def permit_params(_action, _subject)
55
+ # Do nothing
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Model Authorizer to provide authorization functions
5
+ # @since 5.2.0
6
+ class ModelAuthorizer
7
+ extend Baseable::ClassMethods
8
+
9
+ class << self
10
+ # @!attribute [w] model_class
11
+ attr_writer :model_class
12
+
13
+ # @!attribute [r] model_class
14
+ # Return associated model class, e.g. return **Product** for **ProductAuthorizer**.
15
+ #
16
+ # If Wallaby can't recognise the model class for Authorizer, it's required to be configured as below example:
17
+ # @example To configure model class
18
+ # class Admin::ProductAuthorizer < Admin::ApplicationAuthorizer
19
+ # self.model_class = Product
20
+ # end
21
+ # @example To configure model class for version below 5.2.0
22
+ # class Admin::ProductAuthorizer < Admin::ApplicationAuthorizer
23
+ # def self.model_class
24
+ # Product
25
+ # end
26
+ # end
27
+ # @return [Class] assoicated model class
28
+ # @return [nil] if current class is marked as base class
29
+ # @return [nil] if current class is the same as the value of {Wallaby::Configuration::Mapping#model_authorizer}
30
+ # @return [nil] if current class is {Wallaby::ModelAuthorizer}
31
+ # @return [nil] if assoicated model class is not found
32
+ def model_class
33
+ return unless self < ModelAuthorizer
34
+ return if base_class? || self == Wallaby.configuration.mapping.model_authorizer
35
+
36
+ @model_class ||= Map.model_class_map(name.gsub(/(^#{namespace}::)|(Authorizer$)/, EMPTY_STRING))
37
+ end
38
+
39
+ # @!attribute [w] provider_name
40
+ attr_writer :provider_name
41
+
42
+ # @!attribute [r] provider_name
43
+ # @return [String, Symbol] provider name of the authorization framework used
44
+ def provider_name
45
+ @provider_name ||= ModuleUtils.try_to superclass, :provider_name
46
+ end
47
+
48
+ # Factory method to create the model authorizer
49
+ # @param context [ActionController::Base]
50
+ # @param model_class [Class]
51
+ # @return [Wallaby::ModelAuthorizer]
52
+ def create(context, model_class)
53
+ model_class ||= self.model_class
54
+ provider_class = guess_provider_class context, model_class
55
+ new model_class, provider_class, provider_class.args_from(context)
56
+ end
57
+
58
+ private
59
+
60
+ # @param context [ActionController::Base]
61
+ # @param model_class [Class]
62
+ # @return [Class] provider class
63
+ def guess_provider_class(context, model_class)
64
+ providers = Map.authorizer_provider_map model_class
65
+ providers[provider_name] || providers.values.find { |klass| klass.available? context }
66
+ end
67
+ end
68
+
69
+ delegate(*ModelAuthorizationProvider.instance_methods(false), to: :@provider)
70
+
71
+ # @!attribute [r] model_class
72
+ # @return [Class]
73
+ attr_reader :model_class
74
+
75
+ # @!attribute [r] provider
76
+ # @return [Wallaby::ModelAuthorizationProvider]
77
+ # @since 5.2.0
78
+ attr_reader :provider
79
+
80
+ # @param model_class [Class]
81
+ # @param provider_name_or_class [String, Symbol, Class]
82
+ # @param options [Hash]
83
+ def initialize(model_class, provider_name_or_class, options = {})
84
+ @model_class = model_class || self.class.model_class
85
+ @provider = init_provider provider_name_or_class, options
86
+ end
87
+
88
+ protected
89
+
90
+ # Go through provider list and detect which provider is used.
91
+ # @param provider_name_or_class [String, Symbol, Class]
92
+ # @param options [Hash]
93
+ # @return [Wallaby::Authorizer]
94
+ def init_provider(provider_name_or_class, options)
95
+ providers = Map.authorizer_provider_map model_class
96
+ provider_class = provider_name_or_class.is_a?(Class) ? provider_name_or_class : providers[provider_name_or_class]
97
+ provider_class.new(**options)
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Pundit base authorization provider
5
+ class PunditAuthorizationProvider < ModelAuthorizationProvider
6
+ # 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
+ def self.available?(context)
11
+ defined?(Pundit) && context.respond_to?(:pundit_user)
12
+ end
13
+
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
+ # Check user's permission for an action on given subject.
27
+ #
28
+ # This method will be used in controller.
29
+ # @param action [Symbol, String]
30
+ # @param subject [Object, Class]
31
+ # @raise [Wallaby::Forbidden] when user is not authorized to perform the action.
32
+ def authorize(action, subject)
33
+ Pundit.authorize(user, subject, normalize(action)) && subject
34
+ rescue ::Pundit::NotAuthorizedError
35
+ Rails.logger.info I18n.t('errors.unauthorized', user: user, action: action, subject: subject)
36
+ raise Forbidden
37
+ end
38
+
39
+ # Check and see if user is allowed to perform an action on given subject
40
+ # @param action [Symbol, String]
41
+ # @param subject [Object, Class]
42
+ # @return [Boolean]
43
+ def authorized?(action, subject)
44
+ policy = Pundit.policy! user, subject
45
+ ModuleUtils.try_to policy, normalize(action)
46
+ end
47
+
48
+ # Restrict user to assign certain values.
49
+ #
50
+ # It will do a lookup in policy's methods and pick the first available method:
51
+ #
52
+ # - attributes\_for\_#\{ action \}
53
+ # - attributes\_for
54
+ # @param action [Symbol, String]
55
+ # @param subject [Object]
56
+ # @return [Hash] field value paired hash that user's allowed to assign
57
+ def attributes_for(action, subject)
58
+ policy = Pundit.policy! user, subject
59
+ value = ModuleUtils.try_to(policy, "attributes_for_#{action}") || ModuleUtils.try_to(policy, 'attributes_for')
60
+ Rails.logger.warn I18n.t('error.pundit.not_found.attributes_for', subject: subject) unless value
61
+ value || {}
62
+ end
63
+
64
+ # Restrict user for mass assignment.
65
+ #
66
+ # It will do a lookup in policy's methods and pick the first available method:
67
+ #
68
+ # - permitted\_attributes\_for\_#\{ action \}
69
+ # - permitted\_attributes
70
+ # @param action [Symbol, String]
71
+ # @param subject [Object]
72
+ # @return [Array] field list that user's allowed to change.
73
+ def permit_params(action, subject)
74
+ policy = Pundit.policy! user, subject
75
+ # @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')
78
+ end
79
+
80
+ private
81
+
82
+ # Convert action to pundit method name
83
+ # @param action [Symbol, String]
84
+ # @return [String] e.g. `create?`
85
+ def normalize(action)
86
+ "#{action}?".tr('??', '?')
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Authorizer related attributes
5
+ module Authorizable
6
+ # Configurable attribute for authorizer related
7
+ module ClassMethods
8
+ # @!attribute [w] model_authorizer
9
+ def model_authorizer=(model_authorizer)
10
+ ModuleUtils.inheritance_check model_authorizer, application_authorizer
11
+ @model_authorizer = model_authorizer
12
+ end
13
+
14
+ # @!attribute [r] model_authorizer
15
+ # If Wallaby doesn't get it right, please specify the **model_authorizer**.
16
+ # @example To set model authorizer
17
+ # class Admin::ProductionsController < Admin::ApplicationController
18
+ # self.model_authorizer = ProductAuthorizer
19
+ # end
20
+ # @return [Class] model authorizer
21
+ # @raise [ArgumentError] when **model_authorizer** doesn't inherit from **application_authorizer**
22
+ # @see Wallaby::ModelAuthorizer
23
+ # @since 5.2.0
24
+ attr_reader :model_authorizer
25
+
26
+ # @!attribute [w] application_authorizer
27
+ def application_authorizer=(application_authorizer)
28
+ ModuleUtils.inheritance_check model_authorizer, application_authorizer
29
+ @application_authorizer = application_authorizer
30
+ end
31
+
32
+ # @!attribute [r] application_authorizer
33
+ # The **application_authorizer** is as the base class of {#model_authorizer}.
34
+ # @example To set application decorator:
35
+ # class Admin::ApplicationController < Wallaby::ResourcesController
36
+ # self.application_authorizer = AnotherApplicationAuthorizer
37
+ # end
38
+ # @return [Class] application decorator
39
+ # @raise [ArgumentError] when **model_authorizer** doesn't inherit from **application_authorizer**
40
+ # @see Wallaby::ModelAuthorizer
41
+ # @since 5.2.0
42
+ def application_authorizer
43
+ @application_authorizer ||= ModuleUtils.try_to superclass, :application_authorizer
44
+ end
45
+ end
46
+
47
+ # Model authorizer for current modal class.
48
+ #
49
+ # It can be configured in following class attributes:
50
+ #
51
+ # - controller configuration {Wallaby::Authorizable::ClassMethods#model_authorizer .model_authorizer}
52
+ # - a generic authorizer based on
53
+ # {Wallaby::Authorizable::ClassMethods#application_authorizer .application_authorizer}
54
+ # @return [Wallaby::ModelAuthorizer] model authorizer
55
+ # @since 5.2.0
56
+ def current_authorizer
57
+ @current_authorizer ||=
58
+ authorizer_of(current_model_class, controller_to_get(:model_authorizer)).tap do |authorizer|
59
+ Rails.logger.info %( - Current authorizer: #{authorizer.try(:class)})
60
+ end
61
+ end
62
+
63
+ # Check if user is allowed to perform action on given subject
64
+ # @param action [Symbol, String]
65
+ # @param subject [Object, Class]
66
+ # @return [true] if allowed
67
+ # @return [false] if not allowed
68
+ # @since 5.2.0
69
+ def authorized?(action, subject)
70
+ return false unless subject
71
+
72
+ klass = subject.is_a?(Class) ? subject : subject.class
73
+ authorizer_of(klass).authorized? action, subject
74
+ end
75
+
76
+ # Check if user is allowed to perform action on given subject
77
+ # @param action [Symbol, String]
78
+ # @param subject [Object, Class]
79
+ # @return [true] if not allowed
80
+ # @return [false] if allowed
81
+ # @since 5.2.0
82
+ def unauthorized?(action, subject)
83
+ !authorized? action, subject
84
+ end
85
+
86
+ # @deprecated Use {#current_authorizer} instead. It will be removed from 5.3.*
87
+ def authorizer
88
+ Utils.deprecate 'deprecation.authorizer', caller: caller
89
+ current_authorizer
90
+ end
91
+
92
+ protected
93
+
94
+ # @param model_class [Class]
95
+ # @param authorizer_class [Class, nil]
96
+ # @return [Wallaby::ModelAuthorizer] model authorizer for given model
97
+ # @since 5.2.0
98
+ def authorizer_of(model_class, authorizer_class = nil)
99
+ authorizer_class ||= Map.authorizer_map(model_class, controller_to_get(:application_authorizer))
100
+ authorizer_class.try :create, self, model_class
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wallaby
4
+ # Abstract related class methods
5
+ module Baseable
6
+ # Configurable attributes and class methods for marking a class the base class
7
+ # and skipping {Wallaby::Map Wallaby mapping}
8
+ module ClassMethods
9
+ # @return [true] if class is a base class
10
+ # @return [false] if class is not a base class
11
+ def base_class?
12
+ @base_class ||= false
13
+ end
14
+
15
+ # Mark class a base class
16
+ # @return [true]
17
+ def base_class!
18
+ @base_class = true
19
+ end
20
+
21
+ # @!attribute [w] namespace
22
+ # Used by `model_class`
23
+ # @since 5.2.0
24
+ attr_writer :namespace
25
+
26
+ # @!attribute [r] namespace
27
+ # @return [String] namespace
28
+ # @since 5.2.0
29
+ def namespace
30
+ @namespace ||=
31
+ ModuleUtils.try_to(superclass, :namespace) \
32
+ || name.deconstantize.gsub(/Wallaby(::)?/, EMPTY_STRING).presence
33
+ end
34
+ end
35
+ end
36
+ end