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
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