pg_tags_on 0.1.3 → 0.1.4

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.
@@ -1,61 +0,0 @@
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
@@ -1,65 +0,0 @@
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
@@ -1,27 +0,0 @@
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
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe 'ArrayStrings::Tags' 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 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
-
42
- context 'cast integers to strings' do
43
- before do
44
- Entity.create(attr: 'int1', column => [1])
45
- end
46
-
47
- it 'find tags in numeric format' do
48
- tags = relation.all.where(name: 1)
49
-
50
- expect(tags.size).to be_eql(1)
51
- expect(tags.map(&:name)).to be_eql(['1'])
52
- end
53
- end
54
- end
@@ -1,6 +0,0 @@
1
- adapter: postgresql
2
- host: <%= ENV.fetch("PG_HOST") { "localhost" } %>
3
- port: <%= ENV.fetch("PG_PORT") { 5432 } %>
4
- username: <%= ENV.fetch("PG_USER") { raise "PG_USER env. not set" } %>
5
- password: <%= ENV.fetch("PG_PSWD") { raise "PG_PSWD env. not set" } %>
6
- database: <%= ENV.fetch("PG_TAGS_ON_DB") { raise "PG_TAGS_ON_DB env. not set" } %>
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe 'configuration' do
4
- before do
5
- Entity.pg_tags_on_reset
6
- end
7
-
8
- it 'should set query class' do
9
- PgTagsOn.configure do |c|
10
- c.query_class = 'Tagz'
11
- end
12
- Entity.pg_tags_on :tags_int
13
-
14
- expect(Kernel.const_defined?('Tagz')).to be_truthy
15
- end
16
-
17
- it 'should set columns for multiple models' do
18
- class Entity1 < ActiveRecord::Base
19
- self.table_name = 'entities'
20
- pg_tags_on :tags_int
21
- end
22
-
23
- class Entity2 < ActiveRecord::Base
24
- self.table_name = 'entities'
25
- pg_tags_on :tags_str
26
- end
27
-
28
- expect(Entity1.pg_tags_on_settings).to be_eql('tags_int' => {})
29
- expect(Entity2.pg_tags_on_settings).to be_eql('tags_str' => {})
30
- end
31
-
32
- it 'should set multiple columns for the same model' do
33
- Entity.pg_tags_on :tags_int
34
- Entity.pg_tags_on :tags_str
35
-
36
- expect(Entity.pg_tags_on_settings).to be_eql('tags_int' => {}, 'tags_str' => {})
37
- end
38
-
39
- it 'should raise error if column does not exists' do
40
- expect { Entity.pg_tags_on(:dummy) }.to raise_error(PgTagsOn::ColumnNotFoundError)
41
- end
42
-
43
- it 'should create named scopes' do
44
- Entity.pg_tags_on :tags_int
45
-
46
- expect(Entity).to respond_to(:tags_int)
47
- end
48
- end
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DatabaseHelpers
4
- def establish_connection
5
- ActiveRecord::Base.establish_connection(config)
6
- end
7
-
8
- def config
9
- @config ||= begin
10
- file = File.expand_path('../config/database.yml', File.dirname(__FILE__))
11
- YAML.safe_load(ERB.new(File.read(file)).result)
12
- end
13
- end
14
-
15
- def load_schema
16
- @connection = ActiveRecord::Base.connection
17
- @connection.drop_table :entities
18
- @connection.transaction do
19
- @connection.create_table :entities, force: true do |t|
20
- t.integer :tags_int, array: true
21
- t.string :tags_str, array: true
22
- t.text :tags_text, array: true
23
- t.jsonb :tags_jsonb, array: true
24
- t.string :attr
25
- end
26
- @connection.add_index :entities, :tags_str, using: 'gin'
27
- end
28
- end
29
-
30
- def truncate
31
- ActiveRecord::Base.connection.truncate(Entity.table_name)
32
- end
33
-
34
- def encode_json(json)
35
- case json
36
- when String
37
- ActiveSupport::JSON.encode(json)
38
- when Hash
39
- ActiveSupport::JSON.encode(json.to_json)
40
- when Array
41
- "'{" + json.map { |item| encode_json(item) }.join(',') + "}'"
42
- end
43
- end
44
-
45
- module_function :config, :establish_connection, :load_schema, :truncate
46
- end
data/spec/spec_helper.rb DELETED
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- WITH_COVERAGE = ENV['COVERAGE']
4
-
5
- if WITH_COVERAGE
6
- require 'simplecov'
7
- SimpleCov.start do
8
- add_filter %r{/benchmark/}
9
- end
10
- end
11
-
12
- require 'pg'
13
- require 'active_record'
14
- require 'bundler/setup'
15
- require 'pg_tags_on'
16
- require 'helpers/database_helpers'
17
- require 'support/factory'
18
- require 'pry'
19
-
20
- RSpec.configure do |config|
21
- # Enable flags like --only-failures and --next-failure
22
- config.example_status_persistence_file_path = '.rspec_status'
23
-
24
- # Disable RSpec exposing methods globally on `Module` and `main`
25
- config.disable_monkey_patching!
26
-
27
- config.expect_with :rspec do |c|
28
- c.syntax = :expect
29
- end
30
-
31
- config.include DatabaseHelpers
32
-
33
- config.before(:all) do
34
- DatabaseHelpers.establish_connection
35
- DatabaseHelpers.load_schema
36
- end
37
- end
38
-
39
- Entity = Class.new(::ActiveRecord::Base)
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Factory
4
- def self.array_strings
5
- Entity.insert_all([
6
- { tags_str: %w[a b c], attr: 'test1' },
7
- { tags_str: %w[b c], attr: 'test2' },
8
- { tags_str: ['d'], attr: 'test3' }
9
- ])
10
- end
11
-
12
- def self.array_integers
13
- Entity.insert_all([
14
- { tags_int: [1, 2, 3], attr: 'test1' },
15
- { tags_int: [2, 3], attr: 'test2' },
16
- { tags_int: [4], attr: 'test3' }
17
- ])
18
- end
19
-
20
- def self.array_jsonb
21
- Entity.insert_all([
22
- { tags_jsonb: [{ name: 'a' },
23
- { name: 'b' },
24
- { name: 'c' }],
25
- attr: 'test1' },
26
- { tags_jsonb: [{ name: 'b' },
27
- { name: 'c' }],
28
- attr: 'test2' },
29
- { tags_jsonb: [{ name: 'd' }],
30
- attr: 'test3' }
31
- ])
32
- end
33
-
34
- def self.array_jsonb_with_attrs
35
- Entity.insert_all([
36
- { tags_jsonb: [{ name: 'a', meta: 'a' },
37
- { name: 'b', meta: 'b' },
38
- { name: 'c', meta: 'c' }],
39
- attr: 'test1' },
40
- { tags_jsonb: [{ name: 'b', meta: 'b' },
41
- { name: 'c', meta: 'c' }],
42
- attr: 'test2' },
43
- { tags_jsonb: [{ name: 'd', meta: 'd' }],
44
- attr: 'test3' }
45
- ])
46
- end
47
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe 'TagsQuery' do
4
- it 'accept String arguments' do
5
- query = PgTagsOn::TagsQuery.all('a')
6
-
7
- expect(query.value).to be_eql('a')
8
- expect(query.predicate).to be_eql('all')
9
- end
10
-
11
- it 'accept Integer arguments' do
12
- query = PgTagsOn::TagsQuery.all(1)
13
-
14
- expect(query.value).to be_eql(1)
15
- expect(query.predicate).to be_eql('all')
16
- end
17
-
18
- it 'accept Array arguments' do
19
- query = PgTagsOn::TagsQuery.all(%w[a b c])
20
-
21
- expect(query.value).to be_eql(%w[a b c])
22
- expect(query.predicate).to be_eql('all')
23
- end
24
-
25
- it 'accept arguments list' do
26
- query = PgTagsOn::TagsQuery.all('a', 'b', 'c')
27
-
28
- expect(query.value).to be_eql(%w[a b c])
29
- expect(query.predicate).to be_eql('all')
30
- end
31
- end
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe 'Validator' do
4
- before(:all) do
5
- Entity.pg_tags_on :tags_str, limit: 2, tag_length: 3
6
- Entity.pg_tags_on :tags_jsonb, key: :name, limit: 2, tag_length: 3
7
- end
8
-
9
- context 'string tags' do
10
- it 'add errors if number of tags exceeds limit' do
11
- entity = Entity.new(tags_str: %w[a b c])
12
- entity.valid?
13
-
14
- expect(entity.errors.full_messages.first).to include('size exceeded')
15
- end
16
-
17
- it 'add errors if tag length exceeds limit' do
18
- entity = Entity.new(tags_str: %w[a x123])
19
- entity.valid?
20
-
21
- expect(entity.errors.full_messages.first).to include('length exceeded')
22
- end
23
- end
24
-
25
- context 'jsonb tags' do
26
- it 'add errors if number of tags exceeds limit' do
27
- entity = Entity.new(tags_jsonb: [{ name: 'a' }, { name: 'b' }, { name: 'c' }])
28
- entity.valid?
29
-
30
- expect(entity.errors.full_messages.first).to include('size exceeded')
31
- end
32
-
33
- it 'add errors if tag length exceeds limit' do
34
- entity = Entity.new(tags_jsonb: [{ name: 'x123' }])
35
- entity.valid?
36
-
37
- expect(entity.errors.full_messages.first).to include('length exceeded')
38
- end
39
- end
40
- end
data/tasks/benchmark.rake DELETED
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'benchmark'
4
- require './spec/helpers/database_helpers'
5
- require 'lib/pg_tags_on'
6
- require 'pry'
7
-
8
- namespace :pg_tags_on do
9
- task :benchmark do
10
- DatabaseHelpers.establish_connection
11
- DatabaseHelpers.load_schema
12
-
13
- Entity = Class.new(::ActiveRecord::Base)
14
- Entity.pg_tags_on :tags_int
15
- Entity.pg_tags_on :tags_str
16
- Entity.pg_tags_on :tags_jsonb, key: :name
17
-
18
- puts 'How many records to generate? (default 10_000)'
19
- records = STDIN.gets.chomp.to_i
20
- puts 'Minimum tags per record (default 1):'
21
- min_tags = STDIN.gets.chomp.to_i
22
- puts 'Maximum tags per record (default 10):'
23
- max_tags = STDIN.gets.chomp.to_i
24
-
25
- records = 10_000 if records.zero?
26
- min_tags = 1 if min_tags.zero?
27
- max_tags = 10 if max_tags.zero?
28
-
29
- data = []
30
- str_tags = Array.new(100) { Faker::Name.first_name }
31
-
32
- puts "Generating #{records} records..."
33
- records.times do
34
- tags_count = rand(min_tags..max_tags)
35
-
36
- data << {
37
- tags_int: Array.new(tags_count) { rand(1..100) },
38
- tags_str: str_tags.sample(tags_count),
39
- tags_jsonb: Array.new(tags_count) { { name: str_tags.sample } }
40
- }
41
-
42
- if data.size == 5_000
43
- Entity.insert_all data
44
- data = []
45
- end
46
- end
47
-
48
- Entity.insert_all(data) if data.present?
49
- puts 'Done'
50
-
51
- puts "\n\n* character varying[]\n\n"
52
- PgTagsOn::Benchmark.new(Entity, :tags_str).call
53
- puts "\n\n* integer[]\n\n"
54
- PgTagsOn::Benchmark.new(Entity, :tags_int).call
55
- puts "\n\n* jsonb[]\n\n"
56
- PgTagsOn::Benchmark.new(Entity, :tags_jsonb).call
57
- end
58
- end