pg_tags_on 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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +9 -0
  5. data/CHANGELOG.md +3 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +6 -0
  8. data/Gemfile.lock +91 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +195 -0
  11. data/Rakefile +16 -0
  12. data/bin/console +16 -0
  13. data/bin/setup +8 -0
  14. data/lib/pg_tags_on/active_record/arel.rb +66 -0
  15. data/lib/pg_tags_on/active_record/base.rb +41 -0
  16. data/lib/pg_tags_on/benchmark/benchmark.rb +52 -0
  17. data/lib/pg_tags_on/predicate_handler/array_integer_handler.rb +9 -0
  18. data/lib/pg_tags_on/predicate_handler/array_jsonb_handler.rb +31 -0
  19. data/lib/pg_tags_on/predicate_handler/array_jsonb_with_attrs_handler.rb +41 -0
  20. data/lib/pg_tags_on/predicate_handler/array_string_handler.rb +9 -0
  21. data/lib/pg_tags_on/predicate_handler/array_text_handler.rb +9 -0
  22. data/lib/pg_tags_on/predicate_handler/base_handler.rb +89 -0
  23. data/lib/pg_tags_on/predicate_handler.rb +64 -0
  24. data/lib/pg_tags_on/repositories/array_jsonb_repository.rb +88 -0
  25. data/lib/pg_tags_on/repositories/array_repository.rb +103 -0
  26. data/lib/pg_tags_on/repositories/base_repository.rb +44 -0
  27. data/lib/pg_tags_on/repository.rb +59 -0
  28. data/lib/pg_tags_on/tag.rb +31 -0
  29. data/lib/pg_tags_on/tags_query.rb +27 -0
  30. data/lib/pg_tags_on/validations/validator.rb +43 -0
  31. data/lib/pg_tags_on/version.rb +5 -0
  32. data/lib/pg_tags_on.rb +56 -0
  33. data/pg_tags_on.gemspec +38 -0
  34. data/spec/array_integers/records_spec.rb +47 -0
  35. data/spec/array_integers/tag_ops_spec.rb +65 -0
  36. data/spec/array_integers/taggings_spec.rb +27 -0
  37. data/spec/array_integers/tags_spec.rb +53 -0
  38. data/spec/array_jsonb/records_spec.rb +89 -0
  39. data/spec/array_jsonb/tag_ops_spec.rb +115 -0
  40. data/spec/array_jsonb/taggings_spec.rb +27 -0
  41. data/spec/array_jsonb/tags_spec.rb +41 -0
  42. data/spec/array_strings/records_spec.rb +61 -0
  43. data/spec/array_strings/tag_ops_spec.rb +65 -0
  44. data/spec/array_strings/taggings_spec.rb +27 -0
  45. data/spec/array_strings/tags_spec.rb +54 -0
  46. data/spec/config/database.yml +6 -0
  47. data/spec/configuration_spec.rb +48 -0
  48. data/spec/helpers/database_helpers.rb +46 -0
  49. data/spec/spec_helper.rb +39 -0
  50. data/spec/support/factory.rb +47 -0
  51. data/spec/tags_query_spec.rb +31 -0
  52. data/spec/validator_spec.rb +40 -0
  53. data/tasks/benchmark.rake +58 -0
  54. metadata +260 -0
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'pg_tags_on/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'pg_tags_on'
9
+ spec.version = PgTagsOn::VERSION
10
+ spec.authors = ['Catalin Marinescu']
11
+ spec.email = ['catalin.marinescu@gmail.com']
12
+ spec.summary = 'Manage tags stored in Postgresql column.'
13
+ spec.description = 'A gem that makes working with tags stored in a Postgresql column easy.Support for array of string, integer and jsonb values.'
14
+ spec.homepage = 'http://github.com/cata-m/pg_tags_on'
15
+ spec.license = 'MIT'
16
+ spec.metadata['homepage_uri'] = spec.homepage
17
+ spec.metadata['source_code_uri'] = 'http://github.com/cata-m/pg_tags_on'
18
+ spec.metadata['changelog_uri'] = 'http://github.com/cata-m/pg_tags_on/blob/master/CHANGELOG.md'
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = `git ls-files -z`.split("\x0")
23
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
24
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
25
+ spec.require_paths = ['lib']
26
+
27
+ spec.add_dependency 'activerecord', '~> 6.0'
28
+ spec.add_dependency 'activesupport', '~> 6.0'
29
+
30
+ spec.add_development_dependency 'bundler', '~> 2.0'
31
+ spec.add_development_dependency 'faker', '~> 2.10'
32
+ spec.add_development_dependency 'pg', '~> 1.2'
33
+ spec.add_development_dependency 'pry', '~> 0.12'
34
+ spec.add_development_dependency 'rake', '~> 13.0'
35
+ spec.add_development_dependency 'rspec', '~> 3.9'
36
+ spec.add_development_dependency 'rubocop', '~> 0.80'
37
+ spec.add_development_dependency 'simplecov', '~> 0.18'
38
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe 'ArrayIntegers::Records' do
4
+ before(:all) do
5
+ @column = :tags_int # defined as instance var as it is used in before_all callback
6
+ Entity.pg_tags_on @column
7
+ truncate && Factory.array_integers
8
+ end
9
+
10
+ let(:column) { @column }
11
+ let(:ref) { %("#{Entity.table_name}"."#{column}") }
12
+ let(:relation) { Entity.send(column) }
13
+
14
+ it 'find records by tag' do
15
+ rel = Entity.where(column => Tags.one(2))
16
+
17
+ expect(rel.to_sql).to include(%(#{ref} @> '{2}'))
18
+ expect(rel.count).to be_eql(2)
19
+ end
20
+
21
+ it 'find records with exact same tags' do
22
+ rel = Entity.where(column => Tags.eq(%w[3 2]))
23
+
24
+ expect(rel.count).to be_eql(1)
25
+ end
26
+
27
+ it 'find records with all tags' do
28
+ rel = Entity.where(column => Tags.all([1, 2, 3]))
29
+
30
+ expect(rel.to_sql).to include(%(#{ref} @> '{1,2,3}'))
31
+ expect(rel.count).to be_eql(1)
32
+ end
33
+
34
+ it 'find records with any tag' do
35
+ rel = Entity.where(column => Tags.any([1, 2, 3]))
36
+
37
+ expect(rel.to_sql).to include(%(#{ref} && '{1,2,3}'))
38
+ expect(rel.count).to be_eql(2)
39
+ end
40
+
41
+ it 'find records with tags included in tag list' do
42
+ rel = Entity.where(column => Tags.in([1, 2, 3]))
43
+
44
+ expect(rel.to_sql).to include(%(#{ref} <@ '{1,2,3}'))
45
+ expect(rel.count).to be_eql(2)
46
+ end
47
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe 'ArrayIntegers::TagOps' do
4
+ before(:all) do
5
+ @column = :tags_int # defined as instance var as it is used in before_all callback
6
+ Entity.pg_tags_on @column
7
+ end
8
+
9
+ before do
10
+ truncate && Factory.array_integers
11
+ end
12
+
13
+ let(:column) { @column }
14
+ let(:ref) { %("#{Entity.table_name}"."#{column}") }
15
+ let(:relation) { Entity.send(column) }
16
+
17
+ context 'create' do
18
+ it 'create tag' do
19
+ relation.create(5)
20
+
21
+ Entity.all.each do |entity|
22
+ expect(entity.send(column)).to include(5)
23
+ end
24
+ end
25
+
26
+ it 'create tag for filtered records' do
27
+ Entity.where(attr: 'test2').send(column).create(6)
28
+
29
+ expect(Entity.find_by_attr('test1').send(column)).not_to include(6)
30
+ expect(Entity.find_by_attr('test2').send(column)).to include(6)
31
+ end
32
+ end
33
+
34
+ context 'update' do
35
+ it 'update tag' do
36
+ relation.update(2, 22)
37
+
38
+ count = Entity.where(column => Tags.all(22)).count
39
+ expect(count).to be_eql(2)
40
+ end
41
+
42
+ it 'update tag for filtered records' do
43
+ Entity.where(attr: 'test2').send(column).update(3, 33)
44
+
45
+ count = Entity.where(column => Tags.all(33)).count
46
+ expect(count).to be_eql(1)
47
+ end
48
+ end
49
+
50
+ context 'delete' do
51
+ it 'delete tag' do
52
+ relation.delete(2)
53
+
54
+ tags = relation.all.pluck(:name)
55
+ expect(tags).not_to include(2)
56
+ end
57
+
58
+ it 'delete tag for filtered records' do
59
+ Entity.where(attr: 'test2').send(column).delete(3)
60
+
61
+ expect(Entity.find_by_attr('test1').send(column)).to include(3)
62
+ expect(Entity.find_by_attr('test2').send(column)).not_to include(3)
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe 'ArrayIntegers::Taggings' do
4
+ before(:all) do
5
+ @column = :tags_int # defined as instance var as it is used in before_all callback
6
+ Entity.pg_tags_on @column
7
+ truncate && Factory.array_integers
8
+ end
9
+
10
+ let(:column) { @column }
11
+ let(:ref) { %("#{Entity.table_name}"."#{column}") }
12
+ let(:relation) { Entity.send(column) }
13
+
14
+ it 'find all taggings' do
15
+ taggings = relation.taggings.order('name')
16
+
17
+ expect(taggings.size).to be_eql(6)
18
+ expect(taggings.map(&:name)).to be_eql([1, 2, 2, 3, 3, 4])
19
+ end
20
+
21
+ it 'find all taggings for filtered records' do
22
+ taggings = Entity.where(attr: 'test2').send(column).taggings.order('name')
23
+
24
+ expect(taggings.size).to be_eql(2)
25
+ expect(taggings.map(&:name)).to be_eql([2, 3])
26
+ end
27
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe 'ArrayIntegers::Tags' do
4
+ before(:all) do
5
+ @column = :tags_int # defined as instance var as it is used in before_all callback
6
+ Entity.pg_tags_on @column
7
+ truncate && Factory.array_integers
8
+ end
9
+
10
+ let(:column) { @column }
11
+ let(:ref) { %("#{Entity.table_name}"."#{column}") }
12
+ let(:relation) { Entity.send(column) }
13
+
14
+ it 'find all tags' do
15
+ tags = relation.all.order('name')
16
+ expect(tags.size).to be_eql(4)
17
+ expect(tags.map(&:name)).to be_eql([1, 2, 3, 4])
18
+ end
19
+
20
+ it 'find all tags for filtered records' do
21
+ tags = Entity.where(attr: 'test2').send(column).all
22
+
23
+ expect(tags.size).to be_eql(2)
24
+ expect(tags.map(&:name)).to be_eql([2, 3])
25
+ end
26
+
27
+ it 'find all tags with counts' do
28
+ tag = relation.all_with_counts.first
29
+
30
+ expect(tag.count).to be_eql(1)
31
+ end
32
+
33
+ it 'count tags' do
34
+ expect(relation.count).to be_eql(4)
35
+ end
36
+
37
+ it 'count tags for filtered records' do
38
+ expect(Entity.where(attr: 'test2').send(column).count).to be_eql(2)
39
+ end
40
+
41
+ context 'cast strings to integers' do
42
+ before do
43
+ Entity.create(attr: 'int1', column => %w[1])
44
+ end
45
+
46
+ it 'find tags' do
47
+ tags = relation.all.where(name: '1')
48
+
49
+ expect(tags.size).to be_eql(1)
50
+ expect(tags.map(&:name)).to be_eql([1])
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe 'ArrayJsonb::Records' do
4
+ before(:all) do
5
+ @column = :tags_jsonb # defined as instance var as it is used in before_all callback
6
+ end
7
+
8
+ let(:column) { @column }
9
+ let(:ref) { %("#{Entity.table_name}"."#{column}") }
10
+ let(:relation) { Entity.send(column) }
11
+
12
+ context 'objects have only one key' do
13
+ before(:all) do
14
+ Entity.pg_tags_on @column, key: :name
15
+ truncate && Factory.array_jsonb
16
+ end
17
+
18
+ it 'find records by tag' do
19
+ rel = Entity.where(column => Tags.one('b'))
20
+ expected_sql = %(#{ref} @> #{encode_json([{ name: 'b' }])})
21
+
22
+ expect(rel.to_sql).to include(expected_sql)
23
+ expect(rel.count).to be_eql(2)
24
+ end
25
+
26
+ it 'find records with exact same tags' do
27
+ rel = Entity.where(column => Tags.eq(%w[b c]))
28
+
29
+ expect(rel.count).to be_eql(1)
30
+ end
31
+
32
+ it 'find records with all tags' do
33
+ rel = Entity.where(column => Tags.all(%w[a b c]))
34
+ expected_sql = %(#{ref} @> #{encode_json([{ name: 'a' }, { name: 'b' }, { name: 'c' }])})
35
+
36
+ expect(rel.to_sql).to include(expected_sql)
37
+ expect(rel.count).to be_eql(1)
38
+ end
39
+
40
+ it 'find records with any tags' do
41
+ rel = Entity.where(column => Tags.any(%w[a b c]))
42
+ expected_sql = %(#{ref} && #{encode_json([{ name: 'a' }, { name: 'b' }, { name: 'c' }])})
43
+
44
+ expect(rel.to_sql).to include(expected_sql)
45
+ expect(rel.count).to be_eql(2)
46
+ end
47
+
48
+ it 'find records with tags included in tag list' do
49
+ rel = Entity.where(column => Tags.in(%w[a b c]))
50
+ expected_sql = %(#{ref} <@ #{encode_json([{ name: 'a' }, { name: 'b' }, { name: 'c' }])})
51
+
52
+ expect(rel.to_sql).to include(expected_sql)
53
+ expect(rel.count).to be_eql(2)
54
+ end
55
+ end
56
+
57
+ context 'objects have multiple keys' do
58
+ before(:all) do
59
+ Entity.pg_tags_on @column, key: :name, has_attributes: true
60
+ truncate && Factory.array_jsonb_with_attrs
61
+ end
62
+
63
+ it 'find records by tag' do
64
+ rel = Entity.where(column => Tags.one('b'))
65
+ expect(rel.count).to be_eql(2)
66
+ end
67
+
68
+ it 'find records with exact same tags' do
69
+ rel = Entity.where(column => Tags.eq(%w[b c]))
70
+
71
+ expect(rel.count).to be_eql(1)
72
+ end
73
+
74
+ it 'find records with all tags' do
75
+ rel = Entity.where(column => Tags.all(%w[a b c]))
76
+ expect(rel.count).to be_eql(1)
77
+ end
78
+
79
+ it 'find records with any tags' do
80
+ rel = Entity.where(column => Tags.any(%w[a b c]))
81
+ expect(rel.count).to be_eql(2)
82
+ end
83
+
84
+ it 'find records with tags included in tag list' do
85
+ rel = Entity.where(column => Tags.in(%w[a b c]))
86
+ expect(rel.count).to be_eql(2)
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe 'ArrayJsonb::TagOps' do
4
+ before(:all) do
5
+ @column = :tags_jsonb
6
+ end
7
+
8
+ let(:column) { @column }
9
+ let(:ref) { %("#{Entity.table_name}"."#{column}") }
10
+ let(:relation) { Entity.send(column) }
11
+
12
+ context 'objects has only one key' do
13
+ before(:all) do
14
+ Entity.pg_tags_on @column, key: :name
15
+ truncate && Factory.array_jsonb
16
+ end
17
+
18
+ context 'create' do
19
+ it 'create tag' do
20
+ relation.create('new-tag1')
21
+
22
+ Entity.all.each do |entity|
23
+ expect(entity.send(column)).to include({ 'name' => 'new-tag1' })
24
+ end
25
+ end
26
+
27
+ it 'create tag for filtered records' do
28
+ Entity.where(attr: 'test2').send(column).create('new-tag2')
29
+
30
+ expect(Entity.find_by_attr('test1').send(column)).not_to include({ 'name' => 'new-tag2' })
31
+ expect(Entity.find_by_attr('test2').send(column)).to include({ 'name' => 'new-tag2' })
32
+ end
33
+ end
34
+
35
+ context 'update' do
36
+ it 'update tag' do
37
+ relation.update('b', 'updated-b')
38
+
39
+ count = Entity.where(column => Tags.all('updated-b')).count
40
+ expect(count).to be_eql(2)
41
+ end
42
+
43
+ it 'update tag for filtered records' do
44
+ Entity.where(attr: 'test2').send(column).update('c', 'updated-c')
45
+
46
+ count = Entity.where(column => Tags.all('updated-c')).count
47
+ expect(count).to be_eql(1)
48
+ end
49
+ end
50
+
51
+ context 'delete' do
52
+ it 'delete tag' do
53
+ relation.delete('b')
54
+
55
+ tags = relation.all.pluck(:name)
56
+ expect(tags).not_to include('b')
57
+ end
58
+
59
+ it 'delete tag for filtered records' do
60
+ Entity.where(attr: 'test2').send(column).delete('c')
61
+
62
+ expect(Entity.where(attr: 'test1').send(column).all.pluck(:name)).to include('c')
63
+ expect(Entity.where(attr: 'test2').send(column).all.pluck(:name)).not_to include('c')
64
+ end
65
+ end
66
+ end
67
+
68
+ context 'objects have multiple keys' do
69
+ before(:all) do
70
+ Entity.pg_tags_on @column, key: :name, has_attributes: true
71
+ end
72
+
73
+ before(:each) do
74
+ truncate && Factory.array_jsonb_with_attrs
75
+ end
76
+
77
+ context 'update' do
78
+ it 'update tag' do
79
+ relation.update('b', 'updated-b')
80
+
81
+ rel = Entity.where(column => Tags.all('updated-b'))
82
+ expect(rel.count).to be_eql(2)
83
+ rel.all.each do |record|
84
+ expect(record.tags_jsonb).to include({ 'meta' => 'b', 'name' => 'updated-b' })
85
+ end
86
+ end
87
+
88
+ it 'update tag for filtered records' do
89
+ Entity.where(attr: 'test2').send(column).update('c', 'updated-c')
90
+
91
+ rel = Entity.where(column => Tags.all('updated-c'))
92
+ expect(rel.count).to be_eql(1)
93
+ rel.all.each do |record|
94
+ expect(record.tags_jsonb).to include({ 'meta' => 'c', 'name' => 'updated-c' })
95
+ end
96
+ end
97
+ end
98
+
99
+ context 'delete' do
100
+ it 'delete tag' do
101
+ relation.delete('b')
102
+
103
+ tags = relation.all.pluck(:name)
104
+ expect(tags).not_to include('b')
105
+ end
106
+
107
+ it 'delete tag for filtered records' do
108
+ Entity.where(attr: 'test2').send(column).delete('c')
109
+
110
+ expect(Entity.where(attr: 'test1').send(column).all.pluck(:name)).to include('c')
111
+ expect(Entity.where(attr: 'test2').send(column).all.pluck(:name)).not_to include('c')
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe 'ArrayJsonb::Taggings' do
4
+ before(:all) do
5
+ @column = :tags_jsonb
6
+ Entity.pg_tags_on @column, key: :name
7
+ truncate && Factory.array_jsonb
8
+ end
9
+
10
+ let(:column) { @column }
11
+ let(:ref) { %("#{Entity.table_name}"."#{column}") }
12
+ let(:relation) { Entity.send(column) }
13
+
14
+ it 'find all taggings' do
15
+ taggings = relation.taggings.order('name')
16
+
17
+ expect(taggings.size).to be_eql(6)
18
+ expect(taggings.map(&:name)).to be_eql(%w[a b b c c d])
19
+ end
20
+
21
+ it 'find all taggings for filtered records' do
22
+ taggings = Entity.where(attr: 'test2').send(column).taggings.order('name')
23
+
24
+ expect(taggings.size).to be_eql(2)
25
+ expect(taggings.map(&:name)).to be_eql(%w[b c])
26
+ end
27
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe 'ArrayJsonb::Tags' do
4
+ before(:all) do
5
+ @column = :tags_jsonb
6
+ Entity.pg_tags_on @column, key: :name
7
+ truncate && Factory.array_jsonb
8
+ end
9
+
10
+ let(:column) { @column }
11
+ let(:ref) { %("#{Entity.table_name}"."#{column}") }
12
+ let(:relation) { Entity.send(column) }
13
+
14
+ it 'find all tags' do
15
+ tags = relation.all.order('name')
16
+
17
+ expect(tags.size).to be_eql(4)
18
+ expect(tags.map(&:name)).to be_eql(%w[a b c d])
19
+ end
20
+
21
+ it 'find all tags for filtered records' do
22
+ tags = Entity.where(attr: 'test2').send(column).all
23
+
24
+ expect(tags.size).to be_eql(2)
25
+ expect(tags.map(&:name)).to be_eql(%w[b c])
26
+ end
27
+
28
+ it 'find all tags with counts' do
29
+ tag = relation.all_with_counts.first
30
+
31
+ expect(tag.count).to be_eql(1)
32
+ end
33
+
34
+ it 'count tags' do
35
+ expect(relation.count).to be_eql(4)
36
+ end
37
+
38
+ it 'count tags for filtered records' do
39
+ expect(Entity.where(attr: 'test2').send(column).count).to be_eql(2)
40
+ end
41
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe 'ArrayStrings::Records' do
4
+ before(:all) do
5
+ @column = :tags_str # defined as instance var as it is used in before_all callback
6
+ Entity.pg_tags_on @column
7
+ truncate && Factory.array_strings
8
+ end
9
+
10
+ let(:column) { @column }
11
+ let(:ref) { %("#{Entity.table_name}"."#{column}") }
12
+ let(:relation) { Entity.send(column) }
13
+
14
+ it 'find records by tag' do
15
+ rel = Entity.where(column => Tags.one('b'))
16
+
17
+ expect(rel.to_sql).to include(%(#{ref} @> '{b}'))
18
+ expect(rel.count).to be_eql(2)
19
+ end
20
+
21
+ it 'find records without tag' do
22
+ rel = Entity.where.not(column => Tags.one('b'))
23
+
24
+ expect(rel.to_sql).to include(%(NOT (#{ref} @> '{b}')))
25
+ expect(rel.count).to be_eql(1)
26
+ end
27
+
28
+ it 'find records with exact same tags' do
29
+ rel = Entity.where(column => Tags.eq(%w[c b]))
30
+
31
+ expect(rel.count).to be_eql(1)
32
+ end
33
+
34
+ it 'find records with all tags' do
35
+ rel = Entity.where(column => Tags.all(%w[a b c]))
36
+
37
+ expect(rel.to_sql).to include(%(#{ref} @> '{a,b,c}'))
38
+ expect(rel.count).to be_eql(1)
39
+ end
40
+
41
+ it 'find records without tags' do
42
+ rel = Entity.where.not(column => Tags.all(%w[a b]))
43
+
44
+ expect(rel.to_sql).to include(%(NOT (#{ref} @> '{a,b}')))
45
+ expect(rel.count).to be_eql(2)
46
+ end
47
+
48
+ it 'find records with any tag' do
49
+ rel = Entity.where(column => Tags.any(%w[a b c]))
50
+
51
+ expect(rel.to_sql).to include(%(#{ref} && '{a,b,c}'))
52
+ expect(rel.count).to be_eql(2)
53
+ end
54
+
55
+ it 'find records with tags included in tag list' do
56
+ rel = Entity.where(column => Tags.in(%w[a b c]))
57
+
58
+ expect(rel.to_sql).to include(%(#{ref} <@ '{a,b,c}'))
59
+ expect(rel.count).to be_eql(2)
60
+ end
61
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe 'ArrayStrings::TagOps' do
4
+ before(:all) do
5
+ @column = :tags_str # defined as instance var as it is used in before_all callback
6
+ Entity.pg_tags_on @column
7
+ end
8
+
9
+ before do
10
+ truncate && Factory.array_strings
11
+ end
12
+
13
+ let(:column) { @column }
14
+ let(:ref) { %("#{Entity.table_name}"."#{column}") }
15
+ let(:relation) { Entity.send(column) }
16
+
17
+ context 'create' do
18
+ it 'create tag' do
19
+ relation.create('new-tag1')
20
+
21
+ Entity.all.each do |entity|
22
+ expect(entity.send(column)).to include('new-tag1')
23
+ end
24
+ end
25
+
26
+ it 'create tag for filtered records' do
27
+ Entity.where(attr: 'test2').send(column).create('new-tag2')
28
+
29
+ expect(Entity.find_by_attr('test1').send(column)).not_to include('new-tag2')
30
+ expect(Entity.find_by_attr('test2').send(column)).to include('new-tag2')
31
+ end
32
+ end
33
+
34
+ context 'update' do
35
+ it 'update tag' do
36
+ relation.update('b', 'updated-b')
37
+
38
+ count = Entity.where(column => Tags.all('updated-b')).count
39
+ expect(count).to be_eql(2)
40
+ end
41
+
42
+ it 'update tag for filtered records' do
43
+ Entity.where(attr: 'test2').send(column).update('c', 'updated-c')
44
+
45
+ count = Entity.where(column => Tags.all('updated-c')).count
46
+ expect(count).to be_eql(1)
47
+ end
48
+ end
49
+
50
+ context 'delete' do
51
+ it 'delete tag' do
52
+ relation.delete('b')
53
+
54
+ tags = relation.all.pluck(:name)
55
+ expect(tags).not_to include('b')
56
+ end
57
+
58
+ it 'delete tag for filtered records' do
59
+ Entity.where(attr: 'test2').send(column).delete('c')
60
+
61
+ expect(Entity.find_by_attr('test1').send(column)).to include('c')
62
+ expect(Entity.find_by_attr('test2').send(column)).not_to include('c')
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe 'ArrayStrings::Taggings' do
4
+ before(:all) do
5
+ @column = :tags_str # defined as instance var as it is used in before_all callback
6
+ Entity.pg_tags_on @column
7
+ truncate && Factory.array_strings
8
+ end
9
+
10
+ let(:column) { @column }
11
+ let(:ref) { %("#{Entity.table_name}"."#{column}") }
12
+ let(:relation) { Entity.send(column) }
13
+
14
+ it 'find all taggings' do
15
+ taggings = relation.taggings.order('name')
16
+
17
+ expect(taggings.size).to be_eql(6)
18
+ expect(taggings.map(&:name)).to be_eql(%w[a b b c c d])
19
+ end
20
+
21
+ it 'find all taggings for filtered records' do
22
+ taggings = Entity.where(attr: 'test2').send(column).taggings.order('name')
23
+
24
+ expect(taggings.size).to be_eql(2)
25
+ expect(taggings.map(&:name)).to be_eql(%w[b c])
26
+ end
27
+ end