izolenta 0.0.1 → 0.0.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +3 -0
- data/Dockerfile +20 -0
- data/Gemfile.lock +53 -0
- data/README.md +26 -6
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/docker-compose.yml +10 -0
- data/izolenta.gemspec +6 -2
- data/lib/izolenta/active_record_migration.rb +54 -0
- data/lib/izolenta/version.rb +1 -1
- data/lib/izolenta.rb +1 -1
- metadata +51 -9
- data/.idea/inspectionProfiles/profiles_settings.xml +0 -5
- data/.idea/workspace.xml +0 -61
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7209aebff713c525a2b8e8d5c2f501d9759f32407b3cb42a2f6b14422279d8d8
|
4
|
+
data.tar.gz: d19e4c2ca5203587e7d1786a3137dc71cf6a715b9f32ea81529f3c4fc3d9ca1c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 868873584c72ff2fa9e03028916303a2de9d3f29223f2cfd33cf8b03147ece4a8d3fcb38c3befad464cad32175d39436b60dcd8428ffd0c3e1f19d9d257ffac1
|
7
|
+
data.tar.gz: a1f6ac0f5758d250a87e026df7ef41d09d78cdc5f50a0c017205f1c079b34fcdd46bdb6280b2fe5d5b99571c29c5ce4fac366e0a060b7aa2b0ea6835b7ca9584
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
data/Dockerfile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
FROM ruby:2.7.5-bullseye
|
2
|
+
|
3
|
+
WORKDIR /app
|
4
|
+
RUN apt-get update && apt-get -y install lsb-release
|
5
|
+
#
|
6
|
+
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \
|
7
|
+
sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' && \
|
8
|
+
apt-get update && apt-get -y install postgresql postgresql-client-12
|
9
|
+
|
10
|
+
RUN sh -c 'echo "local all all trust" > /etc/postgresql/14/main/pg_hba.conf' && \
|
11
|
+
service postgresql start && \
|
12
|
+
psql -U postgres -c 'CREATE DATABASE "izolenta-test"'
|
13
|
+
|
14
|
+
RUN gem install bundler
|
15
|
+
|
16
|
+
COPY lib/izolenta/version.rb /app/lib/izolenta/version.rb
|
17
|
+
COPY izolenta.gemspec /app/
|
18
|
+
COPY Gemfil* /app/
|
19
|
+
#
|
20
|
+
RUN bundle install
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
izolenta (0.0.2)
|
5
|
+
pg
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (6.1.4.1)
|
11
|
+
activesupport (= 6.1.4.1)
|
12
|
+
activerecord (6.1.4.1)
|
13
|
+
activemodel (= 6.1.4.1)
|
14
|
+
activesupport (= 6.1.4.1)
|
15
|
+
activesupport (6.1.4.1)
|
16
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
17
|
+
i18n (>= 1.6, < 2)
|
18
|
+
minitest (>= 5.1)
|
19
|
+
tzinfo (~> 2.0)
|
20
|
+
zeitwerk (~> 2.3)
|
21
|
+
byebug (11.1.3)
|
22
|
+
coderay (1.1.3)
|
23
|
+
concurrent-ruby (1.1.9)
|
24
|
+
i18n (1.8.11)
|
25
|
+
concurrent-ruby (~> 1.0)
|
26
|
+
method_source (1.0.0)
|
27
|
+
minitest (5.14.4)
|
28
|
+
pg (1.2.3)
|
29
|
+
pry (0.13.1)
|
30
|
+
coderay (~> 1.1)
|
31
|
+
method_source (~> 1.0)
|
32
|
+
rake (12.3.3)
|
33
|
+
ruby_jard (0.3.1)
|
34
|
+
byebug (>= 9.1, < 12.0)
|
35
|
+
pry (~> 0.13.0)
|
36
|
+
tty-screen (~> 0.8.1)
|
37
|
+
tty-screen (0.8.1)
|
38
|
+
tzinfo (2.0.4)
|
39
|
+
concurrent-ruby (~> 1.0)
|
40
|
+
zeitwerk (2.5.1)
|
41
|
+
|
42
|
+
PLATFORMS
|
43
|
+
ruby
|
44
|
+
|
45
|
+
DEPENDENCIES
|
46
|
+
activerecord (>= 5)
|
47
|
+
izolenta!
|
48
|
+
minitest (~> 5.0)
|
49
|
+
rake (~> 12.0)
|
50
|
+
ruby_jard
|
51
|
+
|
52
|
+
BUNDLED WITH
|
53
|
+
2.1.4
|
data/README.md
CHANGED
@@ -1,8 +1,19 @@
|
|
1
1
|
# Izolenta
|
2
|
+
This is a set of migration helpers delivering delegated uniqueness in Postgres ( A fine approach to solve 'optimistic' singleton record creation problem over data-set with a noticeable not-HOT UPDATEs amount present in corresponding object lifecycle. Read more: https://leshchuk.medium.com/delegated-uniqueness-in-postgres-d0fa103f749c )
|
2
3
|
|
3
|
-
|
4
|
+
The idea of the gem and article originated from two things:
|
5
|
+
- first I wanted to replace uniq index for optimistic objects creation with something less burdensome for DB.
|
6
|
+
- second I realised that trigger based solution for dialogs uniqueness I was using will fail to serve its purpose on a default PG isolaiton level.
|
4
7
|
|
5
|
-
|
8
|
+
The first thought was to use redis mutex approach, but it lacks the simplicity and is NOT hard solid as index solution.
|
9
|
+
Its better than done without, but still a lot could happened to fail it eventually.
|
10
|
+
|
11
|
+
So I ended up delegating UNIQUE constraint to a helper table whenever I really can win something from skipping uniq index over data set.
|
12
|
+
|
13
|
+
**What's up with the naming?** In russian 'izolenta' means electrical tape (or insulating tape)
|
14
|
+
serving purpose of isolation ( electrical ) on some monkey patching fixing.
|
15
|
+
|
16
|
+
As the idea of the gem: patching the data circuit with a 100% isolation.
|
6
17
|
|
7
18
|
## Installation
|
8
19
|
|
@@ -22,17 +33,26 @@ Or install it yourself as:
|
|
22
33
|
|
23
34
|
## Usage
|
24
35
|
|
25
|
-
|
36
|
+
```ruby
|
37
|
+
class YourMigration < ActiveRecord::Migration[5.0]
|
38
|
+
def change
|
39
|
+
delegate_uniqueness( :your_table_name, :column_name )
|
40
|
+
end
|
41
|
+
end
|
42
|
+
```
|
26
43
|
|
27
44
|
## Development
|
28
45
|
|
29
|
-
|
46
|
+
```bash
|
47
|
+
docker-compose build
|
30
48
|
|
31
|
-
|
49
|
+
docker-compose run test /bin/bash
|
50
|
+
> service postgresql start && rake test
|
51
|
+
```
|
32
52
|
|
33
53
|
## Contributing
|
34
54
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
55
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/alekseyl/izolenta. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/izolenta/blob/master/CODE_OF_CONDUCT.md).
|
36
56
|
|
37
57
|
|
38
58
|
## License
|
data/bin/console
CHANGED
@@ -1,14 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require "bundler/setup"
|
4
|
-
require "izolenta"
|
5
|
-
|
6
|
-
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
-
# with your gem easier. You can also use a different console, if you like.
|
8
|
-
|
9
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
-
# require "pry"
|
11
|
-
# Pry.start
|
12
|
-
|
13
|
-
require "irb"
|
14
|
-
IRB.start(__FILE__)
|
data/bin/setup
CHANGED
data/docker-compose.yml
ADDED
data/izolenta.gemspec
CHANGED
@@ -6,8 +6,8 @@ Gem::Specification.new do |spec|
|
|
6
6
|
spec.authors = ["alekseyl"]
|
7
7
|
spec.email = ["leshchuk@gmail.com"]
|
8
8
|
|
9
|
-
spec.summary = %q{
|
10
|
-
spec.description = %q{
|
9
|
+
spec.summary = %q{Migration helpers for delegated uniqueness in Postgres}
|
10
|
+
spec.description = %q{Migration helpers for delegated uniqueness in Postgres}
|
11
11
|
spec.homepage = "https://github.com/alekseyl/izolenta"
|
12
12
|
spec.license = "MIT"
|
13
13
|
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
@@ -26,4 +26,8 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.bindir = "exe"
|
27
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
28
|
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_dependency "pg"
|
31
|
+
spec.add_development_dependency "activerecord", ">= 5"
|
32
|
+
spec.add_development_dependency "ruby_jard"
|
29
33
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Izolenta::ActiveRecordMigration
|
2
|
+
|
3
|
+
def delegate_uniqueness(origin_table, column, options = {})
|
4
|
+
helper_table_name = "#{column}_#{origin_table}_uniqs"
|
5
|
+
reversible do |dir|
|
6
|
+
dir.up {
|
7
|
+
create_helper_table(helper_table_name, column, get_column_type(origin_table, column ) )
|
8
|
+
create_sync_trigger( origin_table, column, helper_table_name )
|
9
|
+
}
|
10
|
+
|
11
|
+
dir.down {
|
12
|
+
drop_table( helper_table_name )
|
13
|
+
drop_sync_trigger( origin_table, column )
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_helper_table(helper_table, column_name, column_type )
|
19
|
+
ActiveRecord::Base.connection.execute <<~CREATE_TABLE
|
20
|
+
CREATE TABLE #{helper_table} ( #{column_name} #{column_type} );
|
21
|
+
CREATE_TABLE
|
22
|
+
|
23
|
+
add_index( helper_table, column_name, unique: true )
|
24
|
+
end
|
25
|
+
|
26
|
+
def create_sync_trigger(table, column_name, helper_table_name)
|
27
|
+
trg_name = "#{table}_#{column_name}_trg"
|
28
|
+
ActiveRecord::Base.connection.execute <<~SYNC_TRIGGER
|
29
|
+
CREATE OR REPLACE FUNCTION #{trg_name}() RETURNS trigger AS $$
|
30
|
+
BEGIN
|
31
|
+
INSERT INTO #{helper_table_name} VALUES ( NEW.#{column_name} );
|
32
|
+
RETURN NEW;
|
33
|
+
END $$ LANGUAGE plpgSQL;
|
34
|
+
|
35
|
+
CREATE OR REPLACE TRIGGER #{trg_name} BEFORE INSERT ON #{table} FOR EACH ROW
|
36
|
+
EXECUTE FUNCTION #{trg_name}();
|
37
|
+
SYNC_TRIGGER
|
38
|
+
end
|
39
|
+
|
40
|
+
def drop_sync_trigger(table, column_name)
|
41
|
+
trg_name = "#{table}_#{column_name}_trg"
|
42
|
+
|
43
|
+
ActiveRecord::Base.connection.execute <<~SYNC_TRIGGER
|
44
|
+
DROP TRIGGER IF EXISTS #{trg_name} ON #{table};
|
45
|
+
SYNC_TRIGGER
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_column_type(origin_table, column)
|
49
|
+
ActiveRecord::Base.connection.schema_cache.columns_hash(origin_table.to_s)[column.to_s]&.sql_type
|
50
|
+
end
|
51
|
+
|
52
|
+
end if defined? ActiveRecord
|
53
|
+
|
54
|
+
ActiveRecord::Migration.include(Izolenta::ActiveRecordMigration) if defined? ActiveRecord
|
data/lib/izolenta/version.rb
CHANGED
data/lib/izolenta.rb
CHANGED
metadata
CHANGED
@@ -1,17 +1,58 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: izolenta
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- alekseyl
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
12
|
-
dependencies:
|
13
|
-
|
14
|
-
|
11
|
+
date: 2021-12-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: pg
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activerecord
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: ruby_jard
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Migration helpers for delegated uniqueness in Postgres
|
15
56
|
email:
|
16
57
|
- leshchuk@gmail.com
|
17
58
|
executables: []
|
@@ -19,20 +60,22 @@ extensions: []
|
|
19
60
|
extra_rdoc_files: []
|
20
61
|
files:
|
21
62
|
- ".gitignore"
|
22
|
-
- ".idea/inspectionProfiles/profiles_settings.xml"
|
23
|
-
- ".idea/workspace.xml"
|
24
63
|
- ".ruby-gemset"
|
25
64
|
- ".ruby-version"
|
26
65
|
- ".travis.yml"
|
27
66
|
- CHANGELOG.md
|
67
|
+
- Dockerfile
|
28
68
|
- Gemfile
|
69
|
+
- Gemfile.lock
|
29
70
|
- LICENSE.txt
|
30
71
|
- README.md
|
31
72
|
- Rakefile
|
32
73
|
- bin/console
|
33
74
|
- bin/setup
|
75
|
+
- docker-compose.yml
|
34
76
|
- izolenta.gemspec
|
35
77
|
- lib/izolenta.rb
|
78
|
+
- lib/izolenta/active_record_migration.rb
|
36
79
|
- lib/izolenta/version.rb
|
37
80
|
homepage: https://github.com/alekseyl/izolenta
|
38
81
|
licenses:
|
@@ -60,6 +103,5 @@ requirements: []
|
|
60
103
|
rubygems_version: 3.1.4
|
61
104
|
signing_key:
|
62
105
|
specification_version: 4
|
63
|
-
summary:
|
64
|
-
records with redis mutex
|
106
|
+
summary: Migration helpers for delegated uniqueness in Postgres
|
65
107
|
test_files: []
|
data/.idea/workspace.xml
DELETED
@@ -1,61 +0,0 @@
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
-
<project version="4">
|
3
|
-
<component name="ChangeListManager">
|
4
|
-
<list default="true" id="e09058d8-36f0-45ad-a8b0-398c410f2a78" name="Changes" comment="">
|
5
|
-
<change afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
|
6
|
-
<change afterPath="$PROJECT_DIR$/.idea/inspectionProfiles/profiles_settings.xml" afterDir="false" />
|
7
|
-
<change afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
8
|
-
<change afterPath="$PROJECT_DIR$/.ruby-gemset" afterDir="false" />
|
9
|
-
<change afterPath="$PROJECT_DIR$/.ruby-version" afterDir="false" />
|
10
|
-
<change afterPath="$PROJECT_DIR$/.travis.yml" afterDir="false" />
|
11
|
-
<change afterPath="$PROJECT_DIR$/CODE_OF_CONDUCT.md" afterDir="false" />
|
12
|
-
<change afterPath="$PROJECT_DIR$/Gemfile" afterDir="false" />
|
13
|
-
<change afterPath="$PROJECT_DIR$/LICENSE.txt" afterDir="false" />
|
14
|
-
<change afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
|
15
|
-
<change afterPath="$PROJECT_DIR$/Rakefile" afterDir="false" />
|
16
|
-
<change afterPath="$PROJECT_DIR$/bin/console" afterDir="false" />
|
17
|
-
<change afterPath="$PROJECT_DIR$/bin/setup" afterDir="false" />
|
18
|
-
<change afterPath="$PROJECT_DIR$/izolenta.gemspec" afterDir="false" />
|
19
|
-
<change afterPath="$PROJECT_DIR$/lib/izolenta.rb" afterDir="false" />
|
20
|
-
<change afterPath="$PROJECT_DIR$/lib/izolenta/version.rb" afterDir="false" />
|
21
|
-
<change afterPath="$PROJECT_DIR$/test/izolenta_test.rb" afterDir="false" />
|
22
|
-
<change afterPath="$PROJECT_DIR$/test/test_helper.rb" afterDir="false" />
|
23
|
-
</list>
|
24
|
-
<option name="SHOW_DIALOG" value="false" />
|
25
|
-
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
26
|
-
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
27
|
-
<option name="LAST_RESOLUTION" value="IGNORE" />
|
28
|
-
</component>
|
29
|
-
<component name="Git.Settings">
|
30
|
-
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
31
|
-
</component>
|
32
|
-
<component name="ProjectId" id="21KEQNtyETxN1YRFZmdsghXrmFy" />
|
33
|
-
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
|
34
|
-
<component name="ProjectViewState">
|
35
|
-
<option name="hideEmptyMiddlePackages" value="true" />
|
36
|
-
<option name="showLibraryContents" value="true" />
|
37
|
-
</component>
|
38
|
-
<component name="PropertiesComponent">
|
39
|
-
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
|
40
|
-
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
|
41
|
-
<property name="WebServerToolWindowFactoryState" value="false" />
|
42
|
-
<property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" />
|
43
|
-
<property name="nodejs_npm_path_reset_for_default_project" value="true" />
|
44
|
-
<property name="settings.editor.selected.configurable" value="org.jetbrains.plugins.ruby.settings.RubyActiveModuleSdkConfigurable" />
|
45
|
-
</component>
|
46
|
-
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
47
|
-
<component name="TaskManager">
|
48
|
-
<task active="true" id="Default" summary="Default task">
|
49
|
-
<changelist id="e09058d8-36f0-45ad-a8b0-398c410f2a78" name="Changes" comment="" />
|
50
|
-
<created>1637681361150</created>
|
51
|
-
<option name="number" value="Default" />
|
52
|
-
<option name="presentableId" value="Default" />
|
53
|
-
<updated>1637681361150</updated>
|
54
|
-
<workItem from="1637681363057" duration="114000" />
|
55
|
-
</task>
|
56
|
-
<servers />
|
57
|
-
</component>
|
58
|
-
<component name="TypeScriptGeneratedFilesManager">
|
59
|
-
<option name="version" value="3" />
|
60
|
-
</component>
|
61
|
-
</project>
|