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.
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