simple_command_dispatcher 3.0.4 → 4.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.
@@ -1,135 +1,84 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'simple_command_dispatcher/version'
4
- require 'simple_command_dispatcher/klass_transform'
5
- require 'simple_command'
3
+ require 'active_support/core_ext/object/blank'
6
4
  require 'active_support/core_ext/string/inflections'
7
- require 'simple_command_dispatcher/configure'
8
-
9
- module Kernel
10
- def eigenclass
11
- class << self
12
- self
13
- end
14
- end
15
- end
5
+ require 'core_ext/kernel'
6
+ require 'simple_command_dispatcher/commands/command_callable'
7
+ require 'simple_command_dispatcher/configuration'
8
+ require 'simple_command_dispatcher/errors'
9
+ require 'simple_command_dispatcher/services/command_service'
10
+ require 'simple_command_dispatcher/version'
16
11
 
17
- module SimpleCommand
18
- # Provides a way to call SimpleCommands or your own custom commands in a more dymanic manner.
19
- #
20
- # For information about the simple_command gem, visit {https://rubygems.org/gems/simple_command}
12
+ module SimpleCommandDispatcher
13
+ # Provides a way to call your custom commands dynamically.
21
14
  #
22
- module Dispatcher
23
- class << self
24
- include SimpleCommand::KlassTransform
15
+ class << self
16
+ # Calls a *Command* given the command name, the namespace (modules) the command belongs to
17
+ # and the (request) parameters to pass to the command.
18
+ #
19
+ # @param command [Symbol, String] the name of the Command to call.
20
+ #
21
+ # @param command_namespace [Hash, Array] the ruby modules that qualify the Command to call.
22
+ # When passing a Hash, the Hash keys serve as documentation only.
23
+ # For example, ['Api', 'AppName', 'V1'] and { :api :Api, app_name: :AppName, api_version: :V1 }
24
+ # will both produce 'Api::AppName::V1', this string will be prepended to the command to form the Command
25
+ # to call (e.g. 'Api::AppName::V1::MySimpleCommand' = Api::AppName::V1::MySimpleCommand.call(*request_params)).
26
+ #
27
+ # @param request_params [Hash, Array, Object] the parameters to pass to the call method of the Command. This
28
+ # parameter is simply passed through to the call method of the Command. Hash parameters are passed as
29
+ # keyword arguments, Array parameters are passed as positional arguments, and other objects are passed
30
+ # as a single argument.
31
+ #
32
+ # @return [Object] the Object returned as a result of calling the Command#call method.
33
+ #
34
+ # @example
35
+ #
36
+ # # Below call equates to the following:
37
+ # # Api::Carz4Rent::V1::Authenticate.call({ email: 'sam@gmail.com', password: 'AskM3!' })
38
+ # SimpleCommandDispatcher.call(command: :Authenticate,
39
+ # command_namespace: { api: :Api, app_name: :Carz4Rent, api_version: :V1 },
40
+ # request_params: { email: 'sam@gmail.com', password: 'AskM3!' } ) # => Command result
41
+ #
42
+ # # Below equates to the following: Api::Carz4Rent::V2::Authenticate.call('sam@gmail.com', 'AskM3!')
43
+ # SimpleCommandDispatcher.call(command: :Authenticate,
44
+ # command_namespace: ['Api', 'Carz4Rent', 'V2'],
45
+ # request_params: ['sam@gmail.com', 'AskM3!']) # => Command result
46
+ #
47
+ # # Below equates to the following:
48
+ # # Api::Auth::JazzMeUp::V1::Authenticate.call('jazz_me@gmail.com', 'JazzM3!')
49
+ # SimpleCommandDispatcher.call(command: :Authenticate,
50
+ # command_namespace: ['Api::Auth::JazzMeUp', :V1],
51
+ # request_params: ['jazz_me@gmail.com', 'JazzM3!']) # => Command result
52
+ #
53
+ def call(command:, command_namespace: {}, request_params: nil)
54
+ # Create a constantized class from our command and command_namespace...
55
+ constantized_class_object = Services::CommandService.new(command:, command_namespace:).to_class
56
+ validate_command!(constantized_class_object)
25
57
 
