konsierge-idempotency 0.1.1
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.
- checksums.yaml +7 -0
- data/Appraisals +49 -0
- data/CHANGELOG.md +18 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +66 -0
- data/Rakefile +10 -0
- data/exe/konsierge-idempotency +3 -0
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/rails_7_0.gemfile +31 -0
- data/gemfiles/rails_7_1.gemfile +31 -0
- data/gemfiles/rails_7_2.gemfile +31 -0
- data/gemfiles/rails_8_0.gemfile +31 -0
- data/gemfiles/rails_8_1.gemfile +31 -0
- data/lib/generators/konsierge/idempotency_generator.rb +70 -0
- data/lib/generators/konsierge/templates/add_idempotency_column.rb.tt +11 -0
- data/lib/idempotentable.rb +3 -0
- data/lib/konsierge/idempotency/idempotentable.rb +34 -0
- data/lib/konsierge/idempotency/normalizer.rb +45 -0
- data/lib/konsierge/idempotency/railtie.rb +11 -0
- data/lib/konsierge/idempotency/version.rb +5 -0
- data/lib/konsierge/idempotency.rb +25 -0
- data/lib/konsierge-idempotency.rb +15 -0
- metadata +82 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 19645d6689c36335eeda896fb47d1f1e6024b96dabeb2f25de89eb8313fec296
|
|
4
|
+
data.tar.gz: 9b3d7c0f1087efea8b0d86608f0e95ca3f3e81fa2d86158b16a31a86384770ba
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 43bd43515a2ec9de227b4626417d7566f4c0027a25f0976eeacc2f16fbd1317f69fe25f0e0b5fadb2efb03ba8f7cce762ab6f3ee73e59ff6f93f726a551b5133
|
|
7
|
+
data.tar.gz: d9da719592ff885131f663e06b6a1ce3a803a07299960bb1f8368530b68672ec862ab91fa357dedade95b381a52639dc0666388f31dc37ebc051a16309efe6dc
|
data/Appraisals
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
appraise 'rails-7-0' do
|
|
2
|
+
group :test do
|
|
3
|
+
remove_gem 'rails'
|
|
4
|
+
remove_gem 'sqlite3'
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
gem 'rails', '~> 7.0'
|
|
8
|
+
gem 'sqlite3', '~> 1.4'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
appraise 'rails-7-1' do
|
|
12
|
+
group :test do
|
|
13
|
+
remove_gem 'rails'
|
|
14
|
+
remove_gem 'sqlite3'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
gem 'rails', '~> 7.1'
|
|
18
|
+
gem 'sqlite3', '~> 1.4'
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
appraise 'rails-7-2' do
|
|
22
|
+
group :test do
|
|
23
|
+
remove_gem 'rails'
|
|
24
|
+
remove_gem 'sqlite3'
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
gem 'rails', '~> 7.2'
|
|
28
|
+
gem 'sqlite3', '~> 1.4'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
appraise 'rails-8-0' do
|
|
32
|
+
group :test do
|
|
33
|
+
remove_gem 'rails'
|
|
34
|
+
remove_gem 'sqlite3'
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
gem 'rails', '~> 8.0'
|
|
38
|
+
gem 'sqlite3', '~> 2.1'
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
appraise 'rails-8-1' do
|
|
42
|
+
group :test do
|
|
43
|
+
remove_gem 'rails'
|
|
44
|
+
remove_gem 'sqlite3'
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
gem 'rails', '~> 8.1'
|
|
48
|
+
gem 'sqlite3', '~> 2.1'
|
|
49
|
+
end
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
## [0.1.1] - 2026-05-28
|
|
4
|
+
|
|
5
|
+
- Switched idempotency digest from MD5 to SHA256.
|
|
6
|
+
- Extracted object normalization to a dedicated `Normalizer` class.
|
|
7
|
+
- Added configurable concern key column via `self.idempotency_key_column`.
|
|
8
|
+
- Added top-level `Idempotentable` include alias for models.
|
|
9
|
+
- Added Rails generator `rails g konsierge:idempotency Model [column_name]`:
|
|
10
|
+
- migration with conditional `add_column` and `add_index`
|
|
11
|
+
- model update with `include Idempotentable` and `self.idempotency_key_column = :column_name`
|
|
12
|
+
- Added Rails `railtie` integration for generator loading.
|
|
13
|
+
- Added dummy Rails app and appraisal matrix (`rails 7.0 .. 8.1`) for compatibility checks.
|
|
14
|
+
- Split and expanded test coverage for core, concern, and generator behavior.
|
|
15
|
+
|
|
16
|
+
## [0.1.0] - 2026-05-27
|
|
17
|
+
|
|
18
|
+
- Initial release.
|
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
"konsierge-idempotency" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
|
|
4
|
+
|
|
5
|
+
* Participants will be tolerant of opposing views.
|
|
6
|
+
* Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
|
|
7
|
+
* When interpreting the words and actions of others, participants should always assume good intentions.
|
|
8
|
+
* Behaviour which can be reasonably considered harassment will not be tolerated.
|
|
9
|
+
|
|
10
|
+
If you have any concerns about behaviour within this project, please contact us at ["i@golifox.ru"](mailto:"i@golifox.ru").
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 David Rybolovlev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Konsierge::Idempotency
|
|
2
|
+
|
|
3
|
+
Deterministic idempotency key builder for Ruby objects, plus ActiveRecord concern for model-level keys.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
Konsierge::Idempotency.key(
|
|
9
|
+
amount: 100,
|
|
10
|
+
currency: :usd,
|
|
11
|
+
issued_at: Time.utc(2026, 5, 27, 10, 0, 0, 123_456)
|
|
12
|
+
)
|
|
13
|
+
# => "sha256 hash string"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Instance API is also available:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
service = Konsierge::Idempotency.new(payload)
|
|
20
|
+
service.key
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
For ActiveRecord models:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
class Payment < ApplicationRecord
|
|
27
|
+
include Idempotentable
|
|
28
|
+
self.idempotency_key_column = :idempotency_key
|
|
29
|
+
|
|
30
|
+
def self.idempotent_columns
|
|
31
|
+
%i[external_id amount currency]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# before_create assigns `idempotency_key`
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
If model has no `idempotency_key` column, concern does not assign anything.
|
|
39
|
+
|
|
40
|
+
## Rails Generator
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
rails g konsierge:idempotency Order
|
|
44
|
+
rails g konsierge:idempotency order request_fingerprint
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Generator behavior:
|
|
48
|
+
- creates migration that adds string column (default `idempotency_key`) if it does not exist
|
|
49
|
+
- adds index for that column if it does not exist
|
|
50
|
+
- updates model with `include Idempotentable`
|
|
51
|
+
- sets `self.idempotency_key_column = :column_name` only for custom column names
|
|
52
|
+
|
|
53
|
+
## Development
|
|
54
|
+
|
|
55
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then run `bundle exec rspec` to run tests.
|
|
56
|
+
For Rails matrix validation, run `bundle exec appraisal install` and `bundle exec appraisal rspec`.
|
|
57
|
+
|
|
58
|
+
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 the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
63
|
+
|
|
64
|
+
## Code of Conduct
|
|
65
|
+
|
|
66
|
+
Everyone interacting in the Konsierge::Idempotency project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://gitlab.konsierge.com/digitalbox/gem/konsierge-idempotency/-/blob/master/CODE_OF_CONDUCT.md?ref_type=heads).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# This file was generated by Appraisal
|
|
2
|
+
|
|
3
|
+
source 'https://rubygems.org'
|
|
4
|
+
|
|
5
|
+
gem 'rails', '~> 7.0'
|
|
6
|
+
gem 'sqlite3', '~> 1.4'
|
|
7
|
+
|
|
8
|
+
group :development, :test do
|
|
9
|
+
gem 'dotenv'
|
|
10
|
+
gem 'irb'
|
|
11
|
+
gem 'mutex_m'
|
|
12
|
+
gem 'oj'
|
|
13
|
+
gem 'rack-cors'
|
|
14
|
+
gem 'rake'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
group :development do
|
|
18
|
+
gem 'appraisal'
|
|
19
|
+
gem 'rubocop'
|
|
20
|
+
gem 'rubocop-performance'
|
|
21
|
+
gem 'rubocop-rake'
|
|
22
|
+
gem 'rubocop-rspec'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
group :test do
|
|
26
|
+
gem 'rspec-rails'
|
|
27
|
+
gem 'simplecov'
|
|
28
|
+
gem 'stub_env'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
gemspec path: '../'
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# This file was generated by Appraisal
|
|
2
|
+
|
|
3
|
+
source 'https://rubygems.org'
|
|
4
|
+
|
|
5
|
+
gem 'rails', '~> 7.1'
|
|
6
|
+
gem 'sqlite3', '~> 1.4'
|
|
7
|
+
|
|
8
|
+
group :development, :test do
|
|
9
|
+
gem 'dotenv'
|
|
10
|
+
gem 'irb'
|
|
11
|
+
gem 'mutex_m'
|
|
12
|
+
gem 'oj'
|
|
13
|
+
gem 'rack-cors'
|
|
14
|
+
gem 'rake'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
group :development do
|
|
18
|
+
gem 'appraisal'
|
|
19
|
+
gem 'rubocop'
|
|
20
|
+
gem 'rubocop-performance'
|
|
21
|
+
gem 'rubocop-rake'
|
|
22
|
+
gem 'rubocop-rspec'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
group :test do
|
|
26
|
+
gem 'rspec-rails'
|
|
27
|
+
gem 'simplecov'
|
|
28
|
+
gem 'stub_env'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
gemspec path: '../'
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# This file was generated by Appraisal
|
|
2
|
+
|
|
3
|
+
source 'https://rubygems.org'
|
|
4
|
+
|
|
5
|
+
gem 'rails', '~> 7.2'
|
|
6
|
+
gem 'sqlite3', '~> 1.4'
|
|
7
|
+
|
|
8
|
+
group :development, :test do
|
|
9
|
+
gem 'dotenv'
|
|
10
|
+
gem 'irb'
|
|
11
|
+
gem 'mutex_m'
|
|
12
|
+
gem 'oj'
|
|
13
|
+
gem 'rack-cors'
|
|
14
|
+
gem 'rake'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
group :development do
|
|
18
|
+
gem 'appraisal'
|
|
19
|
+
gem 'rubocop'
|
|
20
|
+
gem 'rubocop-performance'
|
|
21
|
+
gem 'rubocop-rake'
|
|
22
|
+
gem 'rubocop-rspec'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
group :test do
|
|
26
|
+
gem 'rspec-rails'
|
|
27
|
+
gem 'simplecov'
|
|
28
|
+
gem 'stub_env'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
gemspec path: '../'
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# This file was generated by Appraisal
|
|
2
|
+
|
|
3
|
+
source 'https://rubygems.org'
|
|
4
|
+
|
|
5
|
+
gem 'rails', '~> 8.0'
|
|
6
|
+
gem 'sqlite3', '~> 2.1'
|
|
7
|
+
|
|
8
|
+
group :development, :test do
|
|
9
|
+
gem 'dotenv'
|
|
10
|
+
gem 'irb'
|
|
11
|
+
gem 'mutex_m'
|
|
12
|
+
gem 'oj'
|
|
13
|
+
gem 'rack-cors'
|
|
14
|
+
gem 'rake'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
group :development do
|
|
18
|
+
gem 'appraisal'
|
|
19
|
+
gem 'rubocop'
|
|
20
|
+
gem 'rubocop-performance'
|
|
21
|
+
gem 'rubocop-rake'
|
|
22
|
+
gem 'rubocop-rspec'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
group :test do
|
|
26
|
+
gem 'rspec-rails'
|
|
27
|
+
gem 'simplecov'
|
|
28
|
+
gem 'stub_env'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
gemspec path: '../'
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# This file was generated by Appraisal
|
|
2
|
+
|
|
3
|
+
source 'https://rubygems.org'
|
|
4
|
+
|
|
5
|
+
gem 'rails', '~> 8.1'
|
|
6
|
+
gem 'sqlite3', '~> 2.1'
|
|
7
|
+
|
|
8
|
+
group :development, :test do
|
|
9
|
+
gem 'dotenv'
|
|
10
|
+
gem 'irb'
|
|
11
|
+
gem 'mutex_m'
|
|
12
|
+
gem 'oj'
|
|
13
|
+
gem 'rack-cors'
|
|
14
|
+
gem 'rake'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
group :development do
|
|
18
|
+
gem 'appraisal'
|
|
19
|
+
gem 'rubocop'
|
|
20
|
+
gem 'rubocop-performance'
|
|
21
|
+
gem 'rubocop-rake'
|
|
22
|
+
gem 'rubocop-rspec'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
group :test do
|
|
26
|
+
gem 'rspec-rails'
|
|
27
|
+
gem 'simplecov'
|
|
28
|
+
gem 'stub_env'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
gemspec path: '../'
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require 'rails/generators'
|
|
2
|
+
require 'rails/generators/migration'
|
|
3
|
+
|
|
4
|
+
module Konsierge
|
|
5
|
+
module Generators
|
|
6
|
+
class IdempotencyGenerator < Rails::Generators::NamedBase
|
|
7
|
+
include Rails::Generators::Migration
|
|
8
|
+
|
|
9
|
+
DEFAULT_COLUMN_KEY = 'idempotency_key'.freeze
|
|
10
|
+
|
|
11
|
+
source_root File.expand_path('templates', __dir__)
|
|
12
|
+
desc 'Adds idempotency column + index and updates model for Idempotentable concern'
|
|
13
|
+
|
|
14
|
+
argument :column_name, type: :string, default: DEFAULT_COLUMN_KEY, banner: 'column_name'
|
|
15
|
+
|
|
16
|
+
def create_migration_file
|
|
17
|
+
migration_template 'add_idempotency_column.rb.tt', "db/migrate/add_#{column_name}_to_#{table_name}.rb"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def update_model
|
|
21
|
+
unless File.exist?(model_full_path)
|
|
22
|
+
say_status :skip, "Model file not found: #{model_path}", :yellow
|
|
23
|
+
return
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
source = File.read(model_full_path)
|
|
27
|
+
lines = []
|
|
28
|
+
lines << " include Idempotentable\n" unless source.match?(/^\s*include\s+Idempotentable\s*$/)
|
|
29
|
+
|
|
30
|
+
unless default_key_column?
|
|
31
|
+
if source.match?(/^\s*self\.idempotency_key_column\s*=\s*:.+$/)
|
|
32
|
+
gsub_file model_path,
|
|
33
|
+
/^\s*self\.idempotency_key_column\s*=\s*:.+$/,
|
|
34
|
+
" self.idempotency_key_column = :#{column_name}"
|
|
35
|
+
else
|
|
36
|
+
lines << "\n self.idempotency_key_column = :#{column_name}\n"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
return if lines.empty?
|
|
41
|
+
|
|
42
|
+
insert_into_file model_path, lines.join, after: /^\s*class\s+.+\n/
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.next_migration_number(_dirname)
|
|
46
|
+
@migration_number ||= Time.now.utc.strftime('%Y%m%d%H%M%S').to_i
|
|
47
|
+
@migration_number += 1
|
|
48
|
+
@migration_number.to_s
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def table_name(*)
|
|
54
|
+
name.to_s.underscore.pluralize.tr('/', '_')
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def model_path
|
|
58
|
+
File.join('app/models', "#{file_path}.rb")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def model_full_path
|
|
62
|
+
File.join(destination_root, model_path)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def default_key_column?
|
|
66
|
+
column_name.to_s == DEFAULT_COLUMN_KEY
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class <%= "Add#{column_name.camelize}To#{table_name.camelize}" %> < ActiveRecord::Migration[<%= "#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}" %>]
|
|
2
|
+
def change
|
|
3
|
+
unless column_exists?(:<%= table_name %>, :<%= column_name %>)
|
|
4
|
+
add_column :<%= table_name %>, :<%= column_name %>, :string
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
unless index_exists?(:<%= table_name %>, :<%= column_name %>)
|
|
8
|
+
add_index :<%= table_name %>, :<%= column_name %>
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'active_support/concern'
|
|
2
|
+
|
|
3
|
+
module Konsierge
|
|
4
|
+
class Idempotency
|
|
5
|
+
module Idempotentable
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
class_attribute :idempotency_key_column, instance_accessor: false, default: :idempotency_key
|
|
10
|
+
before_create :set_idempotency_key
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def idempotent_attributes
|
|
14
|
+
attributes.slice(*self.class.idempotent_columns.map(&:to_s))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def set_idempotency_key
|
|
20
|
+
key_column = self.class.idempotency_key_column.to_s
|
|
21
|
+
return unless has_attribute?(key_column)
|
|
22
|
+
return unless self[key_column].to_s.empty?
|
|
23
|
+
|
|
24
|
+
self[key_column] = Konsierge::Idempotency.key(idempotent_attributes)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class_methods do
|
|
28
|
+
def idempotent_columns
|
|
29
|
+
raise Konsierge::Idempotency::Error, "Please implement #idempotent_columns method in #{name}"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'date'
|
|
2
|
+
|
|
3
|
+
module Konsierge
|
|
4
|
+
class Idempotency
|
|
5
|
+
class Normalizer
|
|
6
|
+
class << self
|
|
7
|
+
def normalize(...)
|
|
8
|
+
new(...).normalize
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attr_reader :object
|
|
13
|
+
|
|
14
|
+
def initialize(object)
|
|
15
|
+
@object = object
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def normalize
|
|
19
|
+
normalize_object(object)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def normalize_object(value)
|
|
25
|
+
case value
|
|
26
|
+
when Hash
|
|
27
|
+
normalized_object = value.each_with_object({}) do |(k, v), hash|
|
|
28
|
+
hash[k.to_s] = normalize_object(v)
|
|
29
|
+
end
|
|
30
|
+
normalized_object.sort.to_h
|
|
31
|
+
when Array
|
|
32
|
+
value.map { |item| normalize_object(item) }
|
|
33
|
+
when Symbol
|
|
34
|
+
value.to_s
|
|
35
|
+
when Time, DateTime
|
|
36
|
+
value.iso8601(6)
|
|
37
|
+
when Date
|
|
38
|
+
value.iso8601
|
|
39
|
+
else
|
|
40
|
+
value
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'digest/sha2'
|
|
2
|
+
require 'json'
|
|
3
|
+
|
|
4
|
+
module Konsierge
|
|
5
|
+
class Idempotency
|
|
6
|
+
class Error < StandardError; end
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def key(...)
|
|
10
|
+
new(...).key
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attr_reader :object
|
|
15
|
+
|
|
16
|
+
def initialize(object)
|
|
17
|
+
@object = object
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def key
|
|
21
|
+
normalized_object = Normalizer.normalize(object)
|
|
22
|
+
Digest::SHA256.hexdigest(JSON.generate(normalized_object))
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require 'pathname'
|
|
2
|
+
require 'zeitwerk'
|
|
3
|
+
|
|
4
|
+
lib_root = Pathname.new(__dir__).expand_path.to_s
|
|
5
|
+
|
|
6
|
+
loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
|
|
7
|
+
loader.push_dir(lib_root)
|
|
8
|
+
loader.ignore(__FILE__)
|
|
9
|
+
loader.ignore("#{lib_root}/generators")
|
|
10
|
+
loader.ignore("#{lib_root}/idempotentdable.rb")
|
|
11
|
+
loader.ignore("#{lib_root}/konsierge/idempotency/{version,idempotentdable}.rb")
|
|
12
|
+
loader.setup
|
|
13
|
+
|
|
14
|
+
require 'konsierge/idempotency'
|
|
15
|
+
require 'konsierge/idempotency/railtie' if defined?(Rails::Railtie)
|
metadata
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: konsierge-idempotency
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- David Rybolovlev
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: zeitwerk
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
description: Provides object normalization, idempotency key generation and an optional
|
|
27
|
+
ActiveRecord concern.
|
|
28
|
+
email:
|
|
29
|
+
- i@golifox.ru
|
|
30
|
+
executables:
|
|
31
|
+
- konsierge-idempotency
|
|
32
|
+
extensions: []
|
|
33
|
+
extra_rdoc_files: []
|
|
34
|
+
files:
|
|
35
|
+
- Appraisals
|
|
36
|
+
- CHANGELOG.md
|
|
37
|
+
- CODE_OF_CONDUCT.md
|
|
38
|
+
- LICENSE.txt
|
|
39
|
+
- README.md
|
|
40
|
+
- Rakefile
|
|
41
|
+
- exe/konsierge-idempotency
|
|
42
|
+
- gemfiles/.bundle/config
|
|
43
|
+
- gemfiles/rails_7_0.gemfile
|
|
44
|
+
- gemfiles/rails_7_1.gemfile
|
|
45
|
+
- gemfiles/rails_7_2.gemfile
|
|
46
|
+
- gemfiles/rails_8_0.gemfile
|
|
47
|
+
- gemfiles/rails_8_1.gemfile
|
|
48
|
+
- lib/generators/konsierge/idempotency_generator.rb
|
|
49
|
+
- lib/generators/konsierge/templates/add_idempotency_column.rb.tt
|
|
50
|
+
- lib/idempotentable.rb
|
|
51
|
+
- lib/konsierge-idempotency.rb
|
|
52
|
+
- lib/konsierge/idempotency.rb
|
|
53
|
+
- lib/konsierge/idempotency/idempotentable.rb
|
|
54
|
+
- lib/konsierge/idempotency/normalizer.rb
|
|
55
|
+
- lib/konsierge/idempotency/railtie.rb
|
|
56
|
+
- lib/konsierge/idempotency/version.rb
|
|
57
|
+
homepage: https://gitlab.konsierge.com/digitalbox/gem/konsierge-idempotency
|
|
58
|
+
licenses:
|
|
59
|
+
- MIT
|
|
60
|
+
metadata:
|
|
61
|
+
homepage_uri: https://gitlab.konsierge.com/digitalbox/gem/konsierge-idempotency
|
|
62
|
+
source_code_uri: https://gitlab.konsierge.com/digitalbox/gem/konsierge-idempotency/-/tree/master?ref_type=heads
|
|
63
|
+
changelog_uri: https://gitlab.konsierge.com/digitalbox/gem/konsierge-idempotency/-/blob/master/CHANGELOG.md?ref_type=heads
|
|
64
|
+
rubygems_mfa_required: 'false'
|
|
65
|
+
rdoc_options: []
|
|
66
|
+
require_paths:
|
|
67
|
+
- lib
|
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '2.7'
|
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '0'
|
|
78
|
+
requirements: []
|
|
79
|
+
rubygems_version: 4.0.13
|
|
80
|
+
specification_version: 4
|
|
81
|
+
summary: Deterministic idempotency key builder for Ruby objects.
|
|
82
|
+
test_files: []
|