izolenta 0.0.2 → 0.0.5
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/.rubocop.yml +92 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +3 -1
- data/Gemfile.lock +28 -2
- data/README.md +20 -0
- data/Rakefile +3 -1
- data/izolenta.gemspec +11 -8
- data/lib/izolenta/active_record_migration.rb +78 -46
- data/lib/izolenta/version.rb +3 -1
- data/lib/izolenta.rb +3 -1
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c88c2f580d9d1d4e4f8fba35e5a865a18520c63aee5c24d6457aaa32fb6a2da8
|
4
|
+
data.tar.gz: 8b5315fb79d1dfc3a5a1186190f296848c81876a8bfeb5f29e897ce814b6e45d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6394a0583ce73f68e0bd50f1d35feb8c2dd5a74f4f6ae729d5e446291b7495098b4772d84039eb185254cbbc7f153e90910f0dc9d20edfe3057223bea1040f2e
|
7
|
+
data.tar.gz: 560940e4fa253be7315f351e9c757d73d995eafe7d8584ad08ffb6ec9b235aa8c8386f09b01f6d5a4244c499be2fae5e814580a920391abb20c3352df13a21eb
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
inherit_gem:
|
2
|
+
rubocop-shopify: rubocop.yml
|
3
|
+
#
|
4
|
+
#Style/SingleLineMethods:
|
5
|
+
# Description: 'Avoid single-line methods.'
|
6
|
+
# StyleGuide: '#no-single-line-methods'
|
7
|
+
# Enabled: false
|
8
|
+
# VersionAdded: '0.9'
|
9
|
+
# VersionChanged: '1.8'
|
10
|
+
# AllowIfMethodIsEmpty: true
|
11
|
+
#
|
12
|
+
#Style/AsciiComments:
|
13
|
+
# Description: 'Use only ascii symbols in comments.'
|
14
|
+
# StyleGuide: '#english-comments'
|
15
|
+
# Enabled: false
|
16
|
+
# VersionAdded: '0.9'
|
17
|
+
# VersionChanged: '1.21'
|
18
|
+
# AllowedChars:
|
19
|
+
# - ©
|
20
|
+
#
|
21
|
+
#Layout/LineLength:
|
22
|
+
# Description: 'Checks that line length does not exceed the configured limit.'
|
23
|
+
# StyleGuide: '#max-line-length'
|
24
|
+
# Enabled: true
|
25
|
+
# VersionAdded: '0.25'
|
26
|
+
# VersionChanged: '1.4'
|
27
|
+
# Max: 120
|
28
|
+
# # To make it possible to copy or click on URIs in the code, we allow lines
|
29
|
+
# # containing a URI to be longer than Max.
|
30
|
+
# AllowHeredoc: true
|
31
|
+
# AllowURI: true
|
32
|
+
# URISchemes:
|
33
|
+
# - http
|
34
|
+
# - https
|
35
|
+
# # The IgnoreCopDirectives option causes the LineLength rule to ignore cop
|
36
|
+
# # directives like '# rubocop: enable ...' when calculating a line's length.
|
37
|
+
# IgnoreCopDirectives: true
|
38
|
+
# # The AllowedPatterns option is a list of !ruby/regexp and/or string
|
39
|
+
# # elements. Strings will be converted to Regexp objects. A line that matches
|
40
|
+
# # any regular expression listed in this option will be ignored by LineLength.
|
41
|
+
# AllowedPatterns: []
|
42
|
+
# IgnoredPatterns: [] # deprecated
|
43
|
+
# Exclude:
|
44
|
+
# - "./test/**/**/*"
|
45
|
+
#
|
46
|
+
#Metrics/ClassLength:
|
47
|
+
# Description: 'Avoid classes longer than 100 lines of code.'
|
48
|
+
# Enabled: false
|
49
|
+
# VersionAdded: '0.25'
|
50
|
+
# VersionChanged: '0.87'
|
51
|
+
# CountComments: false # count full line comments?
|
52
|
+
# Max: 100
|
53
|
+
# CountAsOne: []
|
54
|
+
#
|
55
|
+
#Lint/MissingCopEnableDirective:
|
56
|
+
# Description: 'Checks for a `# rubocop:enable` after `# rubocop:disable`.'
|
57
|
+
# Enabled: true
|
58
|
+
# VersionAdded: '0.52'
|
59
|
+
# # Maximum number of consecutive lines the cop can be disabled for.
|
60
|
+
# # 0 allows only single-line disables
|
61
|
+
# # 1 would mean the maximum allowed is the following:
|
62
|
+
# # # rubocop:disable SomeCop
|
63
|
+
# # a = 1
|
64
|
+
# # # rubocop:enable SomeCop
|
65
|
+
# # .inf for any size
|
66
|
+
# MaximumRangeSize: .inf
|
67
|
+
#
|
68
|
+
#Style/MethodCallWithArgsParentheses:
|
69
|
+
# Enabled: true
|
70
|
+
# IgnoredMethods:
|
71
|
+
# - require
|
72
|
+
# - require_relative
|
73
|
+
# - require_dependency
|
74
|
+
# - yield
|
75
|
+
# - raise
|
76
|
+
# - puts
|
77
|
+
# Exclude:
|
78
|
+
# - "/**/Gemfile"
|
79
|
+
# - "./db/**/*"
|
80
|
+
#
|
81
|
+
#Layout/EmptyLinesAroundBlockBody:
|
82
|
+
# # its more documentation than code, so it should be readable and
|
83
|
+
# # there are huge amount of multiline description, looks nasty without spaces
|
84
|
+
# Exclude:
|
85
|
+
# - "./app_doc/**/*"
|
86
|
+
#
|
87
|
+
#Style/ClassAndModuleChildren:
|
88
|
+
# Enabled: false
|
89
|
+
#
|
90
|
+
#Lint/UnderscorePrefixedVariableName:
|
91
|
+
# Exclude:
|
92
|
+
# - "./test/**/**/*"
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
#0.0.5
|
2
|
+
- rubocop-shopify added to dev dependencies
|
3
|
+
- fixed ruby 3.0 incompatibility in delegate_uniqueness, now gem could be used with ruby 3+
|
4
|
+
|
5
|
+
#0.0.4
|
6
|
+
- removed 'OR REPLACE' in trigger definition, lowering Postgres version constraint
|
7
|
+
- trigger_condition added ( could replace partial uniq index )
|
8
|
+
|
9
|
+
#0.0.3
|
10
|
+
- wrapper_function options added, you can define function to convert to uniquely sortable types, for instance array to string
|
11
|
+
- moved all helper functions to internal module, just to keep things clear on the migrations
|
12
|
+
|
1
13
|
#0.0.2
|
2
14
|
- delegate_uniqueness helper is available as a migration method
|
3
15
|
- functionality worked and tested
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
izolenta (0.0.
|
4
|
+
izolenta (0.0.5)
|
5
5
|
pg
|
6
6
|
|
7
7
|
GEM
|
@@ -18,18 +18,41 @@ GEM
|
|
18
18
|
minitest (>= 5.1)
|
19
19
|
tzinfo (~> 2.0)
|
20
20
|
zeitwerk (~> 2.3)
|
21
|
+
ast (2.4.2)
|
21
22
|
byebug (11.1.3)
|
22
23
|
coderay (1.1.3)
|
23
24
|
concurrent-ruby (1.1.9)
|
24
25
|
i18n (1.8.11)
|
25
26
|
concurrent-ruby (~> 1.0)
|
27
|
+
json (2.6.2)
|
26
28
|
method_source (1.0.0)
|
27
29
|
minitest (5.14.4)
|
28
|
-
|
30
|
+
parallel (1.22.1)
|
31
|
+
parser (3.1.2.0)
|
32
|
+
ast (~> 2.4.1)
|
33
|
+
pg (1.4.1)
|
29
34
|
pry (0.13.1)
|
30
35
|
coderay (~> 1.1)
|
31
36
|
method_source (~> 1.0)
|
37
|
+
rainbow (3.1.1)
|
32
38
|
rake (12.3.3)
|
39
|
+
regexp_parser (2.5.0)
|
40
|
+
rexml (3.2.5)
|
41
|
+
rubocop (1.31.2)
|
42
|
+
json (~> 2.3)
|
43
|
+
parallel (~> 1.10)
|
44
|
+
parser (>= 3.1.0.0)
|
45
|
+
rainbow (>= 2.2.2, < 4.0)
|
46
|
+
regexp_parser (>= 1.8, < 3.0)
|
47
|
+
rexml (>= 3.2.5, < 4.0)
|
48
|
+
rubocop-ast (>= 1.18.0, < 2.0)
|
49
|
+
ruby-progressbar (~> 1.7)
|
50
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
51
|
+
rubocop-ast (1.18.0)
|
52
|
+
parser (>= 3.1.1.0)
|
53
|
+
rubocop-shopify (2.8.0)
|
54
|
+
rubocop (~> 1.31)
|
55
|
+
ruby-progressbar (1.11.0)
|
33
56
|
ruby_jard (0.3.1)
|
34
57
|
byebug (>= 9.1, < 12.0)
|
35
58
|
pry (~> 0.13.0)
|
@@ -37,6 +60,7 @@ GEM
|
|
37
60
|
tty-screen (0.8.1)
|
38
61
|
tzinfo (2.0.4)
|
39
62
|
concurrent-ruby (~> 1.0)
|
63
|
+
unicode-display_width (2.2.0)
|
40
64
|
zeitwerk (2.5.1)
|
41
65
|
|
42
66
|
PLATFORMS
|
@@ -47,6 +71,8 @@ DEPENDENCIES
|
|
47
71
|
izolenta!
|
48
72
|
minitest (~> 5.0)
|
49
73
|
rake (~> 12.0)
|
74
|
+
rubocop
|
75
|
+
rubocop-shopify
|
50
76
|
ruby_jard
|
51
77
|
|
52
78
|
BUNDLED WITH
|
data/README.md
CHANGED
@@ -39,6 +39,22 @@ class YourMigration < ActiveRecord::Migration[5.0]
|
|
39
39
|
delegate_uniqueness( :your_table_name, :column_name )
|
40
40
|
end
|
41
41
|
end
|
42
|
+
|
43
|
+
class WithWrapperFunctionMigration < ActiveRecord::Migration[5.0]
|
44
|
+
# define some where before 'type_conversion_function' to use it later
|
45
|
+
def change
|
46
|
+
delegate_uniqueness( :your_table_name, :column_name, wrapper_function: 'type_conversion_function' )
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
class WithWrapperFunctionMigration < ActiveRecord::Migration[5.0]
|
52
|
+
# apply trigger condition for partial uniqueness
|
53
|
+
def change
|
54
|
+
delegate_uniqueness( :your_table_name, :column_name, trigger_condition: 'NEW.type IS NOT NULL' )
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
42
58
|
```
|
43
59
|
|
44
60
|
## Development
|
@@ -50,6 +66,10 @@ docker-compose run test /bin/bash
|
|
50
66
|
> service postgresql start && rake test
|
51
67
|
```
|
52
68
|
|
69
|
+
## Future Features
|
70
|
+
- Sequel migration helpers
|
71
|
+
- Existing data sync
|
72
|
+
|
53
73
|
## Contributing
|
54
74
|
|
55
75
|
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).
|
data/Rakefile
CHANGED
data/izolenta.gemspec
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/izolenta/version"
|
2
4
|
|
3
5
|
Gem::Specification.new do |spec|
|
4
6
|
spec.name = "izolenta"
|
@@ -6,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
6
8
|
spec.authors = ["alekseyl"]
|
7
9
|
spec.email = ["leshchuk@gmail.com"]
|
8
10
|
|
9
|
-
spec.summary =
|
10
|
-
spec.description =
|
11
|
+
spec.summary = "Migration helpers for delegated uniqueness in Postgres"
|
12
|
+
spec.description = "Migration helpers for delegated uniqueness in Postgres"
|
11
13
|
spec.homepage = "https://github.com/alekseyl/izolenta"
|
12
14
|
spec.license = "MIT"
|
13
15
|
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
@@ -20,14 +22,15 @@ Gem::Specification.new do |spec|
|
|
20
22
|
|
21
23
|
# Specify which files should be added to the gem when it is released.
|
22
24
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
-
spec.files
|
24
|
-
|
25
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
26
|
+
%x(git ls-files -z).split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
25
27
|
end
|
26
28
|
spec.bindir = "exe"
|
27
29
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
30
|
spec.require_paths = ["lib"]
|
29
31
|
|
30
|
-
spec.add_dependency
|
31
|
-
spec.add_development_dependency
|
32
|
-
spec.add_development_dependency
|
32
|
+
spec.add_dependency("pg")
|
33
|
+
spec.add_development_dependency("activerecord", ">= 5")
|
34
|
+
spec.add_development_dependency("rubocop-shopify")
|
35
|
+
spec.add_development_dependency("ruby_jard")
|
33
36
|
end
|
@@ -1,54 +1,86 @@
|
|
1
|
-
|
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
|
1
|
+
# frozen_string_literal: true
|
17
2
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
3
|
+
if defined? ActiveRecord
|
4
|
+
module Izolenta
|
5
|
+
module ActiveRecordMigration
|
6
|
+
# options:
|
7
|
+
# wrapper_function: 'some_func' # some_func should be defined prior
|
8
|
+
def delegate_uniqueness(origin_table, column, **options)
|
9
|
+
helper_table_name = "#{column}_#{origin_table}_uniqs"
|
22
10
|
|
23
|
-
|
24
|
-
|
11
|
+
reversible do |dir|
|
12
|
+
dir.up do
|
13
|
+
Helpers.create_helper_table(helper_table_name, column,
|
14
|
+
Helpers.get_new_column_type(origin_table, column, options))
|
15
|
+
add_index(helper_table_name, column, unique: true)
|
25
16
|
|
26
|
-
|
27
|
-
|
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
|
17
|
+
Helpers.create_sync_trigger(origin_table, column, helper_table_name, options)
|
18
|
+
end
|
39
19
|
|
40
|
-
|
41
|
-
|
20
|
+
dir.down do
|
21
|
+
drop_table(helper_table_name)
|
22
|
+
Helpers.drop_sync_trigger(origin_table, column)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
42
26
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
27
|
+
# helpers na
|
28
|
+
module Helpers
|
29
|
+
class << self
|
30
|
+
def create_helper_table(helper_table, column_name, column_type)
|
31
|
+
ActiveRecord::Base.connection.execute("CREATE TABLE #{helper_table} ( #{column_name} #{column_type} );")
|
32
|
+
end
|
47
33
|
|
48
|
-
|
49
|
-
|
50
|
-
|
34
|
+
def create_sync_trigger(table, column_name, helper_table_name, options)
|
35
|
+
trg_name = "#{table}_#{column_name}_trg"
|
36
|
+
insert_value = if options[:wrapper_function]
|
37
|
+
"#{options[:wrapper_function]}(NEW.#{column_name})"
|
38
|
+
else
|
39
|
+
"NEW.#{column_name}"
|
40
|
+
end
|
41
|
+
|
42
|
+
trigger_condition = "WHEN( #{options[:trigger_condition]} )" if options[:trigger_condition]
|
43
|
+
|
44
|
+
ActiveRecord::Base.connection.execute(<<~SYNC_TRIGGER)
|
45
|
+
CREATE OR REPLACE FUNCTION #{trg_name}() RETURNS trigger AS $$
|
46
|
+
BEGIN#{" "}
|
47
|
+
INSERT INTO #{helper_table_name} VALUES ( #{insert_value} );
|
48
|
+
RETURN NEW;
|
49
|
+
END $$ LANGUAGE plpgSQL;
|
51
50
|
|
52
|
-
|
51
|
+
CREATE TRIGGER #{trg_name} BEFORE INSERT ON #{table}#{" "}
|
52
|
+
FOR EACH ROW
|
53
|
+
#{trigger_condition}
|
54
|
+
EXECUTE FUNCTION #{trg_name}();
|
55
|
+
SYNC_TRIGGER
|
56
|
+
end
|
57
|
+
|
58
|
+
def drop_sync_trigger(table, column_name)
|
59
|
+
trg_name = "#{table}_#{column_name}_trg"
|
60
|
+
|
61
|
+
ActiveRecord::Base.connection.execute(<<~SYNC_TRIGGER)
|
62
|
+
DROP TRIGGER IF EXISTS #{trg_name} ON #{table};
|
63
|
+
SYNC_TRIGGER
|
64
|
+
end
|
65
|
+
|
66
|
+
def get_new_column_type(origin_table, column, wrapper_function: nil, **)
|
67
|
+
wrapper_function ? get_function_type(wrapper_function) : get_column_type(origin_table, column)
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_column_type(origin_table, column)
|
71
|
+
ActiveRecord::Base.connection.schema_cache.columns_hash(origin_table.to_s)[column.to_s]&.sql_type
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_function_type(wrapper_function)
|
75
|
+
ActiveRecord::Base
|
76
|
+
.connection
|
77
|
+
.execute("SELECT typname FROM pg_type WHERE oid=(SELECT prorettype FROM pg_proc WHERE proname ='#{wrapper_function}')") # rubocop:disable Layout/LineLength
|
78
|
+
.first["typname"]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
53
85
|
|
54
|
-
ActiveRecord::Migration.include(Izolenta::ActiveRecordMigration) if defined? ActiveRecord
|
86
|
+
ActiveRecord::Migration.include(Izolenta::ActiveRecordMigration) if defined? ActiveRecord
|
data/lib/izolenta/version.rb
CHANGED
data/lib/izolenta.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
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.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- alekseyl
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-07-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop-shopify
|
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'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: ruby_jard
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -60,6 +74,7 @@ extensions: []
|
|
60
74
|
extra_rdoc_files: []
|
61
75
|
files:
|
62
76
|
- ".gitignore"
|
77
|
+
- ".rubocop.yml"
|
63
78
|
- ".ruby-gemset"
|
64
79
|
- ".ruby-version"
|
65
80
|
- ".travis.yml"
|