26
- # Calls a *SimpleCommand* or *Command* given the command name, the modules the command belongs to
27
- # and the parameters to pass to the command.
28
- #
29
- # @param command [Symbol, String] the name of the SimpleCommand or Command to call.
30
- #
31
- # @param command_modules [Hash, Array] the ruby modules that qualify the SimpleCommand to call. When
32
- # passing a Hash, the Hash keys serve as documentation only. For example, ['Api', 'AppName', 'V1']
33
- # and { :api :Api, app_name: :AppName, api_version: :V1 } will both produce 'Api::AppName::V1',
34
- # this string will be prepended to the command to form the SimpleCommand to call
35
- # (e.g. 'Api::AppName::V1::MySimpleCommand' = Api::AppName::V1::MySimpleCommand.call(*command_parameters)).
36
- #
37
- # @param [Hash] options the options that determine how command and command_module are transformed.
38
- # @option options [Boolean] :camelize (false) determines whether or not both class and module names should be
39
- # camelized.
40
- # @option options [Boolean] :titleize (false) determines whether or not both class and module names should be
41
- # titleized.
42
- # @option options [Boolean] :class_titleize (false) determines whether or not class names should be titleized.
43
- # @option options [Boolean] :class_camelized (false) determines whether or not class names should be camelized.
44
- # @option options [Boolean] :module_titleize (false) determines whether or not module names should be titleized.
45
- # @option options [Boolean] :module_camelized (false) determines whether or not module names should be camelized.
46
- #
47
- # @param command_parameters [Array<Symbol>] the parameters to pass to the call method of the SimpleCommand.
48
- # This parameter is simplypassed through to the call method of the SimpleCommand/Command.
49
- #
50
- # @return [SimpleCommand, Object] the SimpleCommand or Object returned as a result of calling the
51
- # SimpleCommand#call method or the Command#call method respectfully.
52
- #
53
- # @example
54
- #
55
- # # Below call equates to the following:
56
- # # Api::Carz4Rent::V1::Authenticate.call({ email: 'sam@gmail.com', password: 'AskM3!' })
57
- # SimpleCommand::Dispatcher.call(:Authenticate,
58
- # { api: :Api, app_name: :Carz4Rent, api_version: :V1 },
59
- # { email: 'sam@gmail.com', password: 'AskM3!' } ) # => SimpleCommand result
60
- #
61
- # # Below equates to the following: Api::Carz4Rent::V2::Authenticate.call('sam@gmail.com', 'AskM3!')
62
- # SimpleCommand::Dispatcher.call(:Authenticate,
63
- # ['Api', 'Carz4Rent', 'V2'], 'sam@gmail.com', 'AskM3!') # => SimpleCommand result
64
- #
65
- # # Below equates to the following:
66
- # # Api::Auth::JazzMeUp::V1::Authenticate.call('jazz_me@gmail.com', 'JazzM3!')
67
- # SimpleCommand::Dispatcher.call(:Authenticate, ['Api::Auth::JazzMeUp', :V1],
68
- # 'jazz_me@gmail.com', 'JazzM3!') # => SimpleCommand result
69
- #
70
- def call(command = '', command_modules = {}, options = {}, *command_parameters)
71
- # Create a constantized class from our command and command_modules...
72
- command_class_constant = to_constantized_class(command, command_modules, options)
58
+ # We know we have a valid command class object if we get here. All we need to do is call the .call
59
+ # class method, pass the request_params arguments depending on the request_params data type, and
60
+ # return the results.
73
61
 
74
- # If we're NOT allowing custom commands, make sure we're dealing with a a command class
75
- # that prepends the SimpleCommand module.
76
- if !SimpleCommand::Dispatcher.configuration.allow_custom_commands && !simple_command?(command_class_constant)
77
- raise ArgumentError,
78
- "Class \"#{command_class_constant}\" " \
79
- 'must prepend module SimpleCommand if Configuration#allow_custom_commands is true.'
80
- end
81
-
82
- if valid_command?(command_class_constant)
83
- # We know we have a valid SimpleCommand; all we need to do is call #call,
84
- # pass the command_parameter variable arguments to the call, and return the results.
85
- run_command(command_class_constant, command_parameters)
86
- else
87
- raise NameError, "Class \"#{command_class_constant}\" does not respond_to? method ::call."
88
- end
89
- end
90
-
91
- private
62
+ call_command(constantized_class_object:, request_params:)
63
+ end
92
64
 
