pgrel 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.rubocop.yml +45 -0
- data/.travis.yml +14 -14
- data/Gemfile +9 -0
- data/README.md +2 -0
- data/gemfiles/rails40.gemfile +1 -1
- data/gemfiles/rails41.gemfile +1 -1
- data/gemfiles/rails42.gemfile +1 -1
- data/gemfiles/rails5.gemfile +1 -0
- data/lib/pgrel/active_record/query_methods.rb +31 -0
- data/lib/pgrel/active_record/store_chain/array_chain.rb +18 -0
- data/lib/pgrel/active_record/store_chain/hstore_chain.rb +36 -0
- data/lib/pgrel/active_record/store_chain/jsonb_chain.rb +76 -0
- data/lib/pgrel/active_record/store_chain.rb +176 -0
- data/lib/pgrel/active_record.rb +2 -0
- data/lib/pgrel/version.rb +1 -1
- data/lib/pgrel.rb +2 -2
- data/pgrel.gemspec +3 -4
- data/spec/pgrel/array_spec.rb +71 -0
- data/spec/pgrel/hstore_spec.rb +125 -0
- data/spec/pgrel/jsonb_spec.rb +137 -0
- data/spec/spec_helper.rb +19 -8
- data/spec/support/array_store.rb +2 -0
- data/spec/support/hstore.rb +2 -0
- data/spec/support/jsonb.rb +2 -0
- metadata +36 -65
- data/Gemfile.lock +0 -112
- data/spec/dummy/README.rdoc +0 -28
- data/spec/dummy/Rakefile +0 -6
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +0 -13
- data/spec/dummy/app/assets/stylesheets/application.css +0 -15
- data/spec/dummy/app/controllers/application_controller.rb +0 -5
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/helpers/application_helper.rb +0 -2
- data/spec/dummy/app/mailers/.keep +0 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +0 -14
- data/spec/dummy/bin/bundle +0 -3
- data/spec/dummy/bin/rails +0 -4
- data/spec/dummy/bin/rake +0 -4
- data/spec/dummy/bin/setup +0 -29
- data/spec/dummy/config/application.rb +0 -26
- data/spec/dummy/config/boot.rb +0 -5
- data/spec/dummy/config/database.yml +0 -29
- data/spec/dummy/config/environment.rb +0 -5
- data/spec/dummy/config/environments/development.rb +0 -41
- data/spec/dummy/config/environments/production.rb +0 -79
- data/spec/dummy/config/environments/test.rb +0 -42
- data/spec/dummy/config/initializers/assets.rb +0 -11
- data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/spec/dummy/config/initializers/cookies_serializer.rb +0 -3
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +0 -4
- data/spec/dummy/config/initializers/inflections.rb +0 -16
- data/spec/dummy/config/initializers/mime_types.rb +0 -4
- data/spec/dummy/config/initializers/session_store.rb +0 -3
- data/spec/dummy/config/initializers/wrap_parameters.rb +0 -14
- data/spec/dummy/config/locales/en.yml +0 -23
- data/spec/dummy/config/routes.rb +0 -56
- data/spec/dummy/config/secrets.yml +0 -22
- data/spec/dummy/config.ru +0 -4
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +0 -67
- data/spec/dummy/public/422.html +0 -67
- data/spec/dummy/public/500.html +0 -66
- data/spec/dummy/public/favicon.ico +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 642a7d4b7a7b19f5acc220e6f74d41d6d0e00524
|
4
|
+
data.tar.gz: 007ba40877cfc0c6bdbb260502b98b1627ceaaa6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 82cf0b3c4ed6c5ec72a55e6572ee0a197cb15079c70f48d61499acaac15d4d98bcb2262f5965a55c5c669bd4a98e04edd0e18da9b0ff7e018ae94ca82b2363e5
|
7
|
+
data.tar.gz: 130ed3ac3d71c6c98f72a33a881877f23f147653a1735a68a4bc8016996345cca77721713874d9f0a8d9b2991f2ccc4758b026224b4aa450e9b2cf0e0c8c8ce9
|
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# This strict rubocop config should be used before submiting PR and should pass
|
2
|
+
# Doesn't include:
|
3
|
+
# - specs
|
4
|
+
|
5
|
+
|
6
|
+
AllCops:
|
7
|
+
# Include gemspec and Rakefile
|
8
|
+
Include:
|
9
|
+
- 'app/**/*.rb'
|
10
|
+
- 'config/**/*.rb'
|
11
|
+
- 'lib/**/*.rb'
|
12
|
+
- 'lib/**/*.rake'
|
13
|
+
Exclude:
|
14
|
+
- 'vendor/**/*'
|
15
|
+
- 'spec/**/*'
|
16
|
+
- 'db/*.rb'
|
17
|
+
- 'bin/**/*'
|
18
|
+
RunRailsCops: false
|
19
|
+
DisplayCopNames: true
|
20
|
+
StyleGuideCopsOnly: false
|
21
|
+
|
22
|
+
Style/Documentation:
|
23
|
+
Exclude:
|
24
|
+
- 'lib/**/version.rb'
|
25
|
+
|
26
|
+
Metrics/LineLength:
|
27
|
+
Max: 100
|
28
|
+
|
29
|
+
Metrics/MethodLength:
|
30
|
+
Max: 15
|
31
|
+
|
32
|
+
Metrics/AbcSize:
|
33
|
+
Max: 20
|
34
|
+
|
35
|
+
Style/BarePercentLiterals:
|
36
|
+
Enabled: false
|
37
|
+
|
38
|
+
Style/CommentAnnotation:
|
39
|
+
Enabled: false
|
40
|
+
|
41
|
+
Style/RaiseArgs:
|
42
|
+
Enabled: false
|
43
|
+
|
44
|
+
Style/StringLiterals:
|
45
|
+
Enabled: false
|
data/.travis.yml
CHANGED
@@ -1,21 +1,21 @@
|
|
1
1
|
language: ruby
|
2
2
|
cache: bundler
|
3
|
-
rvm:
|
4
|
-
- 2.2
|
5
3
|
|
6
|
-
|
7
|
-
|
4
|
+
addons:
|
5
|
+
postgresql: "9.4"
|
6
|
+
|
7
|
+
before_script:
|
8
|
+
- createuser pgrel -d
|
9
|
+
- createdb -U pgrel pgrel
|
10
|
+
- psql -U postgres -d pgrel -c 'CREATE EXTENSION IF NOT EXISTS hstore;'
|
8
11
|
|
9
12
|
matrix:
|
10
13
|
include:
|
11
|
-
- rvm: 2.2
|
12
|
-
gemfile: gemfiles/
|
13
|
-
|
14
|
-
- rvm: 2.2
|
15
|
-
gemfile: gemfiles/rails41.gemfile
|
16
|
-
|
17
|
-
- rvm: 2.2
|
14
|
+
- rvm: 2.2.1
|
15
|
+
gemfile: gemfiles/rails5.gemfile
|
16
|
+
- rvm: 2.2.1
|
18
17
|
gemfile: gemfiles/rails42.gemfile
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
- rvm: 2.1
|
19
|
+
gemfile: gemfiles/rails41.gemfile
|
20
|
+
- rvm: 2.1
|
21
|
+
gemfile: gemfiles/rails40.gemfile
|
data/Gemfile
CHANGED
data/README.md
CHANGED
data/gemfiles/rails40.gemfile
CHANGED
data/gemfiles/rails41.gemfile
CHANGED
data/gemfiles/rails42.gemfile
CHANGED
data/gemfiles/rails5.gemfile
CHANGED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'active_record/relation'
|
2
|
+
require 'pgrel/active_record/store_chain'
|
3
|
+
require 'pgrel/active_record/store_chain/array_chain'
|
4
|
+
require 'pgrel/active_record/store_chain/hstore_chain'
|
5
|
+
require 'pgrel/active_record/store_chain/jsonb_chain'
|
6
|
+
|
7
|
+
module ActiveRecord
|
8
|
+
module QueryMethods
|
9
|
+
# Extend WhereChain with 'store' method.
|
10
|
+
class WhereChain
|
11
|
+
def store(store_name, *opts)
|
12
|
+
store_name = store_name.to_s
|
13
|
+
column = @scope.klass.columns_hash[store_name]
|
14
|
+
|
15
|
+
# Rails 4 column has method 'array'
|
16
|
+
# but Rails 5 has 'array?'.
|
17
|
+
#
|
18
|
+
# So, check both(
|
19
|
+
if (arr = column.try(:array)) || (arr.nil? && column.array?)
|
20
|
+
klass = ArrayChain
|
21
|
+
else
|
22
|
+
column_klass = column.type.capitalize
|
23
|
+
klass = "ActiveRecord::QueryMethods::#{column_klass}Chain".constantize
|
24
|
+
end
|
25
|
+
chain = klass.new(@scope, store_name)
|
26
|
+
return chain.where(*opts) unless opts.empty?
|
27
|
+
chain
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module QueryMethods
|
3
|
+
# Store chain for array columns.
|
4
|
+
class ArrayChain < StoreChain
|
5
|
+
# Whether the array overlaps provided array.
|
6
|
+
#
|
7
|
+
# Example
|
8
|
+
# Model.create!(name: 'first', store: ['b', 'c'])
|
9
|
+
# Model.create!(name: 'second', store: ['a', 'b'])
|
10
|
+
#
|
11
|
+
# Model.store(:store).overlap('c').all #=> [Model(name: 'first', ...)]
|
12
|
+
# Model.store(:store).overlap(['b']).size #=> 2
|
13
|
+
def overlap(*vals)
|
14
|
+
update_scope "#{@store_name} && #{type_cast(vals.flatten)}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module QueryMethods
|
3
|
+
# Store chain for hstore columns.
|
4
|
+
class HstoreChain < KeyStoreChain
|
5
|
+
# Query by store values.
|
6
|
+
#
|
7
|
+
# Supports array values.
|
8
|
+
#
|
9
|
+
# Example
|
10
|
+
# Model.create!(name: 'first', store: {b: 1, c: 2})
|
11
|
+
# Model.create!(name: 'second', store: {b: 2, c: 3})
|
12
|
+
#
|
13
|
+
# Model.store(:store, c: 2).all #=> [Model(name: 'first', ...)]
|
14
|
+
# Model.store(:store, b: [1, 2]).size #=> 2
|
15
|
+
def where(opts)
|
16
|
+
opts = stringify(opts)
|
17
|
+
where_with_prefix "#{@store_name}->", opts
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def stringify(val)
|
23
|
+
case val
|
24
|
+
when String
|
25
|
+
val
|
26
|
+
when Array
|
27
|
+
val.map { |v| stringify(v) }
|
28
|
+
when Hash
|
29
|
+
Hash[val.map { |k, v| [k, stringify(v)] }]
|
30
|
+
else
|
31
|
+
val.to_s
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module QueryMethods
|
3
|
+
# Store chain for jsonb columns.
|
4
|
+
class JsonbChain < KeyStoreChain
|
5
|
+
# Query by store values.
|
6
|
+
# Supports array values (convert to IN statement).
|
7
|
+
#
|
8
|
+
# Example
|
9
|
+
# Model.create!(name: 'first', store: {b: 1, c: 2})
|
10
|
+
# Model.create!(name: 'second', store: {b: 2, c: 3})
|
11
|
+
#
|
12
|
+
# Model.store(:store, c: 2).all #=> [Model(name: 'first', ...)]
|
13
|
+
# Model.store(:store, b: [1, 2]).size #=> 2
|
14
|
+
def where(opts)
|
15
|
+
opts = flatten_json(opts)
|
16
|
+
where_with_prefix "#{@store_name}->", opts
|
17
|
+
end
|
18
|
+
|
19
|
+
# Query by quality in path.
|
20
|
+
#
|
21
|
+
# Path can be set as object or as args.
|
22
|
+
#
|
23
|
+
# Example:
|
24
|
+
# Model.create!(name: 'first', store: {b: 1, c: { d: 3 } })
|
25
|
+
# Model.create!(name: 'second', store: {b: 2, c: { d: 1 }})
|
26
|
+
#
|
27
|
+
# Model.store(:store).path(c: {d: 3}).all #=> [Model(name: 'first', ...)]
|
28
|
+
# Model.store(:store).path('c', 'd', [1, 3]).size #=> 2
|
29
|
+
def path(*args)
|
30
|
+
args = flatten_hash(args.first) if args.size == 1
|
31
|
+
val = args.pop
|
32
|
+
|
33
|
+
path = "{#{args.join(',')}}"
|
34
|
+
|
35
|
+
case val
|
36
|
+
when Hash
|
37
|
+
op = '#>'
|
38
|
+
val = ::ActiveSupport::JSON.encode(val)
|
39
|
+
when Array
|
40
|
+
op = '#>>'
|
41
|
+
val = val.map(&:to_s)
|
42
|
+
else
|
43
|
+
op = '#>>'
|
44
|
+
val = val.to_s
|
45
|
+
end
|
46
|
+
|
47
|
+
where_with_prefix "#{@store_name}#{op}", path => val
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def flatten_json(val)
|
53
|
+
Hash[
|
54
|
+
val.map do |k, v|
|
55
|
+
if v.is_a?(Array)
|
56
|
+
[k, v.map { |i| ::ActiveSupport::JSON.encode(i) }]
|
57
|
+
else
|
58
|
+
[k, ::ActiveSupport::JSON.encode(v)]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
]
|
62
|
+
end
|
63
|
+
|
64
|
+
def flatten_hash(hash)
|
65
|
+
case hash
|
66
|
+
when Hash
|
67
|
+
hash.flat_map { |k, v| [k, *flatten_hash(v)] }
|
68
|
+
when Array
|
69
|
+
[hash]
|
70
|
+
else
|
71
|
+
hash
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# Check rails version
|
2
|
+
RAILS_5 = ActiveRecord.version.release >= Gem::Version.new("5")
|
3
|
+
|
4
|
+
module ActiveRecord
|
5
|
+
module QueryMethods
|
6
|
+
# Base class for different store chains (hstore, jsonb, array).
|
7
|
+
# Provides _containment_ queries methods.
|
8
|
+
# Provides basic methods.
|
9
|
+
class StoreChain
|
10
|
+
def initialize(scope, store_name)
|
11
|
+
@scope = scope
|
12
|
+
@store_name = store_name
|
13
|
+
@inverted = false
|
14
|
+
end
|
15
|
+
|
16
|
+
# Whether the store contains provided store
|
17
|
+
#
|
18
|
+
# Example
|
19
|
+
# Model.create!(name: 'first', store: {a: 1, b: 2})
|
20
|
+
# Model.create!(name: 'second', store: {b: 1, c: 3})
|
21
|
+
#
|
22
|
+
# data = {a: 1}
|
23
|
+
# Model.store(:store).contains(data).all #=> [Model(name: 'first', ...)]
|
24
|
+
def contains(opts)
|
25
|
+
update_scope "#{@store_name} @> #{type_cast(opts)}"
|
26
|
+
end
|
27
|
+
|
28
|
+
# Whether the store is contained within provided store
|
29
|
+
#
|
30
|
+
# Example
|
31
|
+
# Model.create!(name: 'first', store: {b: 1})
|
32
|
+
# Model.create!(name: 'second', store: {b: 1, c: 3})
|
33
|
+
#
|
34
|
+
# data = {b: 1, c: 2}
|
35
|
+
# Model.store(:store).contains(data).all #=> [Model(name: 'first', ...)]
|
36
|
+
def contained(opts)
|
37
|
+
update_scope "#{@store_name} <@ #{type_cast(opts)}"
|
38
|
+
end
|
39
|
+
|
40
|
+
# Add negation to condition.
|
41
|
+
#
|
42
|
+
# Example
|
43
|
+
# Model.create!(name: 'first', store: {b: 2})
|
44
|
+
# Model.create!(name: 'second', store: {b: 1, c: 3})
|
45
|
+
#
|
46
|
+
# Model.store(:store).not.contains({c: 3}).all #=> [Model(name: 'first')]
|
47
|
+
#
|
48
|
+
# Model.store(:store).not(b: 2).all #=> [Model(name: 'second')]
|
49
|
+
def not(opts = :chain)
|
50
|
+
@inverted = true
|
51
|
+
return where(opts) unless opts == :chain
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
if RAILS_5
|
56
|
+
protected
|
57
|
+
|
58
|
+
def update_scope(*opts)
|
59
|
+
where_clause = @scope.send(:where_clause_factory).build(opts, {})
|
60
|
+
@scope.where_clause += @inverted ? where_clause.invert : where_clause
|
61
|
+
@scope
|
62
|
+
end
|
63
|
+
|
64
|
+
def type_cast(value)
|
65
|
+
ActiveRecord::Base.connection.quote(
|
66
|
+
@scope.table.type_cast_for_database(@store_name, value)
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
def where_with_prefix(prefix, opts)
|
71
|
+
where_clause = @scope.send(:where_clause_factory).build(opts, {})
|
72
|
+
predicates = where_clause.ast.children.map do |rel|
|
73
|
+
rel.left = to_sql_literal(prefix, rel.left)
|
74
|
+
rel
|
75
|
+
end
|
76
|
+
where_clause = ActiveRecord::Relation::WhereClause.new(
|
77
|
+
predicates,
|
78
|
+
where_clause.binds
|
79
|
+
)
|
80
|
+
@scope.where_clause += @inverted ? where_clause.invert : where_clause
|
81
|
+
@scope
|
82
|
+
end
|
83
|
+
else
|
84
|
+
protected
|
85
|
+
|
86
|
+
def update_scope(*opts)
|
87
|
+
where_clause = @scope.send(:build_where, opts).map do |rel|
|
88
|
+
@inverted ? invert_arel(rel) : rel
|
89
|
+
end
|
90
|
+
@scope.where_values += where_clause
|
91
|
+
@scope
|
92
|
+
end
|
93
|
+
|
94
|
+
def type_cast(value)
|
95
|
+
ActiveRecord::Base.connection.quote(
|
96
|
+
value,
|
97
|
+
@scope.klass.columns_hash[@store_name]
|
98
|
+
)
|
99
|
+
end
|
100
|
+
|
101
|
+
def where_with_prefix(prefix, opts)
|
102
|
+
where_value = @scope.send(:build_where, opts).map do |rel|
|
103
|
+
rel.left = to_sql_literal(prefix, rel.left)
|
104
|
+
@inverted ? invert_arel(rel) : rel
|
105
|
+
end
|
106
|
+
@scope.where_values += where_value
|
107
|
+
@scope
|
108
|
+
end
|
109
|
+
|
110
|
+
def invert_arel(rel)
|
111
|
+
case rel
|
112
|
+
when Arel::Nodes::In
|
113
|
+
Arel::Nodes::NotIn.new(rel.left, rel.right)
|
114
|
+
when Arel::Nodes::Equality
|
115
|
+
Arel::Nodes::NotEqual.new(rel.left, rel.right)
|
116
|
+
when String
|
117
|
+
Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(rel))
|
118
|
+
else
|
119
|
+
Arel::Nodes::Not.new(rel)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Base class for key-value types of stores (hstore, jsonb)
|
126
|
+
class KeyStoreChain < StoreChain
|
127
|
+
# Single key existence
|
128
|
+
#
|
129
|
+
# Example
|
130
|
+
# Model.create!(name: 'first', store: {a: 1})
|
131
|
+
# Model.create!(name: 'second', store: {b: 1})
|
132
|
+
#
|
133
|
+
# # Get all records which have key 'a' in store 'store'
|
134
|
+
# Model.store(:store).key('a').all #=> [Model(name: 'first', ...)]
|
135
|
+
def key(key)
|
136
|
+
update_scope "#{@store_name} ? :key", key: key.to_s
|
137
|
+
end
|
138
|
+
|
139
|
+
# Several keys existence
|
140
|
+
#
|
141
|
+
# Example
|
142
|
+
# Model.create!(name: 'first', store: {a: 1, b: 2})
|
143
|
+
# Model.create!(name: 'second', store: {b: 1, c: 3})
|
144
|
+
#
|
145
|
+
# Model.store(:store).keys('a','b').all #=> [Model(name: 'first', ...)]
|
146
|
+
def keys(*keys)
|
147
|
+
update_scope(
|
148
|
+
"#{@store_name} ?& ARRAY[:keys]",
|
149
|
+
keys: keys.flatten.map(&:to_s)
|
150
|
+
)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Any of the keys existence
|
154
|
+
#
|
155
|
+
# Example
|
156
|
+
# Model.create!(name: 'first', store: {a: 1, b: 2})
|
157
|
+
# Model.create!(name: 'second', store: {b: 1, c: 3})
|
158
|
+
#
|
159
|
+
# Model.store(:store).keys('a','b').count #=> 2
|
160
|
+
def any(*keys)
|
161
|
+
update_scope(
|
162
|
+
"#{@store_name} ?| ARRAY[:keys]",
|
163
|
+
keys: keys.flatten.map(&:to_s)
|
164
|
+
)
|
165
|
+
end
|
166
|
+
|
167
|
+
protected
|
168
|
+
|
169
|
+
def to_sql_literal(prefix, node)
|
170
|
+
Arel::Nodes::SqlLiteral.new(
|
171
|
+
"#{prefix}'#{node.name}'"
|
172
|
+
)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
data/lib/pgrel/version.rb
CHANGED
data/lib/pgrel.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require 'pgrel/version'
|
2
|
+
require 'pgrel/active_record'
|
data/pgrel.gemspec
CHANGED
@@ -17,12 +17,11 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.files = `git ls-files`.split($/)
|
18
18
|
s.require_paths = ["lib"]
|
19
19
|
|
20
|
-
s.add_runtime_dependency "
|
21
|
-
|
22
|
-
s.add_development_dependency "pg"
|
20
|
+
s.add_runtime_dependency "activerecord", ">=4.0.0"
|
23
21
|
|
22
|
+
s.add_development_dependency "pg", "~>0.18"
|
23
|
+
s.add_development_dependency('rake', '~> 10.1')
|
24
24
|
s.add_development_dependency "simplecov", ">= 0.3.8"
|
25
25
|
s.add_development_dependency 'pry-byebug'
|
26
26
|
s.add_development_dependency "rspec", "~> 3.1.0"
|
27
|
-
s.add_development_dependency "rspec-rails", "~> 3.1.0"
|
28
27
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ArrayStore do
|
4
|
+
before do
|
5
|
+
@connection = ActiveRecord::Base.connection
|
6
|
+
|
7
|
+
@connection.transaction do
|
8
|
+
@connection.create_table('array_stores') do |t|
|
9
|
+
t.string 'tags', default: [], null: false, array: true
|
10
|
+
t.string 'name'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
ArrayStore.reset_column_information
|
15
|
+
end
|
16
|
+
|
17
|
+
after do
|
18
|
+
@connection.drop_table 'array_stores', if_exists: true
|
19
|
+
end
|
20
|
+
|
21
|
+
let!(:setup) do
|
22
|
+
ArrayStore.create!(name: 'a', tags: [1, 2, 'a', 'b'])
|
23
|
+
ArrayStore.create!(name: 'b', tags: [2, 'b', 'e'])
|
24
|
+
ArrayStore.create!(name: 'c', tags: ['b'])
|
25
|
+
ArrayStore.create!(name: 'd')
|
26
|
+
ArrayStore.create!(name: 'e', tags: [2, 'x', 'c'])
|
27
|
+
end
|
28
|
+
|
29
|
+
context '#overlap' do
|
30
|
+
it "single simple argument" do
|
31
|
+
records = ArrayStore.where.store(:tags).overlap(:b)
|
32
|
+
expect(records.size).to eq 3
|
33
|
+
end
|
34
|
+
|
35
|
+
it "several arguments" do
|
36
|
+
records = ArrayStore.where.store(:tags).overlap('a', 1)
|
37
|
+
expect(records.size).to eq 1
|
38
|
+
expect(records.first.name).to eq 'a'
|
39
|
+
end
|
40
|
+
|
41
|
+
it "single array argument" do
|
42
|
+
records = ArrayStore.where.store(:tags).overlap([1, 'x'])
|
43
|
+
expect(records.size).to eq 2
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it '#contains' do
|
48
|
+
records = ArrayStore.where.store(:tags).contains([2, 'b'])
|
49
|
+
expect(records.size).to eq 2
|
50
|
+
|
51
|
+
records = ArrayStore.where.store(:tags).contains([2, 'x'])
|
52
|
+
expect(records.size).to eq 1
|
53
|
+
expect(records.first.name).to eq 'e'
|
54
|
+
end
|
55
|
+
|
56
|
+
it '#contained' do
|
57
|
+
records = ArrayStore.where.store(:tags).contained([1, 2, 'a', 'b'])
|
58
|
+
expect(records.size).to eq 3
|
59
|
+
expect(records.detect { |r| r.name == 'd' }).not_to be_nil
|
60
|
+
|
61
|
+
records = ArrayStore.where.store(:tags).contained([])
|
62
|
+
expect(records.size).to eq 1
|
63
|
+
expect(records.first.name).to eq 'd'
|
64
|
+
end
|
65
|
+
|
66
|
+
context '#not' do
|
67
|
+
it '#overlap' do
|
68
|
+
expect(ArrayStore.where.store(:tags).not.overlap('b', 2).size).to eq 1
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|