izolenta 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a32fc530e0234650b1d5d368bdcdfb790d9802ea3fb4f5274680385d54c662e9
4
- data.tar.gz: f4d6e2639d3181cc83b1d07a828102e5497f4da45a8d72326e4200e968eac048
3
+ metadata.gz: 7209aebff713c525a2b8e8d5c2f501d9759f32407b3cb42a2f6b14422279d8d8
4
+ data.tar.gz: d19e4c2ca5203587e7d1786a3137dc71cf6a715b9f32ea81529f3c4fc3d9ca1c
5
5
  SHA512:
6
- metadata.gz: 353b26d474b46ab384e358adcbfec74c3951564858ca7becd858e1c42328589fc76d11b484a7303b0f52e2517ad9d25b87aa97464bd3082c33244afbefbebcac
7
- data.tar.gz: 54ddcaf0cfdbad2c298f9207e1e5f783441434d95e4325e1fd4e9a1c2147fdf4ec4fef0f11ad61caa57e0ec19d1b5b88ce8b2e474366c58b18a13d786ce2e1b9
6
+ metadata.gz: 868873584c72ff2fa9e03028916303a2de9d3f29223f2cfd33cf8b03147ece4a8d3fcb38c3befad464cad32175d39436b60dcd8428ffd0c3e1f19d9d257ffac1
7
+ data.tar.gz: a1f6ac0f5758d250a87e026df7ef41d09d78cdc5f50a0c017205f1c079b34fcdd46bdb6280b2fe5d5b99571c29c5ce4fac366e0a060b7aa2b0ea6835b7ca9584
data/.gitignore CHANGED
@@ -6,3 +6,4 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ /.idea/
data/CHANGELOG.md CHANGED
@@ -0,0 +1,3 @@
1
+ #0.0.2
2
+ - delegate_uniqueness helper is available as a migration method
3
+ - functionality worked and tested
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
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/izolenta`. To experiment with that code, run `bin/console` for an interactive prompt.
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
- TODO: Delete this and the text above, and describe your gem
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
- TODO: Write usage instructions here
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
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
46
+ ```bash
47
+ docker-compose build
30
48
 
31
- 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).
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/[USERNAME]/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).
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
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
@@ -0,0 +1,10 @@
1
+ version: "3.7"
2
+
3
+ services:
4
+ test:
5
+ build: .
6
+ image: izolenta
7
+ command: service postgresql start && rake test
8
+ volumes:
9
+ - '.:/app'
10
+
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{cybersquatting the izolenta name for later creation of short term uniqueness/isolation records with redis mutex}
10
- spec.description = %q{cybersquatting the izolenta name for later creation of short term uniqueness/isolation records with redis mutex}
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
@@ -1,3 +1,3 @@
1
1
  module Izolenta
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/lib/izolenta.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require "izolenta/version"
2
+ require 'izolenta/active_record_migration'
2
3
 
3
4
  module Izolenta
4
5
  class Error < StandardError; end
5
- # Your code goes here...
6
6
  end
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.1
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-11-23 00:00:00.000000000 Z
12
- dependencies: []
13
- description: cybersquatting the izolenta name for later creation of short term uniqueness/isolation
14
- records with redis mutex
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: cybersquatting the izolenta name for later creation of short term uniqueness/isolation
64
- records with redis mutex
106
+ summary: Migration helpers for delegated uniqueness in Postgres
65
107
  test_files: []
@@ -1,5 +0,0 @@
1
- <component name="InspectionProjectProfileManager">
2
- <settings>
3
- <option name="PROJECT_PROFILE" />
4
- </settings>
5
- </component>
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>