hanikamu-operation 0.1.1 → 0.1.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 (4) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/README.md +66 -183
  4. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8ff21656b7650f8c722178312ccfe54ed372086a165bcf13bd358f27d5880dd6
4
- data.tar.gz: 2b22c2521306b5e64a07bc510631c088d84100298947248fd4c27d8fdda8f3f2
3
+ metadata.gz: c0b23bd074385d4c3f45c13b8c247fd39e3b367567bc64b03a2a55db400f3b2a
4
+ data.tar.gz: d2c55ea2d5f6aac49462c6c964ccb2d81c568b37ac3cb85143285521eb85ab86
5
5
  SHA512:
6
- metadata.gz: 33b2a094c24b6d9690e2666a60cf2453d93b2e73113a8d7a247a46f7a0bfeca661301f61f711c26fa94a911d55655b4c9b181930a9fcb5b8f4bbbc8e416db55e
7
- data.tar.gz: 536172f90b5899eed4abf90c95984498dabc4695aa340109430b8be7835076fccca466fad27ce377e01e94c518105ca2e211fc496030560d765ddd2331f8163b
6
+ metadata.gz: '049de88ca1deb2c3604300762ce87fb3b7dad4657f6ffcbd553c33cc086cda780971b5859e84be171ca6800dca2a75c9cbb0de439bef7ad2999bda6c96e953e8'
7
+ data.tar.gz: 80f4dfb5e621dd5c914fc853596828277f940c13cbbc1935d30ed64ea2b42ceb1c79bbeeb1ab14d135a768b581bca2d77a83e2c9fbcaa1a5f9dc5728852cb88e
data/CHANGELOG.md CHANGED
@@ -7,3 +7,10 @@
7
7
  ## [0.1.1] - 2025-11-26
8
8
 
9
9
  - Updated Gemfile.lock
