plucky 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.bundle/config +4 -0
  2. data/.gitignore +3 -1
  3. data/.travis.yml +2 -2
  4. data/Gemfile +13 -3
  5. data/Guardfile +13 -0
  6. data/README.md +1 -1
  7. data/Rakefile +3 -17
  8. data/examples/query.rb +1 -1
  9. data/lib/plucky.rb +13 -0
  10. data/lib/plucky/criteria_hash.rb +85 -56
  11. data/lib/plucky/normalizers/criteria_hash_key.rb +17 -0
  12. data/lib/plucky/normalizers/criteria_hash_value.rb +83 -0
  13. data/lib/plucky/normalizers/fields_value.rb +26 -0
  14. data/lib/plucky/normalizers/integer.rb +19 -0
  15. data/lib/plucky/normalizers/options_hash_key.rb +23 -0
  16. data/lib/plucky/normalizers/options_hash_value.rb +85 -0
  17. data/lib/plucky/normalizers/sort_value.rb +55 -0
  18. data/lib/plucky/options_hash.rb +56 -85
  19. data/lib/plucky/pagination/decorator.rb +3 -2
  20. data/lib/plucky/pagination/paginator.rb +15 -6
  21. data/lib/plucky/query.rb +93 -51
  22. data/lib/plucky/version.rb +1 -1
  23. data/script/criteria_hash.rb +21 -0
  24. data/{test → spec}/helper.rb +12 -8
  25. data/spec/plucky/criteria_hash_spec.rb +166 -0
  26. data/spec/plucky/normalizers/criteria_hash_key_spec.rb +37 -0
  27. data/spec/plucky/normalizers/criteria_hash_value_spec.rb +193 -0
  28. data/spec/plucky/normalizers/fields_value_spec.rb +45 -0
  29. data/spec/plucky/normalizers/integer_spec.rb +24 -0
  30. data/spec/plucky/normalizers/options_hash_key_spec.rb +23 -0
  31. data/spec/plucky/normalizers/options_hash_value_spec.rb +99 -0
  32. data/spec/plucky/normalizers/sort_value_spec.rb +94 -0
  33. data/spec/plucky/options_hash_spec.rb +64 -0
  34. data/{test/plucky/pagination/test_decorator.rb → spec/plucky/pagination/decorator_spec.rb} +8 -10
  35. data/spec/plucky/pagination/paginator_spec.rb +118 -0
  36. data/spec/plucky/query_spec.rb +839 -0
  37. data/spec/plucky_spec.rb +68 -0
  38. data/{test/test_symbol_operator.rb → spec/symbol_operator_spec.rb} +14 -16
  39. data/spec/symbol_spec.rb +9 -0
  40. metadata +58 -23
  41. data/test/plucky/pagination/test_paginator.rb +0 -120
  42. data/test/plucky/test_criteria_hash.rb +0 -359
  43. data/test/plucky/test_options_hash.rb +0 -302
  44. data/test/plucky/test_query.rb +0 -843
  45. data/test/test_plucky.rb +0 -48
  46. data/test/test_symbol.rb +0 -11
@@ -0,0 +1,4 @@
1
+ ---
2
+ BUNDLE_BIN: bin
3
+ BUNDLE_DISABLE_SHARED_GEMS: "1"
4
+ BUNDLE_PATH: vendor/gems
data/.gitignore CHANGED
@@ -1,4 +1,6 @@
1
1
  *.project
2
2
  log
3
3
  *.gem
4
- Gemfile.lock
4
+ Gemfile.lock
5
+ bin
6
+ vendor
@@ -2,8 +2,8 @@ language: ruby
2
2
  rvm:
3
3
  - 1.8.7
4
4
  - ree
5
- - 1.9.2
6
5
  - 1.9.3
7
6
  notifications:
8
7
  email: false
9
- bundler_args: --without debug
8
+ bundler_args: --without debug guard performance
9
+ services: mongodb
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 'shoulda', '~> 2.11'
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
@@ -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
@@ -11,7 +11,7 @@ gem install plucky
11
11
  ## Examples
12
12
 
13
13
  ```ruby
14
- connection = Mongo::Connection.new
14
+ connection = Mongo::MongoClient.new
15
15
  db = connection.db('test')
16
16
  collection = db['users']
17
17
  collection.remove # clear out the collection
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
- namespace :test do
10
- Rake::TestTask.new(:all) do |test|
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 => :test
7
+ task :default => :spec
@@ -7,7 +7,7 @@ lib_path = root_path.join('lib')
7
7
  $:.unshift(lib_path)
8
8
  require 'plucky'
9
9
 
10
- connection = Mongo::Connection.new
10
+ connection = Mongo::MongoClient.new
11
11
  db = connection.db('test')
12
12
  collection = db['users']
13
13
  collection.remove # clear out the collection
@@ -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
@@ -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
- attr_reader :source, :options
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
- NestingOperators = [:$or, :$and, :$nor]
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(source)
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
- Array(old_value).concat(Array(new_value)).uniq
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.to_s[0, 1] == '$' }
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.to_s[0, 1] == '$' }
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 == [:_id].to_set || key_set == [:_id, :_type].to_set
147
+ key_set == SimpleIdQueryKeys || key_set == SimpleIdAndTypeQueryKeys
103
148
  end
104
149
 
105
- private
106
- def method_missing(method, *args, &block)
107
- @source.send(method, *args, &block)
108
- end
150
+ def object_id?(key)
151
+ object_ids.include?(key.to_sym)
152
+ end
109
153
 
110
- def object_id?(key)
111
- object_ids.include?(key.to_sym)
112
- end
154
+ # Private
155
+ def normalized_key(key)
156
+ key_normalizer.call(key)
157
+ end
113
158
 
114
- def normalized_key(key)
115
- key = key.to_sym if key.respond_to?(:to_sym)
116
- return normalized_key(key.field) if key.respond_to?(:field)
117
- return :_id if key == :id
118
- key
119
- end
159
+ # Private
160
+ def key_normalizer
161
+ @key_normalizer ||= @options.fetch(:key_normalizer) {
162
+ Normalizers::CriteriaHashKey.new
163
+ }
164
+ end
120
165
 
121
- def normalized_value(parent_key, key, value)
122
- case value
123
- when Array, Set
124
- value = value.map { |v| Plucky.to_object_id(v) } if object_id?(parent_key)
125
- if NestingOperators.include?(key)
126
- value.map { |v| CriteriaHash.new(v, options).to_hash }
127
- elsif parent_key == key
128
- # we're not nested and not the value for a symbol operator
129
- {'$in' => value.to_a}
130
- else
131
- # we are a value for a symbol operator or nested hash
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