93
- # Returns true or false depending on whether or not the class constant has a public
94
- # class method named ::call defined. Commands that do not have a public class method
95
- # named ::call, are considered invalid.
96
- #
97
- # @param klass_constant [String] a class constant that will be validated to see whether or not the
98
- # class is a valid command.
99
- #
100
- # @return [Boolean] true if klass_constant has a public class method named ::call defined,
101
- # false otherwise.
102
- #
103
- # @!visibility public
104
- def valid_command?(klass_constant)
105
- klass_constant.eigenclass.public_method_defined?(:call)
106
- end
65
+ private
107
66
 
108
- # Returns true or false depending on whether or not the class constant prepends module
109
- # SimpleCommand::ClassMethods.
110
- #
111
- # @param klass_constant [String] a class constant that will be validated to see whether
112
- # or not the class prepends module SimpleCommand::ClassMethods.
113
- #
114
- # @return [Boolean] true if klass_constant prepends Module SimpleCommand::ClassMethods, false otherwise.
115
- #
116
- # @!visibility public
117
- def simple_command?(klass_constant)
118
- klass_constant.eigenclass.included_modules.include? SimpleCommand::ClassMethods
67
+ def call_command(constantized_class_object:, request_params:)
68
+ if request_params.is_a?(Hash)
69
+ constantized_class_object.call(**request_params)
70
+ elsif request_params.is_a?(Array)
71
+ constantized_class_object.call(*request_params)
72
+ elsif request_params.present?
73
+ constantized_class_object.call(request_params)
74
+ else
75
+ constantized_class_object.call
119
76
  end
77
+ end
120
78
 
121
- # Runs the command given the parameters and returns the result.
122
- #
123
- # @param klass_constant [String] a class constant that will be called.
124
- # @param parameters [Array] an array of parameters to pass to the command that will be called.
125
- #
126
- # @return [Object] returns the object (if any) that results from calling the command.
127
- #
128
- # @!visibility public
129
- def run_command(klass_constant, parameters)
130
- klass_constant.call(*parameters)
131
- # rescue NameError
132
- # raise NameError.new("Class \"#{klass_constant}\" does not respond_to? method ::call.")
79
+ def validate_command!(constantized_class_object)
80
+ unless constantized_class_object.eigenclass.public_method_defined?(:call)
81
+ raise Errors::RequiredClassMethodMissingError, constantized_class_object
133
82
  end
134
83
  end
135
84
  end
@@ -6,16 +6,15 @@ require 'simple_command_dispatcher/version'
6
6
 
7
7
  Gem::Specification.new do |spec|
8
8
  spec.name = 'simple_command_dispatcher'
9
- spec.version = SimpleCommand::Dispatcher::VERSION
9
+ spec.version = SimpleCommandDispatcher::VERSION
10
10
  spec.authors = ['Gene M. Angelo, Jr.']
11
11
  spec.email = ['public.gma@gmail.com']
12
12
 
13
- spec.summary = 'Provides a way to dispatch simple_command (ruby gem) commands or your own custom commands (service objects) in a more dynamic manner
14
- within your service API. Ideal for rails-api.'
15
- spec.description = 'Within a services API (rails-api for instance), you may have a need to execute different simple_commands or your own custom commands (service objects)
16
- based on one or more factors: multiple application, API version, user type, user credentials, etc. For example,
17
- your service API may need to execute either Api::Auth::V1::AuthenticateCommand.call(...) or Api::Auth::V2::AuthenticateCommand.call(...)
18
- based on the API version. simple_command_dispatcher allows you to execute either command with one line of code dynamically.'.gsub(/\s+/, ' ')
13
+ spec.summary = 'Dynamic command execution for Rails applications using convention over configuration - automatically maps request routes to command classes.'
14
+ spec.description = 'A lightweight Ruby gem that enables Rails applications to dynamically execute command objects using convention over configuration. ' \
15
+ 'Automatically transforms request paths into Ruby class constants, allowing controllers to dispatch commands based on routes and parameters. ' \
16
+ 'Features the optional CommandCallable module for standardized command interfaces with built-in success/failure tracking and error handling. ' \
17
+ 'Perfect for clean, maintainable Rails APIs with RESTful route-to-command mapping. Only depends on ActiveSupport for reliable camelization.'
19
18
  spec.homepage = 'https://github.com/gangelo/simple_command_dispatcher'
20
19
  spec.license = 'MIT'
21
20
 
@@ -35,7 +34,7 @@ Gem::Specification.new do |spec|
35
34
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
36
35
  spec.require_paths = ['lib']
37
36
 