10
+
11
+ ## [0.1.2] - 2025-12-05
12
+
13
+ - **Breaking Change**: Minimum Ruby version is now 3.4.0
14
+ - Removed `redis-client` as direct dependency (now transitive through `redlock`)
15
+ - Updated CI to test only Ruby 3.4
16
+ - Improved README with clearer FormError vs GuardError examples
data/README.md CHANGED
@@ -8,9 +8,8 @@ A Ruby gem that extends [hanikamu-service](https://github.com/Hanikamu/hanikamu-
8
8
 
9
9
  1. [Why Hanikamu::Operation?](#why-hanikamuoperation)
10
10
  2. [Quick Start](#quick-start)
11
- 3. [Installation](#installation)
12
- 4. [Setup](#setup)
13
- 5. [Usage](#usage)
11
+ 3. [Setup](#setup)
12
+ 4. [Usage](#usage)
14
13
  - [Basic Operation](#basic-operation)
15
14
  - [Distributed Locking](#distributed-locking-with-within_mutex)
16
15
  - [Database Transactions](#database-transactions-with-within_transaction)
@@ -18,14 +17,14 @@ A Ruby gem that extends [hanikamu-service](https://github.com/Hanikamu/hanikamu-
18
17
  - [Guard Conditions](#guard-conditions)
19
18
  - [Block Requirements](#block-requirements)
20
19
  - [Complete Example](#complete-example-combining-all-features)
21
- 6. [Error Handling](#error-handling)
22
- 7. [Best Practices](#best-practices)
23
- 8. [Configuration Reference](#configuration-reference)
24
- 9. [Testing](#testing)
25
- 10. [Development](#development)
26
- 11. [Contributing](#contributing)
27
- 12. [License](#license)
28
- 13. [Credits](#credits)
20
+ 5. [Error Handling](#error-handling)
21
+ 6. [Best Practices](#best-practices)
22
+ 7. [Configuration Reference](#configuration-reference)
23
+ 8. [Testing](#testing)
24
+ 9. [Development](#development)
25
+ 10. [Contributing](#contributing)
26
+ 11. [License](#license)
27
+ 12. [Credits](#credits)
29
28
 
30
29
  ## Why Hanikamu::Operation?
31
30
 
@@ -63,9 +62,11 @@ Use `Hanikamu::Operation` (instead of plain `Hanikamu::Service`) when your busin
63
62
 
64
63
  **1. Install the gem**
65
64
 
65
+ Requires Ruby 3.4.0 or later.
66
+
66
67
  ```ruby
67
68
  # Gemfile
68
- gem 'hanikamu-operation', '~> 0.1.1'
69
+ gem 'hanikamu-operation', '~> 0.1.2'
69
70
  ```
70
71
 
71
72
  ```bash
@@ -122,32 +123,19 @@ else
122
123
  end
123
124
  ```
124
125
 
125
- ## Installation
126
-
127
- Add to your application's Gemfile:
128
-
129
- ```ruby
130
- gem 'hanikamu-operation', '~> 0.1.1'
131
- ```
132
-
133
- Then execute:
134
-
135
- ```bash
136
- bundle install
137
- ```
138
-
139
126
  ## Setup
140
127
 
141
- ### Rails Application Setup Guide
128
+ ### Rails Application Setup
142
129
 
143
130
  Follow these steps to integrate Hanikamu::Operation into a Rails application:
144
131
 
145
132
  **Step 1: Add the gem to your Gemfile**
146
133
 
134
+ Requires Ruby 3.4.0 or later.
135
+
147
136
  ```ruby
148
137
  # Gemfile
149
- gem 'hanikamu-operation', '~> 0.1.1'
150
- gem 'redis-client', '~> 0.22' # Required for distributed locking
138
+ gem 'hanikamu-operation', '~> 0.1.2'
151
139
  ```
152
140
 
153
141
  ```bash
@@ -214,68 +202,7 @@ brew services start redis
214
202
  REDIS_URL=redis://localhost:6379/0
215
203
  ```
216
204
 
217
- **Step 5: Create your first operation**
218
-
219
- ```bash
220
- # Create operations directory
221
- mkdir -p app/operations/users
222
- ```
223
-
224
- ```ruby
225
- # app/operations/users/create_user_operation.rb
226
- module Users
227
- class CreateUserOperation < Hanikamu::Operation
228
- attribute :email, Types::String
229
- attribute :password, Types::String
230
-
231
- validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
232
- validates :password, length: { minimum: 8 }
233
-
234
- within_transaction(:base)
235
-
236
- def execute
237
- user = User.create!(
238
- email: email,
239
- password: password
240
- )
241
-
242
- response user: user
243
- end
244
- end
245
- end
246
- ```
247
-
248
- **Step 6: Use in your controller**
249
-
250
- ```ruby
251
- # app/controllers/users_controller.rb
252
- class UsersController < ApplicationController
253
- def create
254
- result = Users::CreateUserOperation.call(user_params)
255
-
256
- if result.success?
257
- user = result.success.user
258
- render json: { user: user }, status: :created
259
- else
260
- error = result.failure
261
- case error
262
- when Hanikamu::Operation::FormError
263
- render json: { errors: error.errors.full_messages }, status: :unprocessable_entity
264
- else
265
- render json: { error: error.message }, status: :internal_server_error
266
- end
267
- end
268
- end
269
-
270
- private
271
-
272
- def user_params
273
- params.require(:user).permit(:email, :password)
274
- end
275
- end
276
- ```
277
-
278
- **Step 7: Configure for production**
205
+ **Step 5: Production configuration**
279
206
 
280
207
  Set your Redis URL in production (Heroku, AWS, etc.):
281
208
 
@@ -284,71 +211,11 @@ Set your Redis URL in production (Heroku, AWS, etc.):
284
211
  heroku addons:create heroku-redis:mini
285
212
  # REDIS_URL is automatically set
286
213
 
287
- # Or set manually
214
+ # Or set manually for other providers
288
215
  heroku config:set REDIS_URL=redis://your-redis-host:6379/0
289
216
  ```
290
217
 
291
- ### Detailed Configuration Options
292
-
293
- If you need more control, create a detailed initializer:
294
-
295
- ```ruby
296
- # config/initializers/hanikamu_operation.rb
297
- require 'redis-client'
298
-
299
- Hanikamu::Operation.configure do |config|
300
- # Required: Redis client for distributed locking
301
- config.redis_client = RedisClient.new(
302
- url: ENV.fetch('REDIS_URL', 'redis://localhost:6379/0'),
303
- reconnect_attempts: 3,
304
- timeout: 1.0
305
- )
306
-
307
- # Optional: Customize Redlock settings (these are the defaults)
308
- config.mutex_expire_milliseconds = 1500 # Lock TTL
309
- config.redlock_retry_count = 6 # Number of retry attempts
310
- config.redlock_retry_delay = 500 # Milliseconds between retries
311
- config.redlock_retry_jitter = 50 # Random jitter to prevent thundering herd
312
- config.redlock_timeout = 0.1 # Redis command timeout
313
-
314
- # Optional: Add errors to whitelist (Redlock::LockError is always included by default)
315
- config.whitelisted_errors = [CustomBusinessError]
316
- end
317
- ```
318
-
319
- ### Redis Setup by Environment
320
-
321
- **For Development (Docker Compose)**:
322
-
323
- ```yaml
324
- # docker-compose.yml
325
- services:
326
- redis:
327
- image: redis:7-alpine
328
- volumes:
329
- - redis_data:/data
330
- networks:
331
- - app_network
332
-
333
- app:
334
- # ... your app config
335
- environment:
336
- REDIS_URL: redis://redis:6379/0
337
- depends_on:
338
- - redis
339
- networks:
340
- - app_network
341
-
342
- volumes:
343
- redis_data:
344
-
345
- networks:
346
- app_network:
347
- ```
348
-
349
- **For Production**:
350
-
351
- Use a managed Redis service (AWS ElastiCache, Heroku Redis, Redis Labs, etc.) and set the `REDIS_URL` environment variable.
218
+ For advanced configuration options, see the [Configuration Reference](#configuration-reference) section below.
352
219
 
353
220
  ## Usage
354
221
 
@@ -764,52 +631,68 @@ Operations validate at three distinct levels, each serving a specific purpose:
764
631
 
765
632
  ### FormError vs GuardError: Practical Examples
766
633
 
767
- **FormError Example** - Invalid or incorrect input arguments:
634
+ Here's a complete example demonstrating the difference between form validations and guard conditions:
768
635
 
769
636
  ```ruby
770
- # Attempting to create a user with invalid inputs
771
- result = Users::CreateUserOperation.call(
772
- email: "taken@example.com",
773
- password: "short",
774
- password_confirmation: "wrong"
775
- )
776
- # => Failure(#<Hanikamu::Operation::FormError:
777
- # Email has been taken,
778
- # Password is too short,
779
- # Password confirmation does not match password>)
780
-
781
- # Correcting the arguments allows success
782
- result = Users::CreateUserOperation.call(
783
- email: "unique@example.com",
784
- password: "securePassword123!",
785
- password_confirmation: "securePassword123!"
786
- )
787
- # => Success(#<struct user=#<User id: 46, email: "unique@example.com">>)
637
+ class TestOperation < Hanikamu::Operation
638
+ attribute :sentence, Types::String
639
+
640
+ # Form validation - checks input value format/content
641
+ validates :sentence, presence: true
642
+ validates :sentence, exclusion: { in: ["form_error"], message: "is not allowed" }
643
+
644
+ # Guard validation - checks business rules/state
645
+ guard do
646
+ delegates :sentence
647
+
648
+ validates :sentence, exclusion: { in: ["guard_error"], message: "is not allowed" }
649
+ end
650
+
651
+ within_mutex(:mutex_lock)
652
+ within_transaction(:base)
653
+
654
+ def execute
655
+ response(message: sentence.reverse)
656
+ end
657
+
658
+ def mutex_lock
659
+ self.class.name
660
+ end
661
+ end
788
662
  ```
789
663
 
790
- **GuardError Example** - Valid arguments but invalid system state:
664
+ **FormError Example** - Invalid input value triggers form validation:
791
665
 
792
666
  ```ruby
793
- # First attempt succeeds
794
- result = Users::CompleteUserOperation.call!(user_id: 46)
795
- # => Success(#<struct user=#<User id: 46, completed_at: "2025-11-26">>)
667
+ # Form validation fails - input itself is invalid
668
+ result = TestOperation.call(sentence: "form_error")
669
+ # => Failure(#<Hanikamu::Operation::FormError: Sentence is not allowed>)
670
+
671
+ # Correcting the input allows the operation to proceed
672
+ result = TestOperation.call(sentence: "hello world")
673
+ # => Success(#<struct message="dlrow olleh">)
674
+ ```
796
675
 
797
- # Second attempt fails due to state, even with valid arguments
798
- result = Users::CompleteUserOperation.call!(user_id: 46)
799
- # => Failure(#<Hanikamu::Operation::GuardError: User has already been completed>)
676
+ **GuardError Example** - Valid input but business rule violated:
800
677
 
801
- # The arguments are still correct, but the operation cannot proceed
802
- # because the user's state has changed
678
+ ```ruby
679
+ # Input passes form validation, but guard fails
680
+ result = TestOperation.call(sentence: "guard_error")
681
+ # => Failure(#<Hanikamu::Operation::GuardError: Sentence is not allowed>)
682
+
683
+ # The input format is valid, but the business rule prevents execution
803
684
  ```
804
685
 
805
686
  **Type Error Example** - Wrong argument type:
806
687
 
807
688
  ```ruby
808
- # Passing wrong type raises immediately
809
- Users::CompleteUserOperation.call!(user_id: "not-a-number")
689
+ # Passing wrong type raises immediately before any validations
690
+ TestOperation.call!(sentence: 123)
810
691
  # => Raises Dry::Struct::Error
811
692
  ```
812
693
 
694
+ **Key Insight**: Form validations check if the *input is correct*, while guards check if the *operation can proceed* given the current state.
695
+
813
696
  ### Using `.call!` (Raises Exceptions)
814
697
 
815
698
  ```ruby
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hanikamu-operation
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nicolai Seerup
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2025-11-27 00:00:00.000000000 Z
12
+ date: 2025-12-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activemodel
@@ -132,7 +132,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
132
132
  requirements:
133
133
  - - ">="
134
134
  - !ruby/object:Gem::Version
135
- version: 3.2.0
135
+ version: 3.4.0
136
136
  required_rubygems_version: !ruby/object:Gem::Requirement
137
137
  requirements:
138
138
  - - ">="