plucky 0.5.2 → 0.6.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.
- 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
|