38
- spec.required_ruby_version = Gem::Requirement.new('>= 3.0.1', '< 4.0')
39
- spec.add_runtime_dependency 'activesupport', '>= 7.0.8', '< 8.0'
40
- spec.add_runtime_dependency 'simple_command', '~> 1.0', '>= 1.0.1'
37
+ spec.required_ruby_version = Gem::Requirement.new('>= 3.3', '< 4.0')
38
+
39
+ spec.add_runtime_dependency 'activesupport', '>= 7.0.8', '< 9.0'
41
40
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_command_dispatcher
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.4
4
+ version: 4.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gene M. Angelo, Jr.
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-08-03 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activesupport
@@ -19,7 +18,7 @@ dependencies:
19
18
  version: 7.0.8
20
19
  - - "<"
21
20
  - !ruby/object:Gem::Version
22
- version: '8.0'
21
+ version: '9.0'
23
22
  type: :runtime
24
23
  prerelease: false
25
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,33 +28,14 @@ dependencies:
29
28
  version: 7.0.8
30
29
  - - "<"
31
30
  - !ruby/object:Gem::Version
32
- version: '8.0'
33
- - !ruby/object:Gem::Dependency
34
- name: simple_command
35
- requirement: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '1.0'
40
- - - ">="
41
- - !ruby/object:Gem::Version
42
- version: 1.0.1
43
- type: :runtime
44
- prerelease: false
45
- version_requirements: !ruby/object:Gem::Requirement
46
- requirements:
47
- - - "~>"
48
- - !ruby/object:Gem::Version
49
- version: '1.0'
50
- - - ">="
51
- - !ruby/object:Gem::Version
52
- version: 1.0.1
53
- description: 'Within a services API (rails-api for instance), you may have a need
54
- to execute different simple_commands or your own custom commands (service objects)
55
- based on one or more factors: multiple application, API version, user type, user
56
- credentials, etc. For example, your service API may need to execute either Api::Auth::V1::AuthenticateCommand.call(...)
57
- or Api::Auth::V2::AuthenticateCommand.call(...) based on the API version. simple_command_dispatcher
58
- allows you to execute either command with one line of code dynamically.'
31
+ version: '9.0'
32
+ description: A lightweight Ruby gem that enables Rails applications to dynamically
33
+ execute command objects using convention over configuration. Automatically transforms
34
+ request paths into Ruby class constants, allowing controllers to dispatch commands
35
+ based on routes and parameters. Features the optional CommandCallable module for
36
+ standardized command interfaces with built-in success/failure tracking and error
37
+ handling. Perfect for clean, maintainable Rails APIs with RESTful route-to-command
38
+ mapping. Only depends on ActiveSupport for reliable camelization.
59
39
  email:
60
40
  - public.gma@gmail.com
61
41
  executables: []
@@ -80,19 +60,25 @@ files:
80
60
  - Rakefile
81
61
  - bin/console
82
62
  - bin/setup
83
- - lib/core_extensions/string.rb
63
+ - lib/core_ext/kernel.rb
84
64
  - lib/simple_command_dispatcher.rb
65
+ - lib/simple_command_dispatcher/commands/command_callable.rb
66
+ - lib/simple_command_dispatcher/commands/errors.rb
67
+ - lib/simple_command_dispatcher/commands/utils.rb
85
68
  - lib/simple_command_dispatcher/configuration.rb
86
- - lib/simple_command_dispatcher/configure.rb
87
- - lib/simple_command_dispatcher/klass_transform.rb
69
+ - lib/simple_command_dispatcher/errors.rb
70
+ - lib/simple_command_dispatcher/errors/invalid_class_constant_error.rb
71
+ - lib/simple_command_dispatcher/errors/required_class_method_missing_error.rb
72
+ - lib/simple_command_dispatcher/helpers/camelize.rb
73
+ - lib/simple_command_dispatcher/helpers/trim_all.rb
74
+ - lib/simple_command_dispatcher/services/command_namespace_service.rb
75
+ - lib/simple_command_dispatcher/services/command_service.rb
88
76
  - lib/simple_command_dispatcher/version.rb
89
- - lib/tasks/simple_command_dispatcher_sandbox.rake
90
77
  - simple_command_dispatcher.gemspec
91
78
  homepage: https://github.com/gangelo/simple_command_dispatcher
92
79
  licenses:
93
80
  - MIT
94
81
  metadata: {}
95
- post_install_message:
96
82
  rdoc_options: []
