plucky 0.6.3 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -3
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -4
- data/Gemfile +6 -5
- data/Gemfile.lock +84 -0
- data/LICENSE +1 -1
- data/README.md +17 -75
- data/Rakefile +0 -3
- data/examples/query.rb +8 -7
- data/lib/plucky.rb +1 -0
- data/lib/plucky/criteria_hash.rb +78 -62
- data/lib/plucky/extensions/symbol.rb +8 -0
- data/lib/plucky/normalizers/criteria_hash_value.rb +3 -1
- data/lib/plucky/normalizers/fields_value.rb +3 -3
- data/lib/plucky/normalizers/hash_key.rb +19 -0
- data/lib/plucky/normalizers/options_hash_value.rb +5 -7
- data/lib/plucky/normalizers/sort_value.rb +8 -6
- data/lib/plucky/options_hash.rb +9 -3
- data/lib/plucky/pagination.rb +1 -1
- data/lib/plucky/pagination/{decorator.rb → collection.rb} +10 -1
- data/lib/plucky/query.rb +56 -21
- data/lib/plucky/transformer.rb +14 -0
- data/lib/plucky/version.rb +1 -1
- data/plucky.gemspec +4 -5
- data/script/bootstrap +21 -0
- data/script/release +42 -0
- data/script/test +20 -0
- data/spec/functional/options_hash_spec.rb +41 -0
- data/spec/helper.rb +12 -4
- data/spec/plucky/criteria_hash_spec.rb +68 -4
- data/spec/plucky/normalizers/criteria_hash_value_spec.rb +1 -1
- data/spec/plucky/normalizers/fields_value_spec.rb +5 -5
- data/spec/plucky/normalizers/hash_key_spec.rb +15 -0
- data/spec/plucky/normalizers/options_hash_value_spec.rb +2 -2
- data/spec/plucky/normalizers/sort_value_spec.rb +24 -20
- data/spec/plucky/options_hash_spec.rb +2 -2
- data/spec/plucky/pagination/{decorator_spec.rb → collection_spec.rb} +8 -5
- data/spec/plucky/query_spec.rb +92 -35
- data/spec/plucky_spec.rb +5 -5
- data/spec/symbol_operator_spec.rb +18 -1
- metadata +37 -36
- data/lib/plucky/normalizers/options_hash_key.rb +0 -23
- data/script/criteria_hash.rb +0 -21
- data/spec/plucky/normalizers/options_hash_key_spec.rb +0 -23
@@ -73,6 +73,14 @@ class SymbolOperator
|
|
73
73
|
field.to_s <=> other.field.to_s
|
74
74
|
end
|
75
75
|
end
|
76
|
+
|
77
|
+
def hash
|
78
|
+
field.hash + operator.hash
|
79
|
+
end
|
80
|
+
|
81
|
+
def eql?(other)
|
82
|
+
self == other
|
83
|
+
end
|
76
84
|
|
77
85
|
def ==(other)
|
78
86
|
other.class == self.class && field == other.field && operator == other.operator
|
@@ -1,10 +1,12 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
1
3
|
module Plucky
|
2
4
|
module Normalizers
|
3
5
|
class CriteriaHashValue
|
4
6
|
|
5
7
|
# Internal: Used by normalized_value to determine if we need to run the
|
6
8
|
# value through another criteria hash to normalize it.
|
7
|
-
NestingOperators = [:$or, :$and, :$nor]
|
9
|
+
NestingOperators = Set[:$or, :$and, :$nor]
|
8
10
|
|
9
11
|
def initialize(criteria_hash)
|
10
12
|
@criteria_hash = criteria_hash
|
@@ -11,12 +11,12 @@ module Plucky
|
|
11
11
|
if value.size == 1 && value.first.is_a?(Hash)
|
12
12
|
value.first
|
13
13
|
else
|
14
|
-
value.flatten
|
14
|
+
value.flatten.inject({}) {|acc, field| acc.merge(field => 1)}
|
15
15
|
end
|
16
16
|
when Symbol
|
17
|
-
|
17
|
+
{value => 1}
|
18
18
|
when String
|
19
|
-
value.split(',').
|
19
|
+
value.split(',').inject({}) { |acc, v| acc.merge(v.strip => 1) }
|
20
20
|
else
|
21
21
|
value
|
22
22
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Plucky
|
2
|
+
module Normalizers
|
3
|
+
class HashKey
|
4
|
+
|
5
|
+
def initialize(keys)
|
6
|
+
@keys = keys
|
7
|
+
end
|
8
|
+
|
9
|
+
# Public: Normalizes an options hash key
|
10
|
+
#
|
11
|
+
# key - The key to normalize
|
12
|
+
#
|
13
|
+
# Returns a Symbol.
|
14
|
+
def call(key)
|
15
|
+
@keys.fetch key.to_sym, key
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -5,9 +5,9 @@ require 'plucky/normalizers/sort_value'
|
|
5
5
|
module Plucky
|
6
6
|
module Normalizers
|
7
7
|
class OptionsHashValue
|
8
|
-
|
9
|
-
# Public: Initialize an OptionsHashValue.
|
10
|
-
#
|
8
|
+
|
9
|
+
# Public: Initialize an OptionsHashValue.
|
10
|
+
#
|
11
11
|
# args - The hash of arguments (default: {})
|
12
12
|
# :key_normalizer - The key normalizer to use, must respond to call
|
13
13
|
# :value_normalizers - Hash where key is name of options hash key
|
@@ -34,7 +34,7 @@ module Plucky
|
|
34
34
|
}
|
35
35
|
|
36
36
|
@value_normalizers = {
|
37
|
-
:
|
37
|
+
:projection => default_fields_value_normalizer,
|
38
38
|
:sort => default_sort_value_normalizer,
|
39
39
|
:limit => default_limit_value_normalizer,
|
40
40
|
:skip => default_skip_value_normalizer,
|
@@ -66,9 +66,7 @@ module Plucky
|
|
66
66
|
|
67
67
|
# Private
|
68
68
|
def default_sort_value_normalizer
|
69
|
-
Normalizers::SortValue.new({
|
70
|
-
:key_normalizer => @key_normalizer,
|
71
|
-
})
|
69
|
+
Normalizers::SortValue.new(:key_normalizer => Normalizers::HashKey.new({:id => :_id}))
|
72
70
|
end
|
73
71
|
|
74
72
|
# Private
|
@@ -21,7 +21,7 @@ module Plucky
|
|
21
21
|
if value.size == 1 && value[0].is_a?(String)
|
22
22
|
normalized_sort_piece(value[0])
|
23
23
|
else
|
24
|
-
value.compact.
|
24
|
+
value.compact.inject({}) { |acc, v| acc.merge(normalized_sort_piece(v)) }
|
25
25
|
end
|
26
26
|
else
|
27
27
|
normalized_sort_piece(value)
|
@@ -32,13 +32,15 @@ module Plucky
|
|
32
32
|
def normalized_sort_piece(value)
|
33
33
|
case value
|
34
34
|
when SymbolOperator
|
35
|
-
|
35
|
+
normalized_direction(value.field, value.operator)
|
36
36
|
when String
|
37
|
-
value.split(',').
|
38
|
-
normalized_direction(*piece.split(' '))
|
37
|
+
value.split(',').inject({}) do |acc, piece|
|
38
|
+
acc.merge(normalized_direction(*piece.split(' ')))
|
39
39
|
end
|
40
40
|
when Symbol
|
41
|
-
|
41
|
+
normalized_direction(value)
|
42
|
+
when Array
|
43
|
+
Hash[*value]
|
42
44
|
else
|
43
45
|
value
|
44
46
|
end
|
@@ -48,7 +50,7 @@ module Plucky
|
|
48
50
|
def normalized_direction(field, direction=nil)
|
49
51
|
direction ||= 'ASC'
|
50
52
|
direction = direction.upcase == 'ASC' ? 1 : -1
|
51
|
-
|
53
|
+
{@key_normalizer.call(field).to_s => direction}
|
52
54
|
end
|
53
55
|
end
|
54
56
|
end
|
data/lib/plucky/options_hash.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
require 'plucky/normalizers/
|
3
|
+
require 'plucky/normalizers/hash_key'
|
4
4
|
require 'plucky/normalizers/options_hash_value'
|
5
5
|
|
6
6
|
module Plucky
|
@@ -55,7 +55,7 @@ module Plucky
|
|
55
55
|
|
56
56
|
# Public
|
57
57
|
def fields?
|
58
|
-
!self[:
|
58
|
+
!self[:projection].nil?
|
59
59
|
end
|
60
60
|
|
61
61
|
# Public
|
@@ -82,7 +82,13 @@ module Plucky
|
|
82
82
|
# Private
|
83
83
|
def key_normalizer
|
84
84
|
@key_normalizer ||= @options.fetch(:key_normalizer) {
|
85
|
-
Normalizers::
|
85
|
+
Normalizers::HashKey.new({
|
86
|
+
:order => :sort,
|
87
|
+
:select => :projection,
|
88
|
+
:fields => :projection,
|
89
|
+
:offset => :skip,
|
90
|
+
:id => :_id,
|
91
|
+
})
|
86
92
|
}
|
87
93
|
end
|
88
94
|
|
data/lib/plucky/pagination.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'forwardable'
|
2
2
|
module Plucky
|
3
3
|
module Pagination
|
4
|
-
|
4
|
+
class Collection < Array
|
5
5
|
extend Forwardable
|
6
6
|
|
7
7
|
def_delegators :@paginator,
|
@@ -11,6 +11,15 @@ module Plucky
|
|
11
11
|
:skip, :limit,
|
12
12
|
:offset, :out_of_bounds?
|
13
13
|
|
14
|
+
def initialize(records, paginator)
|
15
|
+
replace records
|
16
|
+
@paginator = paginator
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(method, *args)
|
20
|
+
@paginator.send method, *args
|
21
|
+
end
|
22
|
+
|
14
23
|
# Public
|
15
24
|
def paginator(p=nil)
|
16
25
|
return @paginator if p.nil?
|
data/lib/plucky/query.rb
CHANGED
@@ -9,11 +9,12 @@ module Plucky
|
|
9
9
|
|
10
10
|
# Private
|
11
11
|
OptionKeys = Set[
|
12
|
-
:select, :offset, :order,
|
13
|
-
:fields, :skip, :limit, :sort, :hint, :snapshot, # Ruby Driver
|
12
|
+
:select, :offset, :order, :transformer, # MM
|
13
|
+
:projection, :fields, :skip, :limit, :sort, :hint, :snapshot, # Ruby Driver
|
14
14
|
:batch_size, :timeout, :max_scan, :return_key, # Ruby Driver
|
15
|
-
:
|
15
|
+
:show_disk_loc, :comment, :read, # Ruby Driver
|
16
16
|
:tag_sets, :acceptable_latency, # Ruby Driver
|
17
|
+
:max_time_ms, :no_cursor_timeout, :collation, :modifiers
|
17
18
|
]
|
18
19
|
|
19
20
|
attr_reader :criteria, :options, :collection
|
@@ -61,26 +62,26 @@ module Plucky
|
|
61
62
|
:skip => paginator.skip,
|
62
63
|
}).all
|
63
64
|
|
64
|
-
|
65
|
-
docs.paginator(paginator)
|
66
|
-
docs
|
65
|
+
Pagination::Collection.new(docs, paginator)
|
67
66
|
end
|
68
67
|
|
69
68
|
def find_each(opts={})
|
70
69
|
query = clone.amend(opts)
|
71
|
-
cursor = query.cursor
|
72
70
|
|
73
71
|
if block_given?
|
74
|
-
|
75
|
-
|
72
|
+
enumerator = query.enumerator
|
73
|
+
enumerator.each do |doc|
|
74
|
+
yield doc
|
75
|
+
end
|
76
|
+
enumerator
|
77
|
+
else
|
78
|
+
query.enumerator
|
76
79
|
end
|
77
|
-
|
78
|
-
cursor
|
79
80
|
end
|
80
81
|
|
81
82
|
def find_one(opts={})
|
82
|
-
query = clone.amend(opts)
|
83
|
-
query.
|
83
|
+
query = clone.amend(opts.merge(limit: -1))
|
84
|
+
query.enumerator.first
|
84
85
|
end
|
85
86
|
|
86
87
|
def find(*ids)
|
@@ -107,12 +108,12 @@ module Plucky
|
|
107
108
|
|
108
109
|
def remove(opts={}, driver_opts={})
|
109
110
|
query = clone.amend(opts)
|
110
|
-
query.collection.
|
111
|
+
query.collection.find(query.criteria_hash, driver_opts).delete_many
|
111
112
|
end
|
112
113
|
|
113
114
|
def count(opts={})
|
114
115
|
query = clone.amend(opts)
|
115
|
-
cursor = query.
|
116
|
+
cursor = query.view
|
116
117
|
cursor.count
|
117
118
|
end
|
118
119
|
|
@@ -121,8 +122,8 @@ module Plucky
|
|
121
122
|
query.collection.distinct(key, query.criteria_hash)
|
122
123
|
end
|
123
124
|
|
124
|
-
def
|
125
|
-
clone.tap { |query| query.options[:
|
125
|
+
def projection(*args)
|
126
|
+
clone.tap { |query| query.options[:projection] = *args }
|
126
127
|
end
|
127
128
|
|
128
129
|
def ignore(*args)
|
@@ -176,12 +177,27 @@ module Plucky
|
|
176
177
|
alias_method :exist?, :exists?
|
177
178
|
alias_method :filter, :where
|
178
179
|
alias_method :to_a, :all
|
180
|
+
alias_method :fields, :projection
|
179
181
|
end
|
180
182
|
include DSL
|
181
183
|
|
182
184
|
def update(document, driver_opts={})
|
183
185
|
query = clone
|
184
|
-
|
186
|
+
if driver_opts[:multi]
|
187
|
+
query.collection.find(query.criteria_hash).update_many(document, driver_opts)
|
188
|
+
else
|
189
|
+
query.collection.find(query.criteria_hash).update_one(document, driver_opts)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def insert(document_or_array, driver_opts={})
|
194
|
+
query = clone
|
195
|
+
|
196
|
+
if document_or_array.is_a?(Array)
|
197
|
+
query.collection.insert_many(document_or_array, driver_opts)
|
198
|
+
else
|
199
|
+
query.collection.insert_one(document_or_array, driver_opts)
|
200
|
+
end
|
185
201
|
end
|
186
202
|
|
187
203
|
def amend(opts={})
|
@@ -230,8 +246,27 @@ module Plucky
|
|
230
246
|
@options.to_hash
|
231
247
|
end
|
232
248
|
|
233
|
-
def
|
234
|
-
|
249
|
+
def view
|
250
|
+
driver_opts = options_hash.dup
|
251
|
+
driver_opts.delete :transformer
|
252
|
+
case driver_opts[:read]
|
253
|
+
when Hash
|
254
|
+
driver_opts[:read] = Mongo::ServerSelector.get(driver_opts[:read])
|
255
|
+
when Symbol
|
256
|
+
driver_opts[:read] = Mongo::ServerSelector.get(mode: driver_opts[:read])
|
257
|
+
else
|
258
|
+
raise "Unexpected read options: #{driver_opts[:read]} - expected hash or symbol"
|
259
|
+
end if driver_opts.has_key?(:read)
|
260
|
+
|
261
|
+
@collection.find(criteria_hash, driver_opts)
|
262
|
+
end
|
263
|
+
|
264
|
+
def enumerator
|
265
|
+
if transformer = options_hash[:transformer]
|
266
|
+
Transformer.new(view, transformer).to_enum
|
267
|
+
else
|
268
|
+
view.to_enum
|
269
|
+
end
|
235
270
|
end
|
236
271
|
|
237
272
|
private
|
@@ -259,7 +294,7 @@ module Plucky
|
|
259
294
|
def set_field_inclusion(fields, value)
|
260
295
|
fields_option = {}
|
261
296
|
fields.each { |field| fields_option[symbolized_key(field)] = value }
|
262
|
-
clone.tap { |query| query.options[:
|
297
|
+
clone.tap { |query| query.options[:projection] = fields_option }
|
263
298
|
end
|
264
299
|
end
|
265
300
|
end
|
data/lib/plucky/version.rb
CHANGED
data/plucky.gemspec
CHANGED
@@ -3,12 +3,11 @@ require File.expand_path('../lib/plucky/version', __FILE__)
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = 'plucky'
|
6
|
-
s.homepage = 'http://github.com/
|
6
|
+
s.homepage = 'http://github.com/mongomapper/plucky'
|
7
7
|
s.summary = 'Thin layer over the ruby driver that allows you to quickly grab hold of your data (pluck it!).'
|
8
8
|
s.require_path = 'lib'
|
9
|
-
s.
|
10
|
-
s.
|
11
|
-
s.email = ['nunemaker@gmail.com']
|
9
|
+
s.authors = ['John Nunemaker', 'Chris Heald', 'Scott Taylor']
|
10
|
+
s.email = ['nunemaker@gmail.com', 'cheald@gmail.com', 'scott@railsnewbie.com']
|
12
11
|
s.version = Plucky::Version
|
13
12
|
s.platform = Gem::Platform::RUBY
|
14
13
|
|
@@ -17,5 +16,5 @@ Gem::Specification.new do |s|
|
|
17
16
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
17
|
s.require_paths = ["lib"]
|
19
18
|
|
20
|
-
s.add_dependency 'mongo', '~>
|
19
|
+
s.add_dependency 'mongo', '~> 2.0'
|
21
20
|
end
|
data/script/bootstrap
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
#/ Usage: bootstrap [bundle options]
|
3
|
+
#/
|
4
|
+
#/ Bundle install the dependencies.
|
5
|
+
#/
|
6
|
+
#/ Examples:
|
7
|
+
#/
|
8
|
+
#/ bootstrap
|
9
|
+
#/ bootstrap --local
|
10
|
+
#/
|
11
|
+
|
12
|
+
set -e
|
13
|
+
cd $(dirname "$0")/..
|
14
|
+
|
15
|
+
[ "$1" = "--help" -o "$1" = "-h" -o "$1" = "help" ] && {
|
16
|
+
grep '^#/' <"$0"| cut -c4-
|
17
|
+
exit 0
|
18
|
+
}
|
19
|
+
|
20
|
+
rm -rf .bundle/{binstubs,config}
|
21
|
+
bundle install --binstubs .bundle/binstubs --path .bundle --quiet "$@"
|
data/script/release
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
#/ Usage: release
|
3
|
+
#/
|
4
|
+
#/ Tag the version in the repo and push the gem.
|
5
|
+
#/
|
6
|
+
|
7
|
+
set -e
|
8
|
+
cd $(dirname "$0")/..
|
9
|
+
|
10
|
+
[ "$1" = "--help" -o "$1" = "-h" -o "$1" = "help" ] && {
|
11
|
+
grep '^#/' <"$0"| cut -c4-
|
12
|
+
exit 0
|
13
|
+
}
|
14
|
+
|
15
|
+
gem_name=plucky
|
16
|
+
|
17
|
+
# Build a new gem archive.
|
18
|
+
rm -rf $gem_name-*.gem
|
19
|
+
gem build -q $gem_name.gemspec
|
20
|
+
|
21
|
+
# Make sure we're on the master branch.
|
22
|
+
(git branch | grep -q '* master') || {
|
23
|
+
echo "Only release from the master branch."
|
24
|
+
exit 1
|
25
|
+
}
|
26
|
+
|
27
|
+
# Figure out what version we're releasing.
|
28
|
+
tag=v`ls $gem_name-*.gem | sed "s/^$gem_name-\(.*\)\.gem$/\1/"`
|
29
|
+
|
30
|
+
echo "Releasing $tag"
|
31
|
+
|
32
|
+
# Make sure we haven't released this version before.
|
33
|
+
git fetch -t origin
|
34
|
+
|
35
|
+
(git tag -l | grep -q "$tag") && {
|
36
|
+
echo "Whoops, there's already a '${tag}' tag."
|
37
|
+
exit 1
|
38
|
+
}
|
39
|
+
|
40
|
+
# Tag it and bag it.
|
41
|
+
gem push $gem_name-*.gem && git tag "$tag" &&
|
42
|
+
git push origin master && git push origin "$tag"
|