simple_command_dispatcher 3.0.4 → 4.0.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.
data/README.md CHANGED
@@ -1,308 +1,462 @@
1
- [![Ruby](https://github.com/gangelo/simple_command_dispatcher/actions/workflows/ruby.yml/badge.svg?refresh=2)](https://github.com/gangelo/simple_command_dispatcher/actions/workflows/ruby.yml)
2
- [![GitHub version](https://badge.fury.io/gh/gangelo%2Fsimple_command_dispatcher.svg?refresh=5)](https://badge.fury.io/gh/gangelo%2Fsimple_command_dispatcher)
3
- [![Gem Version](https://badge.fury.io/rb/simple_command_dispatcher.svg?refresh=5)](https://badge.fury.io/rb/simple_command_dispatcher)
1
+ [![Ruby](https://github.com/gangelo/simple_command_dispatcher/actions/workflows/ruby.yml/badge.svg?refresh=6)](https://github.com/gangelo/simple_command_dispatcher/actions/workflows/ruby.yml)
2
+ [![GitHub version](https://badge.fury.io/gh/gangelo%2Fsimple_command_dispatcher.svg?refresh=6)](https://badge.fury.io/gh/gangelo%2Fsimple_command_dispatcher)
3
+ [![Gem Version](https://badge.fury.io/rb/simple_command_dispatcher.svg?refresh=6)](https://badge.fury.io/rb/simple_command_dispatcher)
4
4
  [![](https://ruby-gem-downloads-badge.herokuapp.com/simple_command_dispatcher?type=total)](http://www.rubydoc.info/gems/simple_command_dispatcher/)
5
5
  [![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://www.rubydoc.info/gems/simple_command_dispatcher/)
6
6
  [![Report Issues](https://img.shields.io/badge/report-issues-red.svg)](https://github.com/gangelo/simple_command_dispatcher/issues)
7
7
  [![License](http://img.shields.io/badge/license-MIT-yellowgreen.svg)](#license)
8
8
 
9
- # Q. simple_command_dispatcher - what is it?
10
- # A. It's a Ruby gem!!!
9
+ # simple_command_dispatcher
11
10
 
12
11
  ## Overview
13
- __simple_command_dispatcher__ (SCD) allows you to execute __simple_command__ commands (and now _custom commands_ as of version 1.2.1) in a more dynamic way. If you are not familiar with the _simple_command_ gem, check it out [here][simple-command]. SCD was written specifically with the [rails-api][rails-api] in mind; however, you can use SDC wherever you would use simple_command commands.
14
12
 
15
- ## Update as of Version 1.2.1
16
- ### Custom Commands
17
- SCD now allows you to execute _custom commands_ (i.e. classes that do not prepend the _SimpleCommand_ module) by setting `Configuration#allow_custom_commands = true` (see the __Custom Commands__ section below for details).
13
+ **simple_command_dispatcher** (SCD) allows your Rails or Rails API application to _dynamically_ call backend command services from your Rails controller actions using a flexible, convention-over-configuration approach.
18
14
 
19
- ## Example
20
- The below example is from a `rails-api` API that uses token-based authentication and services two mobile applications, identified as *__my_app1__* and *__my_app2__*, in this example.
15
+ ## Features
21
16
 
22
- This example assumes the following:
17
+ - 🚀 **Dynamic Command Dispatch**: Call command classes by name with flexible namespacing
18
+ - 🔄 **Automatic Camelization**: Converts RESTful routes to Ruby constants automatically
19
+ - 🌐 **Unicode Support**: Handles Unicode characters and whitespace properly
20
+ - 🎯 **Multiple Input Formats**: Accepts strings, arrays, hashes for commands and namespaces
21
+ - ⚡ **Performance Optimized**: Uses Rails' proven camelization methods for speed
22
+ - 🔧 **Flexible Parameters**: Supports Hash, Array, and single object parameters
23
+ - 📦 **No Dependencies**: Removed simple_command dependency for lighter footprint
23
24
 
24
- * `application_controller` is a base class, inherited by all other controllers. The `#authenticate_request` method is called for every request in order to make sure the request is authorized (`before_action :authenticate_request`).
25
- * `request.headers` will contain the authorization token to authorize all requests (`request.headers["Authorization"]`)
26
- * This application uses the following folder structure to manage its _simple_command_ commands:
27
-
28
- ![N|Solid](https://cldup.com/1UeyWzOLic.png)
25
+ ## Installation
29
26
 
30
- Command classes (and the modules they reside under) are named *__according to their file name and respective location within the above folder structure__*; for example, the command class defined in the `/api/my_app1/v1/authenticate_request.rb` file would be defined in this manner:
27
+ Add this line to your application's Gemfile:
31
28
 
32
29
  ```ruby
33
- # /api/my_app1/v1/authenticate_request.rb
34
-
35
- module Api
36
- module MyApp1
37
- module V1
38
- class AuthenticateRequest
39
- end
40
- end
41
- end
42
- end
30
+ gem 'simple_command_dispatcher'
43
31
  ```
44
32
 
45
- Likewise, the command class defined in the `/api/my_app2/v2/update_user.rb` file would be defined in this manner, and so on:
33
+ And then execute:
46
34
 
47
- ```ruby
48
- # /api/my_app2/v2/update_user.rb
35
+ $ bundle
49
36
 
50
- module Api
51
- module MyApp2
52
- module V2
53
- class UpdateUser
54
- end
55
- end
56
- end
57
- end
58
- ```
37
+ Or install it yourself as:
59
38
 
60
- The __routes used in this example__, conform to the following format: `"/api/[app_name]/[app_version]/[controller]"` where `[app_name]` = the _application name_,`[app_version]` = the _application version_, and `[controller]` = the _controller_; therefore, running `$ rake routes` for this example would output the following sample route information:
39
+ $ gem install simple_command_dispatcher
61
40
 
41
+ ## Requirements
62
42
 
63
- | Prefix | Verb | URI Pattern | Controller#Action |
64
- |-------------:|:-------------|:------------------|:------------------|
65
- | api_my_app1_v1_user_authenticate | POST | /api/my_app1/v1/user/authenticate(.:format) | api/my_app1/v1/authentication#create |
66
- | api_my_app1_v2_user_authenticate | POST | /api/my_app1/v2/user/authenticate(.:format) | api/my_app1/v2/authentication#create |
67
- | api_my_app2_v1_user_authenticate | POST | /api/my_app2/v1/user/authenticate(.:format) | api/my_app2/v1/authentication#create |
68
- | api_my_app2_v2_user | PATCH | /api/my_app2/v2/users/:id(.:format) | api/my_app2/v2/users#update |
69
- | | PUT | /api/my_app2/v2/users/:id(.:format) | api/my_app2/v2/users#update |
43
+ - Ruby >= 3.1.0
44
+ - Rails (optional, but optimized for Rails applications)
70
45
 
46
+ ## Basic Usage
71
47
 
72
- ### Request Authentication Code Snippet
48
+ ### Simple Command Dispatch
73
49
 
74
50
  ```ruby
75
- # /config/initializers/simple_command_dispatcher.rb
76
-
77
- # See: http://pothibo.com/2013/07/namespace-stuff-in-your-app-folder/
78
-
79
- =begin
80
- # Uncomment this code if you want to namespace your commands in the following manner, for example:
81
- #
82
- # class Api::MyApp1::V1::AuthenticateRequest; end
83
- #
84
- # As opposed to this:
85
- #
86
- # module Api
87
- # module MyApp1
88
- # module V1
89
- # class AuthenticateRequest
90
- # end
91
- # end
92
- # end
93
- # end
94
- #
95
- module Helpers
96
- def self.ensure_namespace(namespace, scope = "::")
97
- namespace_parts = namespace.split("::")
98
-
99
- namespace_chain = ""
100
-
101
- namespace_parts.each { | part |
102
- namespace_chain = (namespace_chain.empty?) ? part : "#{namespace_chain}::#{part}"
103
- eval("module #{scope}#{namespace_chain}; end")
104
- }
105
- end
106
- end
51
+ # Basic command call
52
+ result = SimpleCommandDispatcher.call(
53
+ command: 'AuthenticateUser',
54
+ command_namespace: 'Api::V1',
55
+ request_params: { email: 'user@example.com', password: 'secret' }
56
+ )
57
+
58
+ # This calls: Api::V1::AuthenticateUser.call(email: 'user@example.com', password: 'secret')
59
+ ```
107
60
 
108
- Helpers.ensure_namespace("Api::MyApp1::V1")
109
- Helpers.ensure_namespace("Api::MyApp1::V2")
110
- Helpers.ensure_namespace("Api::MyApp2::V1")
111
- Helpers.ensure_namespace("Api::MyApp2::V2")
112
- =end
113
-
114
- # simple_command_dispatcher creates commands dynamically; therefore we need
115
- # to make sure the namespaces and command classes are loaded before we construct and
116
- # call them. The below code traverses the 'app/api' and all subfolders, and
117
- # autoloads them so that we do not get any NameError exceptions due to
118
- # uninitialized constants.
119
- Rails.application.config.to_prepare do
120
- path = Rails.root + "app/api"
121
- ActiveSupport::Dependencies.autoload_paths -= [path.to_s]
61
+ ### Automatic Camelization
122
62
 
123
- reloader = ActiveSupport::FileUpdateChecker.new [], path.to_s => [:rb] do
124
- ActiveSupport::DescendantsTracker.clear
125
- ActiveSupport::Dependencies.clear
63
+ Command names and namespaces are automatically camelized using optimized RESTful route conversion, allowing flexible input formats:
126
64
 
127
- Dir[path + "**/*.rb"].each do |file|
128
- ActiveSupport.require_or_load file
129
- end
130
- end
65
+ ```ruby
66
+ # All of these are equivalent and call: Api::UserSessions::V1::CreateCommand.call
67
+
68
+ # Lowercase strings with various separators
69
+ SimpleCommandDispatcher.call(
70
+ command: 'create_command',
71
+ command_namespace: 'api::user_sessions::v1'
72
+ )
73
+
74
+ # Mixed case array
75
+ SimpleCommandDispatcher.call(
76
+ command: :CreateCommand,
77
+ command_namespace: ['api', 'UserSessions', 'v1']
78
+ )
79
+
80
+ # Route-like strings (optimized for Rails controllers)
81
+ SimpleCommandDispatcher.call(
82
+ command: '/create_command',
83
+ command_namespace: '/api/user_sessions/v1'
84
+ )
85
+
86
+ # Mixed separators (hyphens, dots, spaces)
87
+ SimpleCommandDispatcher.call(
88
+ command: 'create-command',
89
+ command_namespace: 'api.user-sessions/v1'
90
+ )
91
+ ```
131
92
 
132
- Rails.application.reloaders << reloader
133
- ActionDispatch::Reloader.to_prepare { reloader.execute_if_updated }
134
- reloader.execute
135
- end
93
+ The camelization handles Unicode characters and removes all whitespace (including Unicode whitespace):
136
94
 
137
- # Optionally set our configuration setting to allow
138
- # for custom command execution.
139
- SimpleCommand::Dispatcher.configure do |config|
140
- config.allow_custom_commands = true
141
- end
95
+ ```ruby
96
+ # Unicode support
97
+ SimpleCommandDispatcher.call(
98
+ command: 'café_command',
99
+ command_namespace: 'api :: café :: v1' # Spaces are removed
100
+ )
101
+ # Calls: Api::Café::V1::CaféCommand.call
142
102
  ```
143
103
 
104
+ ### Parameter Handling
105
+
106
+ The dispatcher supports multiple parameter formats:
107
+
144
108
  ```ruby
145
- # /app/controllers/application_controller.rb
109
+ # Hash parameters (passed as keyword arguments)
110
+ SimpleCommandDispatcher.call(
111
+ command: 'CreateUser',
112
+ command_namespace: 'Api::V1',
113
+ request_params: { name: 'John', email: 'john@example.com' }
114
+ )
115
+ # Calls: Api::V1::CreateUser.call(name: 'John', email: 'john@example.com')
116
+
117
+ # Array parameters (passed as positional arguments)
118
+ SimpleCommandDispatcher.call(
119
+ command: 'ProcessData',
120
+ command_namespace: 'Services',
121
+ request_params: ['data1', 'data2', 'data3']
122
+ )
123
+ # Calls: Services::ProcessData.call('data1', 'data2', 'data3')
124
+
125
+ # Single parameter
126
+ SimpleCommandDispatcher.call(
127
+ command: 'SendEmail',
128
+ command_namespace: 'Mailers',
129
+ request_params: 'user@example.com'
130
+ )
131
+ # Calls: Mailers::SendEmail.call('user@example.com')
132
+
133
+ # No parameters
134
+ SimpleCommandDispatcher.call(
135
+ command: 'HealthCheck',
136
+ command_namespace: 'System'
137
+ )
138
+ # Calls: System::HealthCheck.call
139
+ ```
140
+
141
+ ## Rails Integration Example
142
+
143
+ Here's a comprehensive example showing how to integrate SCD with a Rails API application:
144
+
145
+ ### Application Controller
146
146
 
147
+ ```ruby
148
+ # app/controllers/application_controller.rb
147
149
  require 'simple_command_dispatcher'
148
150
 
149
151
  class ApplicationController < ActionController::API
150
- before_action :authenticate_request
151
- attr_reader :current_user
152
-
153
- protected
154
-
155
- def get_command_path
156
- # request.env['PATH_INFO'] could return any number of paths. The important
157
- # thing (in the case of our example), is that we get the portion of the
158
- # path that uniquely identifies the SimpleCommand we need to call; this
159
- # would include the application, the API version and the SimpleCommand
160
- # name itself.
161
- command_path = request.env['PATH_INFO'] # => "/api/[app name]/v1/[action]”
162
- command_path = command_path.split('/').slice(0,4).join('/') # => "/api/[app name]/v1/"
163
- end
164
-
165
- private
166
-
167
- def authenticate_request
168
- # The parameters and options we are passing to the dispatcher, wind up equating
169
- # to the following: Api::MyApp1::V1::AuthenticateRequest.call(request.headers).
170
- # Explaination: @param command_modules (e.g. path, "/api/my_app1/v1/"), in concert with @param
171
- # options { camelize: true }, is transformed into "Api::MyApp1::V1" and prepended to the
172
- # @param command, which becomes "Api::MyApp1::V1::AuthenticateRequest." This string is then
173
- # simply constantized; #call is then executed, passing the @param command_parameters
174
- # (e.g. request.headers, which contains ["Authorization"], out authorization token).
175
- # Consequently, the correlation between our routes and command class module structure
176
- # was no coincidence.
177
- command = SimpleCommand::Dispatcher.call(:AuthenticateRequest, get_command_path, { camelize: true}, request.headers)
178
- if command.success?
179
- @current_user = command.result
180
- else
181
- render json: { error: 'Not Authorized' }, status: 401
182
- end
152
+ before_action :authenticate_request
153
+ attr_reader :current_user
154
+
155
+ protected
156
+
157
+ def get_command_namespace
158
+ # Extract namespace from request path: "/api/my_app/v1/users" "api/my_app/v1"
159
+ path_segments = request.path.split('/').reject(&:empty?)
160
+ path_segments.take(3).join('/')
161
+ end
162
+
163
+ private
164
+
165
+ def authenticate_request
166
+ result = SimpleCommandDispatcher.call(
167
+ command: 'AuthenticateRequest',
168
+ command_namespace: get_command_namespace,
169
+ request_params: { headers: request.headers }
170
+ )
171
+
172
+ if result.success?
173
+ @current_user = result.user
174
+ else
175
+ render json: { error: 'Not Authorized' }, status: 401
183
176
  end
177
+ end
184
178
  end
185
179
  ```
186
180
 
187
- ## Custom Commands
181
+ ### Controller Actions
182
+
183
+ ```ruby
184
+ # app/controllers/api/my_app/v1/users_controller.rb
185
+ class Api::MyApp::V1::UsersController < ApplicationController
186
+ def create
187
+ result = SimpleCommandDispatcher.call(
188
+ command: 'CreateUser',
189
+ command_namespace: get_command_namespace,
190
+ request_params: user_params
191
+ )
192
+
193
+ if result.success?
194
+ render json: result.user, status: :ok
195
+ else
196
+ render json: { errors: result.errors }, status: :unprocessable_entity
197
+ end
198
+ end
199
+
200
+ def update
201
+ result = SimpleCommandDispatcher.call(
202
+ command: 'UpdateUser',
203
+ command_namespace: get_command_namespace,
204
+ request_params: { id: params[:id], **user_params }
205
+ )
206
+
207
+ if result.success?
208
+ render json: result.user
209
+ else
210
+ render json: { errors: result.errors }, status: :unprocessable_entity
211
+ end
212
+ end
188
213
 
189
- As of __Version 1.2.1__ simple_command_dispatcher (SCD) allows you to execute _custom commands_ (i.e. classes that do not prepend the _SimpleCommand_ module) by setting `Configuration#allow_custom_commands = true`.
214
+ private
190
215
 
191
- In order to execute _custom commands_, there are three (3) requirements:
192
- 1. Create a _custom command_. Your _custom command_ class must expose a public `::call` class method.
193
- 2. Set the `Configuration#allow_custom_commands` property to `true`.
194
- 3. Execute your _custom command_ by calling the `::call` class method.
216
+ def user_params
217
+ params.require(:user).permit(:name, :email, :phone)
218
+ end
219
+ end
220
+ ```
195
221
 
196
- ### Custom Command Example
222
+ ### Command Classes
197
223
 
198
- #### 1. Create a Custom Command
199
224
  ```ruby
200
- # /api/my_app/v1/custom_command.rb
201
-
225
+ # app/commands/api/my_app/v1/authenticate_request.rb
202
226
  module Api
203
- module MyApp
204
- module V1
227
+ module MyApp
228
+ module V1
229
+ class AuthenticateRequest
230
+ def self.call(headers:)
231
+ new(headers: headers).call
232
+ end
233
+
234
+ def initialize(headers:)
235
+ @headers = headers
236
+ end
237
+
238
+ def call
239
+ user = authenticate_with_token
240
+ if user
241
+ OpenStruct.new(success?: true, user: user)
242
+ else
243
+ OpenStruct.new(success?: false, errors: ['Invalid token'])
244
+ end
245
+ end
246
+
247
+ private
248
+
249
+ attr_reader :headers
250
+
251
+ def authenticate_with_token
252
+ token = headers['Authorization']&.gsub('Bearer ', '')
253
+ return nil unless token
254
+
255
+ # Your authentication logic here
256
+ User.find_by(auth_token: token)
257
+ end
258
+ end
259
+ end
260
+ end
261
+ end
262
+ ```
205
263
 
206
- # This is a custom command that does not prepend SimpleCommand.
207
- class CustomCommand
264
+ ```ruby
265
+ # app/commands/api/my_app/v1/create_user.rb
266
+ module Api
267
+ module MyApp
268
+ module V1
269
+ class CreateUser
270
+ def self.call(**params)
271
+ new(**params).call
272
+ end
273
+
274
+ def initialize(name:, email:, phone: nil)
275
+ @name = name
276
+ @email = email
277
+ @phone = phone
278
+ end
279
+
280
+ def call
281
+ user = User.new(name: name, email: email, phone: phone)
282
+
283
+ if user.save
284
+ OpenStruct.new(success?: true, user: user)
285
+ else
286
+ OpenStruct.new(success?: false, errors: user.errors.full_messages)
287
+ end
288
+ end
289
+
290
+ private
291
+
292
+ attr_reader :name, :email, :phone
293
+ end
294
+ end
295
+ end
296
+ end
297
+ ```
208
298
 
209
- def self.call(*args)
210
- command = self.new(*args)
211
- if command
212
- command.send(:execute)
213
- else
214
- false
215
- end
216
- end
299
+ ### Autoloading Commands
217
300
 
218
- private
301
+ To ensure your command classes are properly loaded:
219
302
 
220
- def initialize(params = {})
221
- @param1 = params[:param1]
222
- end
303
+ ```ruby
304
+ # config/initializers/simple_command_dispatcher.rb
223
305
 
224
- private
306
+ # Autoload command classes
307
+ Rails.application.config.to_prepare do
308
+ commands_path = Rails.root.join('app', 'commands')
225
309
 
226
- attr_accessor :param1
310
+ if commands_path.exist?
311
+ Dir[commands_path.join('**', '*.rb')].each do |file|
312
+ require_dependency file
313
+ end
314
+ end
315
+ end
316
+ ```
227
317
 
228
- def execute
229
- if (param1 == :param1)
230
- return true
231
- end
318
+ ## Error Handling
232
319
 
233
- return false
234
- end
235
- end
320
+ The dispatcher provides specific error classes for different failure scenarios:
236
321
 
237
- end
238
- end
322
+ ```ruby
323
+ begin
324
+ result = SimpleCommandDispatcher.call(
325
+ command: 'NonExistentCommand',
326
+ command_namespace: 'Api::V1'
327
+ )
328
+ rescue SimpleCommandDispatcher::Errors::InvalidClassConstantError => e
329
+ # Command class doesn't exist
330
+ puts "Command not found: #{e.message}"
331
+ rescue SimpleCommandDispatcher::Errors::RequiredClassMethodMissingError => e
332
+ # Command class exists but doesn't have a .call method
333
+ puts "Invalid command: #{e.message}"
334
+ rescue ArgumentError => e
335
+ # Invalid arguments (empty command, wrong parameter types, etc.)
336
+ puts "Invalid arguments: #{e.message}"
239
337
  end
240
338
  ```
241
- #### 2. Set the `Configuration#allow_custom_commands` property to `true`
242
- ```ruby
243
- # In your rails, rails-api app, etc...
244
- # /config/initializers/simple_command_dispatcher.rb
245
339
 
246
- SimpleCommand::Dispatcher.configure do |config|
247
- config.allow_custom_commands = true
340
+ ## Advanced Usage
341
+
342
+ ### Route-Based Command Dispatch
343
+
344
+ For RESTful APIs, you can map routes directly to commands:
345
+
346
+ ```ruby
347
+ # Extract command from route
348
+ def dispatch_from_route
349
+ # Route: "/api/my_app/v1/users/create"
350
+ path_segments = request.path.split('/').reject(&:empty?)
351
+
352
+ namespace = path_segments.take(3).join('/') # "api/my_app/v1"
353
+ command = path_segments.last # "create"
354
+
355
+ SimpleCommandDispatcher.call(
356
+ command: "#{command}_#{controller_name.singularize}", # "create_user"
357
+ command_namespace: namespace,
358
+ request_params: request_params
359
+ )
248
360
  end
249
361
  ```
250
362
 
251
- #### 3. Execute your _Custom Command_
252
- Executing your _custom command_ is no different than executing a __SimpleCommand__ command with the exception that you must properly handle the return object that results from calling your _custom command_; being a _custom command_, there is no guarantee that the return object will be the command object as is the case when calling a SimpleCommand command.
363
+ ### Dynamic API Versioning
364
+
253
365
  ```ruby
254
- # /app/controllers/some_controller.rb
366
+ # Handle multiple API versions dynamically
367
+ def call_versioned_command(command_name, version = 'v1')
368
+ SimpleCommandDispatcher.call(
369
+ command: command_name,
370
+ command_namespace: ['api', app_name, version],
371
+ request_params: request_params
372
+ )
373
+ end
255
374
 
256
- require 'simple_command_dispatcher'
375
+ # Usage
376
+ result = call_versioned_command('authenticate_user', 'v2')
377
+ ```
257
378
 
258
- class SomeController < ApplicationController::API
259
- public
379
+ ### Batch Command Execution
260
380
 
261
- def some_api
262
- success = SimpleCommand::Dispatcher.call(:CustomCommand, get_command_path, { camelize: true}, request.headers)
263
- if success
264
- # Do something...
265
- else
266
- # Do something else...
267
- end
268
- end
381
+ ```ruby
382
+ # Execute multiple related commands
383
+ def process_user_registration(user_data)
384
+ commands = [
385
+ { command: 'validate_user', params: user_data },
386
+ { command: 'create_user', params: user_data },
387
+ { command: 'send_welcome_email', params: { email: user_data[:email] } }
388
+ ]
389
+
390
+ results = commands.map do |cmd|
391
+ SimpleCommandDispatcher.call(
392
+ command: cmd[:command],
393
+ command_namespace: 'user_registration',
394
+ request_params: cmd[:params]
395
+ )
396
+ end
397
+
398
+ # Check if all commands succeeded
399
+ if results.all?(&:success?)
400
+ { success: true, user: results[1].user }
401
+ else
402
+ { success: false, errors: results.map(&:errors).flatten.compact }
403
+ end
269
404
  end
270
405
  ```
271
406
 
272
- ## Installation
407
+ ## Configuration
273
408
 
274
- Add this line to your application's Gemfile:
409
+ The gem can be configured in an initializer:
275
410
 
276
411
  ```ruby
277
- gem 'simple_command_dispatcher'
412
+ # config/initializers/simple_command_dispatcher.rb
413
+ SimpleCommandDispatcher.configure do |config|
414
+ # Configuration options will be added in future versions
415
+ end
278
416
  ```
279
417
 
280
- And then execute:
418
+ ## Migration from v3.x
281
419
 
282
- $ bundle
420
+ If you're upgrading from v3.x, here are the key changes:
283
421
 
284
- Or install it yourself as:
422
+ ### Breaking Changes
285
423
 
286
- $ gem install simple_command_dispatcher
424
+ 1. **Method signature changed to keyword arguments:**
425
+
426
+ ```ruby
427
+ # v3.x (old)
428
+ SimpleCommandDispatcher.call(:CreateUser, 'Api::V1', { options }, params)
429
+
430
+ # v4.x (new)
431
+ SimpleCommandDispatcher.call(
432
+ command: :CreateUser,
433
+ command_namespace: 'Api::V1',
434
+ request_params: params
435
+ )
436
+ ```
287
437
 
288
- ## Usage
438
+ 2. **Removed simple_command dependency:**
289
439
 
290
- See the example above.
440
+ - Commands no longer need to include SimpleCommand
441
+ - Commands must implement a `.call` class method
442
+ - Return value is whatever your command returns (no automatic Result object)
291
443
 
292
- ## Development
444
+ 3. **Removed configuration options:**
293
445
 
294
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
446
+ - `allow_custom_commands` option removed (all commands are "custom" now)
447
+ - Camelization options removed (always enabled)
295
448
 
296
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
449
+ 4. **Namespace changes:**
450
+ - Error classes: `SimpleCommand::Dispatcher::Errors::*` → `SimpleCommandDispatcher::Errors::*`
297
451
 
298
452
  ## Contributing
299
453
 
300
454
  Bug reports and pull requests are welcome on GitHub at https://github.com/gangelo/simple_command_dispatcher. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
301
455
 
302
-
303
456
  ## License
304
457
 
305
458
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
306
459
 
307
- [simple-command]: <https://rubygems.org/gems/simple_command>
308
- [rails-api]: <https://rubygems.org/gems/rails-api>
460
+ ## Changelog
461
+
462
+ See [CHANGELOG.md](CHANGELOG.md) for version history and breaking changes.