97
83
  require_paths:
98
84
  - lib
@@ -100,7 +86,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
100
86
  requirements:
101
87
  - - ">="
102
88
  - !ruby/object:Gem::Version
103
- version: 3.0.1
89
+ version: '3.3'
104
90
  - - "<"
105
91
  - !ruby/object:Gem::Version
106
92
  version: '4.0'
@@ -110,10 +96,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
110
96
  - !ruby/object:Gem::Version
111
97
  version: '0'
112
98
  requirements: []
113
- rubygems_version: 3.2.15
114
- signing_key:
99
+ rubygems_version: 3.6.8
115
100
  specification_version: 4
116
- summary: Provides a way to dispatch simple_command (ruby gem) commands or your own
117
- custom commands (service objects) in a more dynamic manner within your service API.
118
- Ideal for rails-api.
101
+ summary: Dynamic command execution for Rails applications using convention over configuration
102
+ - automatically maps request routes to command classes.
119
103
  test_files: []
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class String
4
- # Returns a copy of string with all spaces removed.
5
- #
6
- # @return [String] with all spaces trimmed which includes all leading, trailing and embedded spaces.
7
- def trim_all
8
- gsub(/\s+/, '')
9
- end
10
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'configuration'
4
-
5
- module SimpleCommand
6
- module Dispatcher
7
- class << self
8
- attr_writer :configuration
9
- end
10
-
11
- # Returns the application configuration object.
12
- #
13
- # @return [Configuration] the application Configuration object.
14
- def self.configuration
15
- @configuration ||= Configuration.new
16
- end
17
-
18
- def self.configure
19
- yield(configuration)
20
- end
21
- end
22
- end
@@ -1,251 +0,0 @@
1
- # rubocop:disable Style/OptionHash
2
- # frozen_string_literal: true
3
-
4
- require_relative '../core_extensions/string'
5
-
6
- module SimpleCommand
7
- # Handles class and module transformations.
8
- module KlassTransform
9
- # Returns a constantized class (as a Class constant), given the klass and klass_modules.
10
- #
11
- # @param klass [Symbol or String] the class name.
12
- # @param klass_modules [Hash, Array or String] the modules klass belongs to.
13
- # @param options [Hash] the options that determine how klass_modules is transformed.
14
- # @option options [Boolean] :camelize (false) determines whether or not both klass and klass_modules
15
- # should be camelized.
16
- # @option options [Boolean] :titleize (false) determines whether or not both klass and klass_modules
17
- # should be titleized.
18
- # @option options [Boolean] :class_titleize (false) determines whether or not klass names should be
19
- # titleized.
20
- # @option options [Boolean] :class_camelized (false) determines whether or not klass names should be
21
- # camelized.
22
- # @option options [Boolean] :module_titleize (false) determines whether or not klass_modules names
23
- # should be titleized.
24
- # @option options [Boolean] :module_camelized (false) determines whether or not klass_modules names
25
- # should be camelized.
26
- #
27
- # @return [Class] the class constant. Can be used to call ClassConstant.constantize.
28
- #
29
- # @raise [NameError] if the constantized class string cannot be constantized; that is, if it is not
30
- # a valid class constant.
31
- #
32
- # @example
33
- #
34
- # to_constantized_class("Authenticate", "Api") # => Api::Authenticate
35
- # to_constantized_class(:Authenticate, [:Api, :AppName, :V1]) # => Api::AppName::V1::Authenticate
36
- # to_constantized_class(:Authenticate, { :api :Api, app_name: :AppName, api_version: :V2 })
37
- # # => Api::AppName::V2::Authenticate
38
- # to_constantized_class("authenticate", { :api :api, app_name: :app_name, api_version: :v1 },
39
- # { class_titleize: true, module_titleize: true }) # => Api::AppName::V1::Authenticate
40
- #
41
- def to_constantized_class(klass, klass_modules = [], options = {})
42
- constantized_class_string = to_constantized_class_string(klass, klass_modules, options)
43
-
44
- begin
45
- constantized_class_string.constantize
46
- rescue StandardError
47
- raise NameError, "\"#{constantized_class_string}\" is not a valid class constant."
48
- end
49
- end
50
-
51
- # Returns a fully-qualified constantized class (as a string), given the klass and klass_modules.
52
- #
53
- # @param [Symbol or String] klass the class name.
54
- # @param [Hash, Array or String] klass_modules the modules klass belongs to.
55
- # @param [Hash] options the options that determine how klass_modules is transformed.
56
- # @option options [Boolean] :class_titleize (false) Determines whether or not klass should be
57
- # titleized.
58
- # @option options [Boolean] :module_titleize (false) Determines whether or not klass_modules
59
- # should be titleized.
60
- #
61
- # @return [String] the fully qualified class, which includes module(s) and class name.
62
- #
63
- # @example
64
- #
65
- # to_constantized_class_string("Authenticate", "Api") # => "Api::Authenticate"
66
- # to_constantized_class_string(:Authenticate, [:Api, :AppName, :V1]) # => "Api::AppName::V1::Authenticate"
67
- # to_constantized_class_string(:Authenticate, { :api :Api, app_name: :AppName, api_version: :V2 })
68
- # # => "Api::AppName::V2::Authenticate"
69
- # to_constantized_class_string("authenticate", { :api :api, app_name: :app_name, api_version: :v1 },
70
- # { class_titleize: true, module_titleize: true }) # => "Api::AppName::V1::Authenticate"
71
- #
72
- def to_constantized_class_string(klass, klass_modules = [], options = {})
73
- options = ensure_options(options)
74
- klass_modules = to_modules_string(klass_modules, options)
75
- klass_string = to_class_string(klass, options)
76
- "#{klass_modules}#{klass_string}"
77
- end
78
-
79
- # Returns a string of modules that can be subsequently prepended to a class, to create a constantized class.
80
- #
81
- # @param [Hash, Array or String] klass_modules the modules a class belongs to.
82
- # @param [Hash] options the options that determine how klass_modules is transformed.
83
- # @option options [Boolean] :module_titleize (false) Determines whether or not klass_modules should be titleized.
84
- #
85
- # @return [String] a string of modules that can be subsequently prepended to a class, to create a
86
- # constantized class.
87
- #
88
- # @raise [ArgumentError] if the klass_modules is not of type String, Hash or Array.
89
- #
90
- # @example
91
- #
92
- # to_modules_string("Api") # => "Api::"
93
- # to_modules_string([:Api, :AppName, :V1]) # => "Api::AppName::V1::"
94
- # to_modules_string({ :api :Api, app_name: :AppName, api_version: :V1 }) # => "Api::AppName::V1::"
95
- # to_modules_string({ :api :api, app_name: :app_name, api_version: :v1 }, { module_titleize: true })
96
- # # => "Api::AppName::V1::"
97
- #
98
- def to_modules_string(klass_modules = [], options = {}) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
99
- klass_modules = validate_klass_modules(klass_modules)
100
-
101
- options = ensure_options(options)
102
-
103
- klass_modules_string = ''
104
- unless klass_modules.empty?
105
- case klass_modules
106
- when String
107
- klass_modules_string = klass_modules
108
- when Array
109
- klass_modules_string = klass_modules.join('::').to_s
110
- when Hash
111
- klass_modules_string = ''
112
- klass_modules.to_a.each_with_index.map do |value, index|
113
- klass_modules_string = index.zero? ? value[1].to_s : "#{klass_modules_string}::#{value[1]}"
114
- end
115
- else
116
- raise ArgumentError, 'Class modules is not a String, Hash or Array.'
117
- end
118
- klass_modules_string = klass_modules_string.split('::').map(&:titleize).join('::') if options[:module_titleize]
119
- klass_modules_string = camelize(klass_modules_string) if options[:module_camelize]
120
- klass_modules_string = klass_modules_string.trim_all
121
- klass_modules_string = "#{klass_modules_string}::" unless klass_modules_string.empty?
122
- end
123
-
124
- klass_modules_string
125
- end
126
-
127
- # Returns the klass as a string after transformations have been applied.
128
- #
129
- # @param [Symbol or String] klass the class name to be transformed.
130
- # @param [Hash] options the options that determine how klass will be transformed.
131
- # @option options [Boolean] :class_titleize (false) Determines whether or not klass should be titleized.
132
- #
133
- # @return [String] the transformed class as a string.
134
- #
135
- # @example
136
- #
137
- # to_class_string("MyClass") # => "MyClass"
138
- # to_class_string("myClass", { class_titleize: true }) # => "MyClass"
139
- # to_class_string(:MyClass) # => "MyClass"
140
- # to_class_string(:myClass, { class_titleize: true }) # => "MyClass"
141
- #
142
- def to_class_string(klass, options = {})
143
- klass = validate_klass(klass, options)
144
- klass = klass.titleize if options[:class_titleize]
145
- klass = camelize(klass) if options[:class_camelize]
146
- klass
147
- end
148
-
149
- # Transforms a route into a module string
150
- #
151
- # @return [String] the camelized token.
152
- #
153
- # @example
154
- #
155
- # camelize("/api/app/auth/v1") # => "Api::App::Auth::V1"
156
- # camelize("/api/app_name/auth/v1") # => "Api::AppName::Auth::V1"
157
- #
158
- def camelize(token)
159
- raise ArgumentError, 'Token is not a String' unless token.instance_of? String
160
-
161
- token.titlecase.camelize.sub(/^:*/, '').trim_all unless token.empty?
162
- end
163
-
164
- private
165
-
166
- # @!visibility public
167
- #
168
- # Ensures options are initialized and valid before accessing them.
169
- #
170
- # @param [Hash] options the options that determine how processing and transformations will be handled.
171
- # @option options [Boolean] :camelize (false) determines whether or not both class and module names
172
- # should be camelized.
173
- # @option options [Boolean] :titleize (false) determines whether or not both class and module names
174
- # should be titleized.
175
- # @option options [Boolean] :class_titleize (false) determines whether or not class names should be titleized.
176
- # @option options [Boolean] :module_titleize (false) determines whether or not module names should be titleized.
177
- # @option options [Boolean] :class_camelized (false) determines whether or not class names should be camelized.
178
- # @option options [Boolean] :module_camelized (false) determines whether or not module names should be camelized.
179
- #
180
- # @return [Hash] the initialized, validated options.
181
- #
182
- def ensure_options(options)
183
- options = {} unless options.instance_of? Hash
184
- options = { camelize: false, titleize: false, class_titleize: false, module_titleize: false,
185
- class_camelize: false, module_camelize: false }.merge(options)
186
-
187
- options[:class_camelize] = options[:module_camelize] = true if options[:camelize]
188
-
189
- options[:class_titleize] = options[:module_titleize] = true if options[:titleize]
190
-
191
- options
192
- end
193
-
194
- # @!visibility public
195
- #
196
- # Validates klass and returns klass as a string after all blanks have been removed using klass.gsub(/\s+/, "").
197
- #
198
- # @param [Symbol or String] klass the class name to be validated. klass cannot be empty?
199
- #
200
- # @return [String] the validated class as a string with blanks removed.
201
- #
202
- # @raise [ArgumentError] if the klass is empty? or not of type String or Symbol.
203
- #
204
- # @example
205
- #
206
- # validate_klass(" My Class ") # => "MyClass"
207
- # validate_klass(:MyClass) # => "MyClass"
208
- #
209
- def validate_klass(klass, _options)
210
- unless klass.is_a?(Symbol) || klass.is_a?(String)
211
- raise ArgumentError,
212
- 'Class is not a String or Symbol. Class must equal the class name of the ' \
213
- 'SimpleCommand or Command to call in the form of a String or Symbol.'
214
- end
215
-
216
- klass = klass.to_s.strip
217
-
218
- raise ArgumentError, 'Class is empty?' if klass.empty?
219
-
220
- klass
221
- end
222
-
223
- # @!visibility public
224
- #
225
- # Validates and returns klass_modules.
226
- #
227
- # @param [Symbol, Array or String] klass_modules the module(s) to be validated.
228
- #
229
- # @return [Symbol, Array or String] the validated module(s).
230
- #
231
- # @raise [ArgumentError] if the klass_modules is not of type String, Hash or Array.
232
- #
233
- # @example
234
- #
235
- # validate_modules(" Module ") # => " Module "
236
- # validate_modules(:Module) # => "Module"
237
- # validate_module("ModuleA::ModuleB") # => "ModuleA::ModuleB"
238
- #
239
- def validate_klass_modules(klass_modules)
240
- return {} if klass_modules.nil? || (klass_modules.respond_to?(:empty?) && klass_modules.empty?)
241
-
242
- if !klass_modules.instance_of?(String) && !klass_modules.instance_of?(Hash) && !klass_modules.instance_of?(Array)
243
- raise ArgumentError, 'Class modules is not a String, Hash or Array.'
244
- end
245
-
246
- klass_modules
247
- end
248
- end
249
- end
250
-
251
- # rubocop:enable Style/OptionHash