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,37 @@
1
+ require 'helper'
2
+
3
+ describe Plucky::Normalizers::CriteriaHashKey do
4
+ subject {
5
+ described_class.new
6
+ }
7
+
8
+ context "with a string" do
9
+ it "returns symbol" do
10
+ subject.call('foo').should eq(:foo)
11
+ end
12
+ end
13
+
14
+ context "with a symbol" do
15
+ it "returns symbol" do
16
+ subject.call(:foo).should eq(:foo)
17
+ end
18
+ end
19
+
20
+ context "with :id" do
21
+ it "returns :_id" do
22
+ subject.call(:id).should eq(:_id)
23
+ end
24
+ end
25
+
26
+ it "returns key if something weird" do
27
+ subject.call(['crazytown']).should eq(['crazytown'])
28
+ end
29
+
30
+ SymbolOperators.each do |operator|
31
+ context "with #{operator} symbol operator" do
32
+ it "returns field" do
33
+ subject.call(:age.send(operator)).should eq(:age)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,193 @@
1
+ require 'helper'
2
+
3
+ describe Plucky::Normalizers::CriteriaHashValue do
4
+ let(:criteria_hash) { Plucky::CriteriaHash.new }
5
+
6
+ subject {
7
+ described_class.new(criteria_hash)
8
+ }
9
+
10
+ context "with a string" do
11
+ it "leaves string values for string keys alone" do
12
+ subject.call(:foo, :foo, 'bar').should eq('bar')
13
+ end
14
+
15
+ context "that is actually an object id" do
16
+ it "converts string values to object ids for object id keys" do
17
+ criteria_hash.object_ids = [:_id]
18
+ id = BSON::ObjectId.new
19
+ subject.call(:_id, :_id, id.to_s).should eq(id)
20
+ end
21
+ end
22
+ end
23
+
24
+ context "with a time" do
25
+ it "converts times to utc" do
26
+ time = Time.now
27
+ actual = time
28
+ expected = time.utc
29
+ result = subject.call(:foo, :foo, actual)
30
+ result.should be_utc
31
+ result.should eq(expected)
32
+ end
33
+
34
+ it "leaves utc times alone" do
35
+ time = Time.now
36
+ actual = time.utc
37
+ expected = time.utc
38
+ result = subject.call(:foo, :foo, actual)
39
+ result.should be_utc
40
+ result.should eq(expected)
41
+ end
42
+ end
43
+
44
+ context "with an array" do
45
+ it "defaults to $in" do
46
+ actual = [1,2,3]
47
+ expected = {:$in => [1,2,3]}
48
+ subject.call(:foo, :foo, actual).should eq(expected)
49
+ end
50
+
51
+ it "does not double up $in" do
52
+ actual = [1, 2, 3]
53
+ expected = [1, 2, 3]
54
+ subject.call(:$in, :$in, actual).should eq(expected)
55
+ end
56
+
57
+ it "uses existing modifier if present" do
58
+ actual = {'$all' => [1,2,3]}
59
+ expected = {'$all' => [1,2,3]}
60
+ subject.call(:foo, :foo, actual).should eq(expected)
61
+
62
+ actual = {'$any' => [1,2,3]}
63
+ expected = {'$any' => [1,2,3]}
64
+ subject.call(:foo, :foo, actual).should eq(expected)
65
+ end
66
+
67
+ it "does not turn value to $in with $or key" do
68
+ actual = [{:numbers => 1}, {:numbers => 2}]
69
+ expected = [{:numbers => 1}, {:numbers => 2}]
70
+ subject.call(:$or, :$or, actual).should eq(expected)
71
+ end
72
+
73
+ it "does not turn value to $in with $and key" do
74
+ actual = [{:numbers => 1}, {:numbers => 2}]
75
+ expected = [{:numbers => 1}, {:numbers => 2}]
76
+ subject.call(:$and, :$and, actual).should eq(expected)
77
+ end
78
+
79
+ it "does not turn value to $in with $nor key" do
80
+ actual = [{:numbers => 1}, {:numbers => 2}]
81
+ expected = [{:numbers => 1}, {:numbers => 2}]
82
+ subject.call(:$nor, :$nor, actual).should eq(expected)
83
+ end
84
+
85
+ it "defaults to $in even with ObjectId keys" do
86
+ actual = [1,2,3]
87
+ expected = {:$in => [1,2,3]}
88
+ criteria_hash.object_ids = [:mistake_id]
89
+ subject.call(:mistake_id, :mistake_id, actual).should eq(expected)
90
+ end
91
+ end
92
+
93
+ context "with a set" do
94
+ it "defaults to $in and convert to array" do
95
+ actual = [1,2,3].to_set
96
+ expected = {:$in => [1,2,3]}
97
+ subject.call(:numbers, :numbers, actual).should eq(expected)
98
+ end
99
+
100
+ it "uses existing modifier if present and convert to array" do
101
+ actual = {'$all' => [1,2,3].to_set}
102
+ expected = {'$all' => [1,2,3]}
103
+ subject.call(:foo, :foo, actual).should eq(expected)
104
+
105
+ actual = {'$any' => [1,2,3].to_set}
106
+ expected = {'$any' => [1,2,3]}
107
+ subject.call(:foo, :foo, actual).should eq(expected)
108
+ end
109
+ end
110
+
111
+ context "with string object ids for string keys" do
112
+ let(:object_id) { BSON::ObjectId.new }
113
+
114
+ it "leaves string ids as strings" do
115
+ subject.call(:_id, :_id, object_id.to_s).should eq(object_id.to_s)
116
+ subject.call(:room_id, :room_id, object_id.to_s).should eq(object_id.to_s)
117
+ end
118
+ end
119
+
120
+ context "with string object ids for object id keys" do
121
+ let(:object_id) { BSON::ObjectId.new }
122
+
123
+ before do
124
+ criteria_hash.object_ids = [:_id, :room_id]
125
+ end
126
+
127
+ it "converts strings to object ids" do
128
+ subject.call(:_id, :_id, object_id.to_s).should eq(object_id)
129
+ subject.call(:room_id, :room_id, object_id.to_s).should eq(object_id)
130
+ end
131
+
132
+ context "nested with modifier" do
133
+ let(:oid1) { BSON::ObjectId.new }
134
+ let(:oid2) { BSON::ObjectId.new }
135
+ let(:oids) { [oid1.to_s, oid2.to_s] }
136
+
137
+ it "converts strings to object ids" do
138
+ actual = {:$in => oids}
139
+ expected = {:$in => [oid1, oid2]}
140
+ subject.call(:_id, :_id, actual).should eq(expected)
141
+ end
142
+
143
+ it "does not modify original array of string ids" do
144
+ subject.call(:_id, :_id, {:$in => oids})
145
+ oids.should == [oid1.to_s, oid2.to_s]
146
+ end
147
+ end
148
+ end
149
+
150
+ context "nested clauses" do
151
+ it "knows constant array of operators that take nested queries" do
152
+ described_class::NestingOperators.should == [:$or, :$and, :$nor]
153
+ end
154
+
155
+ described_class::NestingOperators.each do |operator|
156
+ context "with #{operator}" do
157
+ it "works with symbol operators" do
158
+ nested1 = {:age.gt => 12, :age.lt => 20}
159
+ translated1 = {:age => {:$gt => 12, :$lt => 20 }}
160
+ nested2 = {:type.nin => ['friend', 'enemy']}
161
+ translated2 = {:type => {:$nin => ['friend', 'enemy']}}
162
+ value = [nested1, nested2]
163
+ expected = [translated1, translated2]
164
+
165
+ subject.call(operator, operator, value).should eq(expected)
166
+ end
167
+
168
+ it "honors criteria hash options" do
169
+ nested = [{:post_id => '4f5ead6378fca23a13000001'}]
170
+ translated = [{:post_id => BSON::ObjectId.from_string('4f5ead6378fca23a13000001')}]
171
+ given = {operator.to_s => [nested]}
172
+
173
+ criteria_hash.object_ids = [:post_id]
174
+ subject.call(operator, operator, nested).should eq(translated)
175
+ end
176
+ end
177
+ end
178
+
179
+ context "doubly nested" do
180
+ it "works with symbol operators" do
181
+ nested1 = {:age.gt => 12, :age.lt => 20}
182
+ translated1 = {:age => {:$gt => 12, :$lt => 20}}
183
+ nested2 = {:type.nin => ['friend', 'enemy']}
184
+ translated2 = {:type => {:$nin => ['friend', 'enemy']}}
185
+ nested3 = {'$and' => [nested2]}
186
+ translated3 = {:$and => [translated2]}
187
+ expected = [translated1, translated3]
188
+
189
+ subject.call(:$or, :$or, [nested1, nested3]).should eq(expected)
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,45 @@
1
+ require 'helper'
2
+ require 'plucky/normalizers/fields_value'
3
+
4
+ describe Plucky::Normalizers::FieldsValue do
5
+ it "defaults to nil" do
6
+ subject.call(nil).should be_nil
7
+ end
8
+
9
+ it "returns nil if empty string" do
10
+ subject.call('').should be_nil
11
+ end
12
+
13
+ it "returns nil if empty array" do
14
+ subject.call([]).should be_nil
15
+ end
16
+
17
+ it "works with array" do
18
+ subject.call(['one', 'two']).should eq(['one', 'two'])
19
+ end
20
+
21
+ # Ruby 1.9.x was sending array [{:age => 20}], instead of hash.
22
+ it "works with array that has one hash" do
23
+ subject.call([{:age => 20}]).should eq({:age => 20})
24
+ end
25
+
26
+ it "flattens multi-dimensional array" do
27
+ subject.call([[:one, :two]]).should eq([:one, :two])
28
+ end
29
+
30
+ it "works with symbol" do
31
+ subject.call(:one).should eq([:one])
32
+ end
33
+
34
+ it "works with array of symbols" do
35
+ subject.call([:one, :two]).should eq([:one, :two])
36
+ end
37
+
38
+ it "works with hash" do
39
+ subject.call({:one => 1, :two => -1}).should eq({:one => 1, :two => -1})
40
+ end
41
+
42
+ it "converts comma separated list to array" do
43
+ subject.call('one, two').should eq(['one', 'two'])
44
+ end
45
+ end
@@ -0,0 +1,24 @@
1
+ require 'helper'
2
+ require 'plucky/normalizers/integer'
3
+
4
+ describe Plucky::Normalizers::Integer do
5
+ context "with nil" do
6
+ it "returns nil" do
7
+ subject.call(nil).should be_nil
8
+ end
9
+ end
10
+
11
+ context "with an integer" do
12
+ it "returns an integer" do
13
+ subject.call(1).should be(1)
14
+ subject.call(3232).should be(3232)
15
+ end
16
+ end
17
+
18
+ context "with a string" do
19
+ it "returns a string" do
20
+ subject.call('1').should be(1)
21
+ subject.call('3232').should be(3232)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ require 'helper'
2
+
3
+ describe Plucky::Normalizers::OptionsHashKey do
4
+ subject {
5
+ described_class.new
6
+ }
7
+
8
+ it "changes order to sort" do
9
+ subject.call(:order).should eq(:sort)
10
+ end
11
+
12
+ it "changes select to fields" do
13
+ subject.call(:select).should eq(:fields)
14
+ end
15
+
16
+ it "changes offset to skip" do
17
+ subject.call(:offset).should eq(:skip)
18
+ end
19
+
20
+ it "changes id to _id" do
21
+ subject.call(:id).should eq(:_id)
22
+ end
23
+ end
@@ -0,0 +1,99 @@
1
+ require 'helper'
2
+
3
+ describe Plucky::Normalizers::OptionsHashValue do
4
+ let(:key_normalizer) {
5
+ lambda { |key|
6
+ if key == :id
7
+ :_id
8
+ else
9
+ key.to_sym
10
+ end
11
+ }
12
+ }
13
+
14
+ let(:upcasing_normalizer) {
15
+ lambda { |value| value.to_s.upcase }
16
+ }
17
+
18
+ let(:default_arguments) {
19
+ {
20
+ :key_normalizer => key_normalizer,
21
+ }
22
+ }
23
+
24
+ subject {
25
+ described_class.new(default_arguments)
26
+ }
27
+
28
+ it "raises exception if missing key normalizer" do
29
+ expect {
30
+ described_class.new
31
+ }.to raise_error(ArgumentError, "Missing required key :key_normalizer")
32
+ end
33
+
34
+ it "allows injecting a new value normalizer" do
35
+ instance = described_class.new(default_arguments.merge({
36
+ :value_normalizers => {
37
+ :some_field => upcasing_normalizer,
38
+ }
39
+ }))
40
+
41
+ instance.call(:some_field, 'upcase me').should eq('UPCASE ME')
42
+ end
43
+
44
+ context "with :fields key" do
45
+ subject {
46
+ described_class.new(default_arguments.merge({
47
+ :value_normalizers => {
48
+ :fields => upcasing_normalizer
49
+ },
50
+ }))
51
+ }
52
+
53
+ it "calls the fields value normalizer" do
54
+ subject.call(:fields, :foo).should eq('FOO')
55
+ end
56
+ end
57
+
58
+ context "with :sort key" do
59
+ subject {
60
+ described_class.new(default_arguments.merge({
61
+ :value_normalizers => {
62
+ :sort => upcasing_normalizer
63
+ },
64
+ }))
65
+ }
66
+
67
+ it "calls the sort value normalizer" do
68
+ subject.call(:sort, :foo).should eq('FOO')
69
+ end
70
+ end
71
+
72
+ context "with :limit key" do
73
+ subject {
74
+ described_class.new(default_arguments.merge({
75
+ :value_normalizers => {
76
+ :limit => upcasing_normalizer
77
+ },
78
+ }))
79
+ }
80
+
81
+ it "calls the limit value normalizer" do
82
+ subject.call(:limit, :foo).should eq('FOO')
83
+ end
84
+ end
85
+
86
+ context "with :skip key" do
87
+ subject {
88
+ described_class.new(default_arguments.merge({
89
+ :value_normalizers => {
90
+ :skip => upcasing_normalizer
91
+ },
92
+ }))
93
+ }
94
+
95
+ it "calls the skip value normalizer" do
96
+ subject.call(:skip, :foo).should eq('FOO')
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,94 @@
1
+ require 'helper'
2
+ require 'plucky/normalizers/sort_value'
3
+
4
+ describe Plucky::Normalizers::SortValue do
5
+ let(:key_normalizer) {
6
+ lambda { |key| key == :id ? :_id : key.to_sym }
7
+ }
8
+
9
+ subject {
10
+ described_class.new({
11
+ :key_normalizer => key_normalizer,
12
+ })
13
+ }
14
+
15
+ it "raises exception if missing key normalizer" do
16
+ expect {
17
+ described_class.new
18
+ }.to raise_error(ArgumentError, "Missing required key :key_normalizer")
19
+ end
20
+
21
+ it "defaults to nil" do
22
+ subject.call(nil).should eq(nil)
23
+ end
24
+
25
+ it "works with natural order ascending" do
26
+ subject.call('$natural' => 1).should eq('$natural' => 1)
27
+ end
28
+
29
+ it "works with natural order descending" do
30
+ subject.call('$natural' => -1).should eq('$natural' => -1)
31
+ end
32
+
33
+ it "converts single ascending field (string)" do
34
+ subject.call('foo asc').should eq([['foo', 1]])
35
+ subject.call('foo ASC').should eq([['foo', 1]])
36
+ end
37
+
38
+ it "converts single descending field (string)" do
39
+ subject.call('foo desc').should eq([['foo', -1]])
40
+ subject.call('foo DESC').should eq([['foo', -1]])
41
+ end
42
+
43
+ it "converts multiple fields (string)" do
44
+ subject.call('foo desc, bar asc').should eq([['foo', -1], ['bar', 1]])
45
+ end
46
+
47
+ it "converts multiple fields and default no direction to ascending (string)" do
48
+ subject.call('foo desc, bar, baz').should eq([['foo', -1], ['bar', 1], ['baz', 1]])
49
+ end
50
+
51
+ it "converts symbol" do
52
+ subject.call(:name).should eq([['name', 1]])
53
+ end
54
+
55
+ it "converts operator" do
56
+ subject.call(:foo.desc).should eq([['foo', -1]])
57
+ end
58
+
59
+ it "converts array of operators" do
60
+ subject.call([:foo.desc, :bar.asc]).should eq([['foo', -1], ['bar', 1]])
61
+ end
62
+
63
+ it "converts array of symbols" do
64
+ subject.call([:first_name, :last_name]).should eq([['first_name', 1], ['last_name', 1]])
65
+ end
66
+
67
+ it "works with array and one string element" do
68
+ subject.call(['foo, bar desc']).should eq([['foo', 1], ['bar', -1]])
69
+ end
70
+
71
+ it "works with array of single array" do
72
+ subject.call([['foo', -1]]).should eq([['foo', -1]])
73
+ end
74
+
75
+ it "works with array of multiple arrays" do
76
+ subject.call([['foo', -1], ['bar', 1]]).should eq([['foo', -1], ['bar', 1]])
77
+ end
78
+
79
+ it "compacts nil values in array" do
80
+ subject.call([nil, :foo.desc]).should eq([['foo', -1]])
81
+ end
82
+
83
+ it "converts array with mix of values" do
84
+ subject.call([:foo.desc, 'bar']).should eq([['foo', -1], ['bar', 1]])
85
+ end
86
+
87
+ it "converts keys based on key normalizer" do
88
+ subject.call([:id.asc]).should eq([['_id', 1]])
89
+ end
90
+
91
+ it "converts string with $natural correctly" do
92
+ subject.call('$natural desc').should eq([['$natural', -1]])
93
+ end
94
+ end