pgrel 0.0.1 → 0.1.0
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/.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
|