plucky 0.5.2 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.bundle/config +4 -0
- data/.gitignore +3 -1
- data/.travis.yml +2 -2
- data/Gemfile +13 -3
- data/Guardfile +13 -0
- data/README.md +1 -1
- data/Rakefile +3 -17
- data/examples/query.rb +1 -1
- data/lib/plucky.rb +13 -0
- data/lib/plucky/criteria_hash.rb +85 -56
- data/lib/plucky/normalizers/criteria_hash_key.rb +17 -0
- data/lib/plucky/normalizers/criteria_hash_value.rb +83 -0
- data/lib/plucky/normalizers/fields_value.rb +26 -0
- data/lib/plucky/normalizers/integer.rb +19 -0
- data/lib/plucky/normalizers/options_hash_key.rb +23 -0
- data/lib/plucky/normalizers/options_hash_value.rb +85 -0
- data/lib/plucky/normalizers/sort_value.rb +55 -0
- data/lib/plucky/options_hash.rb +56 -85
- data/lib/plucky/pagination/decorator.rb +3 -2
- data/lib/plucky/pagination/paginator.rb +15 -6
- data/lib/plucky/query.rb +93 -51
- data/lib/plucky/version.rb +1 -1
- data/script/criteria_hash.rb +21 -0
- data/{test → spec}/helper.rb +12 -8
- data/spec/plucky/criteria_hash_spec.rb +166 -0
- data/spec/plucky/normalizers/criteria_hash_key_spec.rb +37 -0
- data/spec/plucky/normalizers/criteria_hash_value_spec.rb +193 -0
- data/spec/plucky/normalizers/fields_value_spec.rb +45 -0
- data/spec/plucky/normalizers/integer_spec.rb +24 -0
- data/spec/plucky/normalizers/options_hash_key_spec.rb +23 -0
- data/spec/plucky/normalizers/options_hash_value_spec.rb +99 -0
- data/spec/plucky/normalizers/sort_value_spec.rb +94 -0
- data/spec/plucky/options_hash_spec.rb +64 -0
- data/{test/plucky/pagination/test_decorator.rb → spec/plucky/pagination/decorator_spec.rb} +8 -10
- data/spec/plucky/pagination/paginator_spec.rb +118 -0
- data/spec/plucky/query_spec.rb +839 -0
- data/spec/plucky_spec.rb +68 -0
- data/{test/test_symbol_operator.rb → spec/symbol_operator_spec.rb} +14 -16
- data/spec/symbol_spec.rb +9 -0
- metadata +58 -23
- data/test/plucky/pagination/test_paginator.rb +0 -120
- data/test/plucky/test_criteria_hash.rb +0 -359
- data/test/plucky/test_options_hash.rb +0 -302
- data/test/plucky/test_query.rb +0 -843
- data/test/test_plucky.rb +0 -48
- data/test/test_symbol.rb +0 -11
data/.bundle/config
ADDED
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
@@ -4,14 +4,24 @@ gemspec
|
|
4
4
|
gem 'bson_ext', '~> 1.5'
|
5
5
|
gem 'rake'
|
6
6
|
|
7
|
+
group :performance do
|
8
|
+
gem 'perftools.rb', :require => 'perftools'
|
9
|
+
end
|
10
|
+
|
7
11
|
group(:debug) do
|
8
12
|
platforms(:mri_18) { gem 'ruby-debug' }
|
9
13
|
platforms(:mri_19) { gem 'ruby-debug19' }
|
10
14
|
end
|
11
15
|
|
12
16
|
group(:test) do
|
13
|
-
gem '
|
14
|
-
gem 'jnunemaker-matchy', '~> 0.4.0', :require => 'matchy'
|
15
|
-
gem 'mocha', '~> 0.9.8'
|
17
|
+
gem 'rspec'
|
16
18
|
gem 'log_buddy'
|
17
19
|
end
|
20
|
+
|
21
|
+
group(:guard) do
|
22
|
+
gem 'guard'
|
23
|
+
gem 'guard-rspec'
|
24
|
+
gem 'guard-bundler'
|
25
|
+
gem 'terminal-notifier-guard'
|
26
|
+
gem 'rb-fsevent'
|
27
|
+
end
|
data/Guardfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'bundler' do
|
5
|
+
watch('Gemfile')
|
6
|
+
watch(/^.+\.gemspec/)
|
7
|
+
end
|
8
|
+
|
9
|
+
guard 'rspec' do
|
10
|
+
watch(%r{^spec/.+_spec\.rb$})
|
11
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
12
|
+
watch('spec/helper.rb') { "spec" }
|
13
|
+
end
|
data/README.md
CHANGED
data/Rakefile
CHANGED
@@ -1,21 +1,7 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'rake'
|
3
|
-
require 'rake/testtask'
|
4
|
-
require File.expand_path('../lib/plucky/version', __FILE__)
|
5
|
-
|
6
1
|
require 'bundler'
|
7
2
|
Bundler::GemHelper.install_tasks
|
8
3
|
|
9
|
-
|
10
|
-
|
11
|
-
test.libs << 'lib' << 'test'
|
12
|
-
test.pattern = 'test/**/test_*.rb'
|
13
|
-
test.verbose = true
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
task :test do
|
18
|
-
Rake::Task['test:all'].invoke
|
19
|
-
end
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
RSpec::Core::RakeTask.new
|
20
6
|
|
21
|
-
task :default => :
|
7
|
+
task :default => :spec
|
data/examples/query.rb
CHANGED
data/lib/plucky.rb
CHANGED
@@ -13,6 +13,11 @@ module Plucky
|
|
13
13
|
# Array of finder DSL methods to delegate
|
14
14
|
Methods = Plucky::Query::DSL.instance_methods.sort.map(&:to_sym)
|
15
15
|
|
16
|
+
# Public: Converts value to object id if possible
|
17
|
+
#
|
18
|
+
# value - The value to attempt converation of
|
19
|
+
#
|
20
|
+
# Returns BSON::ObjectId or value
|
16
21
|
def self.to_object_id(value)
|
17
22
|
return value if value.is_a?(BSON::ObjectId)
|
18
23
|
return nil if value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
@@ -23,4 +28,12 @@ module Plucky
|
|
23
28
|
value
|
24
29
|
end
|
25
30
|
end
|
31
|
+
|
32
|
+
# Private
|
33
|
+
ModifierString = '$'
|
34
|
+
|
35
|
+
# Internal
|
36
|
+
def self.modifier?(key)
|
37
|
+
key.to_s[0, 1] == ModifierString
|
38
|
+
end
|
26
39
|
end
|
data/lib/plucky/criteria_hash.rb
CHANGED
@@ -1,51 +1,85 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
+
require 'set'
|
3
|
+
require 'plucky/normalizers/criteria_hash_value'
|
4
|
+
require 'plucky/normalizers/criteria_hash_key'
|
5
|
+
|
2
6
|
module Plucky
|
3
7
|
class CriteriaHash
|
4
|
-
|
8
|
+
# Private: The Hash that stores query criteria
|
9
|
+
attr_reader :source
|
10
|
+
|
11
|
+
# Private: The Hash that stores options
|
12
|
+
attr_reader :options
|
13
|
+
|
14
|
+
# Internal: Used to determine if criteria keys match simple id lookup.
|
15
|
+
SimpleIdQueryKeys = [:_id].to_set
|
5
16
|
|
6
|
-
|
17
|
+
# Internal: Used to determine if criteria keys match simple id and type
|
18
|
+
# lookup (for single collection inheritance).
|
19
|
+
SimpleIdAndTypeQueryKeys = [:_id, :_type].to_set
|
7
20
|
|
21
|
+
# Internal: Used to quickly check if it is possible that the
|
22
|
+
# criteria hash is simple.
|
23
|
+
SimpleQueryMaxSize = [SimpleIdQueryKeys.size, SimpleIdAndTypeQueryKeys.size].max
|
24
|
+
|
25
|
+
# Public
|
8
26
|
def initialize(hash={}, options={})
|
9
27
|
@source, @options = {}, options
|
10
28
|
hash.each { |key, value| self[key] = value }
|
11
29
|
end
|
12
30
|
|
13
|
-
def initialize_copy(
|
31
|
+
def initialize_copy(original)
|
14
32
|
super
|
15
33
|
@options = @options.dup
|
16
34
|
@source = @source.dup
|
17
|
-
each do |key, value|
|
35
|
+
@source.each do |key, value|
|
18
36
|
self[key] = value.clone if value.duplicable?
|
19
37
|
end
|
20
38
|
end
|
21
39
|
|
40
|
+
# Public
|
41
|
+
def [](key)
|
42
|
+
@source[key]
|
43
|
+
end
|
44
|
+
|
45
|
+
# Public
|
46
|
+
# The contents of this make me sad...need to clean it up
|
22
47
|
def []=(key, value)
|
23
48
|
normalized_key = normalized_key(key)
|
49
|
+
|
24
50
|
if key.is_a?(SymbolOperator)
|
25
|
-
operator = "$#{key.operator}"
|
51
|
+
operator = :"$#{key.operator}"
|
26
52
|
normalized_value = normalized_value(normalized_key, operator, value)
|
27
|
-
source[normalized_key] ||= {}
|
28
|
-
source[normalized_key][operator] = normalized_value
|
53
|
+
@source[normalized_key] ||= {}
|
54
|
+
@source[normalized_key][operator] = normalized_value
|
29
55
|
else
|
30
56
|
if key == :conditions
|
31
57
|
value.each { |k, v| self[k] = v }
|
32
58
|
else
|
33
59
|
normalized_value = normalized_value(normalized_key, normalized_key, value)
|
34
|
-
source[normalized_key] = normalized_value
|
60
|
+
@source[normalized_key] = normalized_value
|
35
61
|
end
|
36
62
|
end
|
37
63
|
end
|
38
64
|
|
65
|
+
# Public
|
66
|
+
def keys
|
67
|
+
@source.keys
|
68
|
+
end
|
69
|
+
|
70
|
+
# Public
|
39
71
|
def ==(other)
|
40
|
-
source == other.source
|
72
|
+
@source == other.source
|
41
73
|
end
|
42
74
|
|
75
|
+
# Public
|
43
76
|
def to_hash
|
44
|
-
source
|
77
|
+
@source
|
45
78
|
end
|
46
79
|
|
80
|
+
# Public and completely disgusting
|
47
81
|
def merge(other)
|
48
|
-
target = source.dup
|
82
|
+
target = @source.dup
|
49
83
|
other.source.each_key do |key|
|
50
84
|
value, other_value = target[key], other[key]
|
51
85
|
target[key] =
|
@@ -55,16 +89,20 @@ module Plucky
|
|
55
89
|
|
56
90
|
if value_is_hash && other_is_hash
|
57
91
|
value.update(other_value) do |key, old_value, new_value|
|
58
|
-
|
92
|
+
if old_value.is_a?(Hash) && new_value.is_a?(Hash)
|
93
|
+
self.class.new(old_value).merge(self.class.new(new_value)).to_hash
|
94
|
+
else
|
95
|
+
Array(old_value).concat(Array(new_value)).uniq
|
96
|
+
end
|
59
97
|
end
|
60
98
|
elsif value_is_hash && !other_is_hash
|
61
|
-
if modifier_key = value.keys.detect { |k| k
|
99
|
+
if modifier_key = value.keys.detect { |k| Plucky.modifier?(k) }
|
62
100
|
value[modifier_key].concat(Array(other_value)).uniq!
|
63
101
|
else
|
64
102
|
# kaboom! Array(value).concat(Array(other_value)).uniq
|
65
103
|
end
|
66
104
|
elsif other_is_hash && !value_is_hash
|
67
|
-
if modifier_key = other_value.keys.detect { |k| k
|
105
|
+
if modifier_key = other_value.keys.detect { |k| Plucky.modifier?(k) }
|
68
106
|
other_value[modifier_key].concat(Array(value)).uniq!
|
69
107
|
else
|
70
108
|
# kaboom! Array(value).concat(Array(other_value)).uniq
|
@@ -79,71 +117,62 @@ module Plucky
|
|
79
117
|
self.class.new(target)
|
80
118
|
end
|
81
119
|
|
120
|
+
# Public
|
82
121
|
def merge!(other)
|
83
122
|
merge(other).to_hash.each do |key, value|
|
84
123
|
self[key] = value
|
85
124
|
end
|
125
|
+
self
|
86
126
|
end
|
87
127
|
|
128
|
+
# Private
|
88
129
|
def object_ids
|
89
130
|
@options[:object_ids] ||= []
|
90
131
|
end
|
91
132
|
|
133
|
+
# Private
|
92
134
|
def object_ids=(value)
|
93
135
|
raise ArgumentError unless value.is_a?(Array)
|
94
136
|
@options[:object_ids] = value.flatten
|
95
137
|
end
|
96
138
|
|
97
|
-
# The definition of simple is querying by only _id or _id and _type.
|
139
|
+
# Public: The definition of simple is querying by only _id or _id and _type.
|
98
140
|
# If this is the case, you can use IdentityMap in library to not perform
|
99
141
|
# query and instead just return from map.
|
142
|
+
#
|
143
|
+
# Returns true or false
|
100
144
|
def simple?
|
145
|
+
return false if keys.size > SimpleQueryMaxSize
|
101
146
|
key_set = keys.to_set
|
102
|
-
key_set ==
|
147
|
+
key_set == SimpleIdQueryKeys || key_set == SimpleIdAndTypeQueryKeys
|
103
148
|
end
|
104
149
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
end
|
150
|
+
def object_id?(key)
|
151
|
+
object_ids.include?(key.to_sym)
|
152
|
+
end
|
109
153
|
|
110
|
-
|
111
|
-
|
112
|
-
|
154
|
+
# Private
|
155
|
+
def normalized_key(key)
|
156
|
+
key_normalizer.call(key)
|
157
|
+
end
|
113
158
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
159
|
+
# Private
|
160
|
+
def key_normalizer
|
161
|
+
@key_normalizer ||= @options.fetch(:key_normalizer) {
|
162
|
+
Normalizers::CriteriaHashKey.new
|
163
|
+
}
|
164
|
+
end
|
120
165
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
value.to_a
|
133
|
-
end
|
134
|
-
when Time
|
135
|
-
value.utc
|
136
|
-
when String
|
137
|
-
return Plucky.to_object_id(value) if object_id?(key)
|
138
|
-
value
|
139
|
-
when Hash
|
140
|
-
value.each { |k, v| value[k] = normalized_value(key, k, v) }
|
141
|
-
value
|
142
|
-
when Regexp
|
143
|
-
Regexp.new(value)
|
144
|
-
else
|
145
|
-
value
|
146
|
-
end
|
147
|
-
end
|
166
|
+
# Private
|
167
|
+
def normalized_value(parent_key, key, value)
|
168
|
+
value_normalizer.call(parent_key, key, value)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Private
|
172
|
+
def value_normalizer
|
173
|
+
@value_normalizer ||= @options.fetch(:value_normalizer) {
|
174
|
+
Normalizers::CriteriaHashValue.new(self)
|
175
|
+
}
|
176
|
+
end
|
148
177
|
end
|
149
178
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Plucky
|
2
|
+
module Normalizers
|
3
|
+
class CriteriaHashKey
|
4
|
+
# Public: Returns key normalized for Mongo
|
5
|
+
#
|
6
|
+
# key - The key to normalize
|
7
|
+
#
|
8
|
+
# Returns key as Symbol if possible, else key with no changes
|
9
|
+
def call(key)
|
10
|
+
key = key.to_sym if key.respond_to?(:to_sym)
|
11
|
+
return call(key.field) if key.respond_to?(:field)
|
12
|
+
return :_id if key == :id
|
13
|
+
key
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Plucky
|
2
|
+
module Normalizers
|
3
|
+
class CriteriaHashValue
|
4
|
+
|
5
|
+
# Internal: Used by normalized_value to determine if we need to run the
|
6
|
+
# value through another criteria hash to normalize it.
|
7
|
+
NestingOperators = [:$or, :$and, :$nor]
|
8
|
+
|
9
|
+
def initialize(criteria_hash)
|
10
|
+
@criteria_hash = criteria_hash
|
11
|
+
end
|
12
|
+
|
13
|
+
# Public: Returns value normalized for Mongo
|
14
|
+
#
|
15
|
+
# parent_key - The parent key if nested, otherwise same as key
|
16
|
+
# key - The key we are currently normalizing
|
17
|
+
# value - The value that should be normalized
|
18
|
+
#
|
19
|
+
# Returns value normalized for Mongo
|
20
|
+
def call(parent_key, key, value)
|
21
|
+
case value
|
22
|
+
when Array, Set
|
23
|
+
if object_id?(parent_key)
|
24
|
+
value = value.map { |v| to_object_id(v) }
|
25
|
+
end
|
26
|
+
|
27
|
+
if nesting_operator?(key)
|
28
|
+
value.map { |v| criteria_hash_class.new(v, options).to_hash }
|
29
|
+
elsif parent_key == key && !modifier?(key)
|
30
|
+
# we're not nested and not the value for a symbol operator
|
31
|
+
{:$in => value.to_a}
|
32
|
+
else
|
33
|
+
# we are a value for a symbol operator or nested hash
|
34
|
+
value.to_a
|
35
|
+
end
|
36
|
+
when Time
|
37
|
+
value.utc
|
38
|
+
when String
|
39
|
+
if object_id?(key)
|
40
|
+
return to_object_id(value)
|
41
|
+
end
|
42
|
+
value
|
43
|
+
when Hash
|
44
|
+
value.each { |k, v| value[k] = call(key, k, v) }
|
45
|
+
value
|
46
|
+
when Regexp
|
47
|
+
Regexp.new(value)
|
48
|
+
else
|
49
|
+
value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Private: Ensures value is object id if possible
|
54
|
+
def to_object_id(value)
|
55
|
+
Plucky.to_object_id(value)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Private: Returns class of provided criteria hash
|
59
|
+
def criteria_hash_class
|
60
|
+
@criteria_hash.class
|
61
|
+
end
|
62
|
+
|
63
|
+
# Private: Returns options of provided criteria hash
|
64
|
+
def options
|
65
|
+
@criteria_hash.options
|
66
|
+
end
|
67
|
+
|
68
|
+
# Private: Returns true or false if key should be converted to object id
|
69
|
+
def object_id?(key)
|
70
|
+
@criteria_hash.object_id?(key)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Private: Returns true or false if key is a nesting operator
|
74
|
+
def nesting_operator?(key)
|
75
|
+
NestingOperators.include?(key)
|
76
|
+
end
|
77
|
+
|
78
|
+
def modifier?(key)
|
79
|
+
Plucky.modifier?(key)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|