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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b808379fa5d1d1105d40bd2c9f6f960c0c2ebbfab0fffead3c483b1cd07fab4d
4
- data.tar.gz: 1cdfde9da6cf530b366fb31a5ed659d9d4175181cb8ec28b6588f686a50a7ece
3
+ metadata.gz: e734b411c2c25a8eb828f0a3610a868aea0da1ec3292ead6422494a7e69fe4f3
4
+ data.tar.gz: 37c0db19ed98a67512706633ca4c99120de9bdd0e246ee66d91cf19c536bfa5a
5
5
  SHA512:
6
- metadata.gz: 81f0c8d139ec9975ce47ffb157fb12c273f44c7496c8b071d40e0fed1498226e19ca7ab3254fe2d825b0ed8936a74db1341f2c40d0e3dfa46091880603dbeba2
7
- data.tar.gz: fb8f6be411fb84876a7ed7a1f9aa0f5f51341e97a3bdd6d2803c6357c1e8f32bed20d9b11e14bad886f926123d33a65bd49879a572f318b56ddc3a8bb4c897c4
6
+ metadata.gz: 80179286ff0d8c67f99d3881fec98791d436047de690a79de6b3a0df204ba8d132265111e78a00997e6d0188e1c04e36e5811aa9a9b2bcaacbb7e76492a833b5
7
+ data.tar.gz: 431ca7009491c389d0b0f34862077daa4214b5f6a8c75c44b5c5291fdeec09f607452bec1700dde50bd9c8449cd3d9c7f15b47821269f2fa57d921e0a3f20f6c
data/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  [![Test Coverage](https://api.codeclimate.com/v1/badges/f16f8d87553424c1aacc/test_coverage)](https://codeclimate.com/github/wallaby-rails/wallaby-core/test_coverage)
8
8
  [![Inch CI](https://inch-ci.org/github/wallaby-rails/wallaby-core.svg?branch=master)](https://inch-ci.org/github/wallaby-rails/wallaby-core)
9
9
 
10
- Wallaby::Core contains all the core interfaces that [Wallaby](https://github.com/wallaby-rails/wallaby) gem is built upon.
10
+ Wallaby::Core holds all the core interfaces that [Wallaby](https://github.com/wallaby-rails/wallaby) gem is built upon.
11
11
 
12
12
  ## Install
13
13
 
@@ -5,8 +5,17 @@ module Wallaby
5
5
 
6
6
  # Resources controller, superclass for all customization controllers.
7
7
  # It contains CRUD template action methods
8
- # (`index`/`new`/`create`/`edit`/`update`/`destroy`)
8
+ # ({Wallaby::ResourcesConcern#index #index} / {Wallaby::ResourcesConcern#new #new}
9
+ # / {Wallaby::ResourcesConcern#create #create} / {Wallaby::ResourcesConcern#edit #edit}
10
+ # / {Wallaby::ResourcesConcern#update #update} / {Wallaby::ResourcesConcern#destroy #destroy})
9
11
  # that allow subclasses to override.
12
+ #
13
+ # For better practice, please create an application controller class (see example)
14
+ # to better control the functions shared between different resource controllers.
15
+ # @example Create an application class for Admin Interface usage
16
+ # class Admin::ApplicationController < Wallaby::ResourcesController
17
+ # base_class!
18
+ # end
10
19
  class ResourcesController
11
20
  include ResourcesConcern
12
21
  end
@@ -1,9 +1,9 @@
1
1
  en:
2
2
  wallaby:
3
3
  map:
4
- missing_mode_for_model_class: "[WALLABY] Don't know how to handle this model %{model}."
4
+ missing_mode_for_model_class: "Don't know how to handle this model %{model}."
5
5
  model_class_mapper:
6
- missing_model_class: "[WALLABY] Please define self.model_class for %{model} or set it as global.\n @see Wallaby.configuration.mapping"
6
+ missing_model_class: "Please define self.model_class for %{model} or set it as global.\n @see Wallaby.configuration.mapping"
7
7
 
8
8
  mode:
9
9
  inherit_required: '%{klass} must inherit from %{parent}.'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Wallaby
4
4
  class Custom
5
- # Default provider for custom mode
5
+ # Default authorization provider for {Wallaby::Custom} mode that whitelists everything.
6
6
  class DefaultProvider < DefaultAuthorizationProvider
7
7
  end
8
8
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Wallaby
4
4
  class Custom
5
- # Custom modal decorator
5
+ # {Wallaby::Custom} mode decorator that only pulls out all the attributes from setter/getter methods.
6
6
  class ModelDecorator < ::Wallaby::ModelDecorator
7
7
  # Assume that attributes come from the setter/getter, e.g. `name=`/`name`
8
8
  # @return [ActiveSupport::HashWithIndifferentAccess] metadata
@@ -11,8 +11,7 @@ module Wallaby
11
11
  ::ActiveSupport::HashWithIndifferentAccess.new.tap do |hash|
12
12
  methods = model_class.public_instance_methods(false).map(&:to_s)
13
13
  methods
14
- .grep(/[^=]$/)
15
- .select { |method_id| methods.include? "#{method_id}=" }
14
+ .grep(/[^=]$/).select { |method_id| methods.include? "#{method_id}=" }
16
15
  .each { |attribute| hash[attribute] = { label: attribute.humanize, type: 'string' } }
17
16
  end.freeze
18
17
  end
@@ -50,21 +49,23 @@ module Wallaby
50
49
  @form_field_names ||= form_fields.keys - [primary_key.to_s]
51
50
  end
52
51
 
52
+ # @param resource [Object]
53
53
  # @return [ActiveModel::Errors]
54
54
  def form_active_errors(resource)
55
55
  @form_active_errors ||= ActiveModel::Errors.new resource
56
56
  end
57
57
 
58
- # @return [String, Symbole] primary key name
58
+ # @return [String, Symbole] default to `:id`
59
59
  def primary_key
60
60
  @primary_key ||= :id
61
61
  end
62
62
 
63
63
  # @param resource [Object]
64
- # @return [String]
64
+ # @return [String, nil]
65
65
  def guess_title(resource)
66
- field_name = FieldUtils.first_field_by({ name: /name|title|subject/ }, fields)
67
- ModuleUtils.try_to resource, field_name
66
+ FieldUtils
67
+ .first_field_by({ name: /name|title|subject/ }, fields)
68
+ .try { |field_name| resource.try field_name }
68
69
  end
69
70
  end
70
71
  end
@@ -2,9 +2,10 @@
2
2
 
3
3
  module Wallaby
4
4
  class Custom
5
- # Model finder
5
+ # Model finder for {Wallaby::Custom} mode that returns the list of model set by
6
+ # {Wallaby::Configuration#custom_models}
6
7
  class ModelFinder < ::Wallaby::ModelFinder
7
- # @return [Array<Class>] a list of classes
8
+ # @return [Wallaby::ClashArray] a list of classes
8
9
  def all
9
10
  Wallaby.configuration.custom_models.presence
10
11
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Wallaby
4
4
  class Custom
5
- # Model pagination provider
5
+ # Model pagination provider for {Wallaby::Custom} mode
6
6
  class ModelPaginationProvider < ::Wallaby::ModelPaginationProvider
7
7
  # By default, it doesn't support pagination
8
8
  # @return [false]
@@ -2,47 +2,8 @@
2
2
 
3
3
  module Wallaby
4
4
  class Custom
5
- # Model service provider
5
+ # Model service provider for {Wallaby::Custom} mode
6
6
  class ModelServiceProvider < ::Wallaby::ModelServiceProvider
7
- # @raise [Wallaby::NotImplemented]
8
- def permit(*)
9
- raise Wallaby::NotImplemented, Locale.t('errors.not_implemented.model_servicer', method_name: __callee__)
10
- end
11
-
12
- # @raise [Wallaby::NotImplemented]
13
- def collection(*)
14
- raise Wallaby::NotImplemented, Locale.t('errors.not_implemented.model_servicer', method_name: __callee__)
15
- end
16
-
17
- # @raise [Wallaby::NotImplemented]
18
- def paginate(*)
19
- raise Wallaby::NotImplemented, Locale.t('errors.not_implemented.model_servicer', method_name: __callee__)
20
- end
21
-
22
- # @raise [Wallaby::NotImplemented]
23
- def new(*)
24
- raise Wallaby::NotImplemented, Locale.t('errors.not_implemented.model_servicer', method_name: __callee__)
25
- end
26
-
27
- # @raise [Wallaby::NotImplemented]
28
- def find(*)
29
- raise Wallaby::NotImplemented, Locale.t('errors.not_implemented.model_servicer', method_name: __callee__)
30
- end
31
-
32
- # @raise [Wallaby::NotImplemented]
33
- def create(*)
34
- raise Wallaby::NotImplemented, Locale.t('errors.not_implemented.model_servicer', method_name: __callee__)
35
- end
36
-
37
- # @raise [Wallaby::NotImplemented]
38
- def update(*)
39
- raise Wallaby::NotImplemented, Locale.t('errors.not_implemented.model_servicer', method_name: __callee__)
40
- end
41
-
42
- # @raise [Wallaby::NotImplemented]
43
- def destroy(*)
44
- raise Wallaby::NotImplemented, Locale.t('errors.not_implemented.model_servicer', method_name: __callee__)
45
- end
46
7
  end
47
8
  end
48
9
  end
@@ -1,58 +1,63 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Wallaby
4
- # Cancancan base authorization provider
4
+ # @note This authorization provider DOES NOT use the existing
5
+ # {https://www.rubydoc.info/github/CanCanCommunity/cancancan/CanCan%2FControllerAdditions:current_ability
6
+ # current_ability} helper. It has its own version of {#ability} instance.
7
+ # {https://github.com/CanCanCommunity/cancancan CanCanCan} base authorization provider.
5
8
  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.
9
+ # Detect and see if CanCanCan is in use.
10
+ # @param context [ActionController::Base, ActionView::Base]
11
+ # @return [true] if CanCanCan is in use
12
+ # @return [false] otherwise.
10
13
  def self.available?(context)
11
- defined?(CanCanCan) && defined?(Ability) && context.respond_to?(:current_ability)
14
+ defined?(CanCanCan) && context.respond_to?(:current_ability)
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
- { ability: context.current_ability, user: ModuleUtils.try_to(context, :current_user) }
19
- end
17
+ # @!attribute [w] ability
18
+ attr_writer :ability
20
19
 
21
20
  # @!attribute [r] ability
22
- # @return [Ability]
23
- attr_reader :ability
24
-
25
- def initialize(ability:, user: nil)
26
- @ability = ability
27
- @user = user
21
+ # @return [Ability] the Ability instance for {#user #user} (which is a
22
+ # {Wallaby::AuthenticationConcern#wallaby_user #wallaby_user})
23
+ def ability
24
+ # NOTE: use current_ability's class to create the ability instance.
25
+ # just in case that developer uses a different Ability class (e.g. UserAbility)
26
+ @ability ||= options[:ability] || Ability.new(user)
27
+ rescue ArgumentError, NameError
28
+ context.current_ability
28
29
  end
29
30
 
30
31
  # Check user's permission for an action on given subject.
31
- # This method will be used in controller.
32
+ #
33
+ # This method will be mostly used in controller.
32
34
  # @param action [Symbol, String]
33
35
  # @param subject [Object, Class]
34
36
  # @raise [Wallaby::Forbidden] when user is not authorized to perform the action.
35
37
  def authorize(action, subject)
36
38
  ability.authorize! action, subject
37
39
  rescue ::CanCan::AccessDenied
38
- Logger.info Locale.t('errors.unauthorized', user: user, action: action, subject: subject)
40
+ Logger.error <<~MESSAGE
41
+ #{Utils.inspect user} is forbidden to perform #{action} on #{Utils.inspect subject}
42
+ MESSAGE
39
43
  raise Forbidden
40
44
  end
41
45
 
42
46
  # Check and see if user is allowed to perform an action on given subject.
43
47
  # @param action [Symbol, String]
44
48
  # @param subject [Object, Class]
45
- # @return [Boolean]
49
+ # @return [true] if user is allowed to perform the action
50
+ # @return [false] otherwise
46
51
  def authorized?(action, subject)
47
52
  ability.can? action, subject
48
53
  end
49
54
 
50
- # Restrict user to access certain scope.
55
+ # Restrict user to access certain scope/query.
51
56
  # @param action [Symbol, String]
52
57
  # @param scope [Object]
53
58
  # @return [Object]
54
59
  def accessible_for(action, scope)
55
- ModuleUtils.try_to(scope, :accessible_by, ability, action) || scope
60
+ scope.try(:accessible_by, ability, action) || scope
56
61
  end
57
62
 
58
63
  # @!method attributes_for(action, subject)
@@ -62,7 +67,7 @@ module Wallaby
62
67
  # @return nil
63
68
  delegate :attributes_for, to: :ability
64
69
 
65
- # Just return nil
70
+ # Simply return nil as CanCanCan doesn't provide such a feature.
66
71
  # @param action [Symbol, String]
67
72
  # @param subject [Object]
68
73
  # @return [nil]
@@ -1,20 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Wallaby
4
- # Default authorization provider
4
+ # Default authorization provider that whitelists everything.
5
5
  class DefaultAuthorizationProvider < ModelAuthorizationProvider
6
- # Always available.
7
- # @param _context [ActionController::Base]
8
- # @return [true]
6
+ # It returns false so that it can be used as the last resort.
7
+ # @param _context [ActionController::Base, ActionView::Base]
8
+ # @return [false]
9
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
- {}
10
+ false
18
11
  end
19
12
 
20
13
  # Do nothing
@@ -47,7 +40,7 @@ module Wallaby
47
40
  {}
48
41
  end
49
42
 
50
- # @note Please make sure to return nil when the authorization doesn't support this feature.
43
+ # @note Please make sure to return nil when the authorization provider doesn't support this feature.
51
44
  # @param _action [Symbol, String]
52
45
  # @param _subject [Object]
53
46
  # @return [nil]
@@ -1,68 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Wallaby
4
- # Model Authorizer to provide authorization functions
5
- # @since 5.2.0
4
+ # This is the base authorizer class to provider authorization for given/associated model.
5
+ #
6
+ # For best practice, please create an application authorizer class (see example)
7
+ # to better control the functions shared between different model authorizers.
8
+ # @example Create an application class for Admin Interface usage
9
+ # class Admin::ApplicationAuthorizer < Wallaby::ModelAuthorizer
10
+ # base_class!
11
+ # end
12
+ # @since wallaby-5.2.0
6
13
  class ModelAuthorizer
7
14
  extend Baseable::ClassMethods
15
+ base_class!
8
16
 
9
17
  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
18
  # @!attribute [w] provider_name
40
19
  attr_writer :provider_name
41
20
 
42
21
  # @!attribute [r] provider_name
43
- # @return [String, Symbol] provider name of the authorization framework used
22
+ # Provider name of the authorization framework used.
23
+ # It will be inherited from its parent classes if there isn't one for current class.
24
+ # @return [String, Symbol]
44
25
  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 }
26
+ @provider_name ||= superclass.try :provider_name
66
27
  end
67
28
  end
68
29
 
@@ -73,28 +34,43 @@ module Wallaby
73
34
  attr_reader :model_class
74
35
 
75
36
  # @!attribute [r] provider
76
- # @return [Wallaby::ModelAuthorizationProvider]
77
- # @since 5.2.0
37
+ # @return [Wallaby::ModelAuthorizationProvider] the instance that does the job
38
+ # @since wallaby-5.2.0
78
39
  attr_reader :provider
79
40
 
41
+ # @!attribute [r] context
42
+ # @return [ActionController::Base, ActionView::Base]
43
+ # @since 0.2.2
44
+ attr_reader :context
45
+
46
+ # @!attribute [r] options
47
+ # @return [Hash]
48
+ # @since 0.2.2
49
+ attr_reader :options
50
+
80
51
  # @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 = {})
52
+ # @param context [ActionController::Base, ActionView::Base]
53
+ # @param options [Symbol, String, nil]
54
+ def initialize(model_class, context, **options)
84
55
  @model_class = model_class || self.class.model_class
85
- @provider = init_provider provider_name_or_class, options
56
+ @context = context
57
+ @options = options
58
+ @provider = guess_provider_from(context)
86
59
  end
87
60
 
88
61
  protected
89
62
 
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)
63
+ # Go through the provider list and find out the one is
64
+ # {Wallaby::ModelAuthorizationProvider.available? .available?}
65
+ # @param context [ActionController::Base, ActionView::Base]
66
+ def guess_provider_from(context)
67
+ provider_class =
68
+ Map.authorizer_provider_map(model_class).try do |providers|
69
+ providers[options[:provider_name] || self.class.provider_name] \
70
+ || providers.values.find { |klass| klass.available? context } \
71
+ || providers[:default] # fallback to default
72
+ end
73
+ provider_class.new context, options
98
74
  end
99
75
  end
100
76
  end