mark_mapper 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (211) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.rdoc +39 -0
  4. data/examples/attr_accessible.rb +24 -0
  5. data/examples/attr_protected.rb +24 -0
  6. data/examples/cache_key.rb +26 -0
  7. data/examples/custom_types.rb +26 -0
  8. data/examples/identity_map.rb +30 -0
  9. data/examples/identity_map/automatic.rb +2 -0
  10. data/examples/keys.rb +42 -0
  11. data/examples/modifiers/set.rb +27 -0
  12. data/examples/plugins.rb +40 -0
  13. data/examples/querying.rb +39 -0
  14. data/examples/sample_app.rb +43 -0
  15. data/examples/scopes.rb +56 -0
  16. data/examples/validating/embedded_docs.rb +31 -0
  17. data/lib/mark_mapper.rb +125 -0
  18. data/lib/mark_mapper/config.rb +90 -0
  19. data/lib/mark_mapper/connection.rb +60 -0
  20. data/lib/mark_mapper/criteria_hash.rb +194 -0
  21. data/lib/mark_mapper/document.rb +46 -0
  22. data/lib/mark_mapper/embedded_document.rb +32 -0
  23. data/lib/mark_mapper/exceptions.rb +33 -0
  24. data/lib/mark_mapper/extensions/array.rb +27 -0
  25. data/lib/mark_mapper/extensions/boolean.rb +45 -0
  26. data/lib/mark_mapper/extensions/date.rb +29 -0
  27. data/lib/mark_mapper/extensions/duplicable.rb +86 -0
  28. data/lib/mark_mapper/extensions/float.rb +18 -0
  29. data/lib/mark_mapper/extensions/hash.rb +26 -0
  30. data/lib/mark_mapper/extensions/integer.rb +27 -0
  31. data/lib/mark_mapper/extensions/kernel.rb +11 -0
  32. data/lib/mark_mapper/extensions/nil_class.rb +18 -0
  33. data/lib/mark_mapper/extensions/object.rb +30 -0
  34. data/lib/mark_mapper/extensions/object_id.rb +18 -0
  35. data/lib/mark_mapper/extensions/set.rb +20 -0
  36. data/lib/mark_mapper/extensions/string.rb +31 -0
  37. data/lib/mark_mapper/extensions/symbol.rb +87 -0
  38. data/lib/mark_mapper/extensions/time.rb +29 -0
  39. data/lib/mark_mapper/locale/en.yml +5 -0
  40. data/lib/mark_mapper/middleware/identity_map.rb +41 -0
  41. data/lib/mark_mapper/normalizers/criteria_hash_key.rb +17 -0
  42. data/lib/mark_mapper/normalizers/criteria_hash_value.rb +66 -0
  43. data/lib/mark_mapper/normalizers/fields_value.rb +26 -0
  44. data/lib/mark_mapper/normalizers/hash_key.rb +19 -0
  45. data/lib/mark_mapper/normalizers/integer.rb +19 -0
  46. data/lib/mark_mapper/normalizers/options_hash_value.rb +83 -0
  47. data/lib/mark_mapper/normalizers/sort_value.rb +55 -0
  48. data/lib/mark_mapper/options_hash.rb +103 -0
  49. data/lib/mark_mapper/pagination.rb +6 -0
  50. data/lib/mark_mapper/pagination/collection.rb +32 -0
  51. data/lib/mark_mapper/pagination/paginator.rb +46 -0
  52. data/lib/mark_mapper/plugins.rb +22 -0
  53. data/lib/mark_mapper/plugins/accessible.rb +61 -0
  54. data/lib/mark_mapper/plugins/active_model.rb +18 -0
  55. data/lib/mark_mapper/plugins/associations.rb +96 -0
  56. data/lib/mark_mapper/plugins/associations/base.rb +98 -0
  57. data/lib/mark_mapper/plugins/associations/belongs_to_association.rb +63 -0
  58. data/lib/mark_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +35 -0
  59. data/lib/mark_mapper/plugins/associations/belongs_to_proxy.rb +52 -0
  60. data/lib/mark_mapper/plugins/associations/collection.rb +29 -0
  61. data/lib/mark_mapper/plugins/associations/embedded_collection.rb +44 -0
  62. data/lib/mark_mapper/plugins/associations/in_array_proxy.rb +133 -0
  63. data/lib/mark_mapper/plugins/associations/many_association.rb +63 -0
  64. data/lib/mark_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
  65. data/lib/mark_mapper/plugins/associations/many_documents_proxy.rb +142 -0
  66. data/lib/mark_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +32 -0
  67. data/lib/mark_mapper/plugins/associations/many_embedded_proxy.rb +24 -0
  68. data/lib/mark_mapper/plugins/associations/many_polymorphic_proxy.rb +14 -0
  69. data/lib/mark_mapper/plugins/associations/one_as_proxy.rb +22 -0
  70. data/lib/mark_mapper/plugins/associations/one_association.rb +48 -0
  71. data/lib/mark_mapper/plugins/associations/one_embedded_polymorphic_proxy.rb +30 -0
  72. data/lib/mark_mapper/plugins/associations/one_embedded_proxy.rb +44 -0
  73. data/lib/mark_mapper/plugins/associations/one_proxy.rb +95 -0
  74. data/lib/mark_mapper/plugins/associations/proxy.rb +138 -0
  75. data/lib/mark_mapper/plugins/associations/single_association.rb +46 -0
  76. data/lib/mark_mapper/plugins/caching.rb +21 -0
  77. data/lib/mark_mapper/plugins/callbacks.rb +42 -0
  78. data/lib/mark_mapper/plugins/clone.rb +24 -0
  79. data/lib/mark_mapper/plugins/counter_cache.rb +97 -0
  80. data/lib/mark_mapper/plugins/dirty.rb +61 -0
  81. data/lib/mark_mapper/plugins/document.rb +41 -0
  82. data/lib/mark_mapper/plugins/dumpable.rb +22 -0
  83. data/lib/mark_mapper/plugins/dynamic_querying.rb +45 -0
  84. data/lib/mark_mapper/plugins/dynamic_querying/dynamic_finder.rb +44 -0
  85. data/lib/mark_mapper/plugins/embedded_callbacks.rb +81 -0
  86. data/lib/mark_mapper/plugins/embedded_document.rb +53 -0
  87. data/lib/mark_mapper/plugins/equality.rb +23 -0
  88. data/lib/mark_mapper/plugins/identity_map.rb +144 -0
  89. data/lib/mark_mapper/plugins/indexable.rb +86 -0
  90. data/lib/mark_mapper/plugins/inspect.rb +16 -0
  91. data/lib/mark_mapper/plugins/keys.rb +470 -0
  92. data/lib/mark_mapper/plugins/keys/key.rb +134 -0
  93. data/lib/mark_mapper/plugins/keys/static.rb +45 -0
  94. data/lib/mark_mapper/plugins/logger.rb +18 -0
  95. data/lib/mark_mapper/plugins/modifiers.rb +140 -0
  96. data/lib/mark_mapper/plugins/pagination.rb +16 -0
  97. data/lib/mark_mapper/plugins/partial_updates.rb +77 -0
  98. data/lib/mark_mapper/plugins/persistence.rb +79 -0
  99. data/lib/mark_mapper/plugins/protected.rb +45 -0
  100. data/lib/mark_mapper/plugins/querying.rb +173 -0
  101. data/lib/mark_mapper/plugins/querying/decorated_markmapper_query.rb +75 -0
  102. data/lib/mark_mapper/plugins/rails.rb +79 -0
  103. data/lib/mark_mapper/plugins/rails/active_record_association_adapter.rb +33 -0
  104. data/lib/mark_mapper/plugins/sci.rb +82 -0
  105. data/lib/mark_mapper/plugins/scopes.rb +28 -0
  106. data/lib/mark_mapper/plugins/serialization.rb +109 -0
  107. data/lib/mark_mapper/plugins/timestamps.rb +29 -0
  108. data/lib/mark_mapper/plugins/touch.rb +18 -0
  109. data/lib/mark_mapper/plugins/userstamps.rb +18 -0
  110. data/lib/mark_mapper/plugins/validations.rb +96 -0
  111. data/lib/mark_mapper/query.rb +278 -0
  112. data/lib/mark_mapper/railtie.rb +52 -0
  113. data/lib/mark_mapper/railtie/database.rake +65 -0
  114. data/lib/mark_mapper/translation.rb +10 -0
  115. data/lib/mark_mapper/version.rb +4 -0
  116. data/lib/rails/generators/mark_mapper/config/config_generator.rb +37 -0
  117. data/lib/rails/generators/mark_mapper/config/templates/marklogic.yml +19 -0
  118. data/lib/rails/generators/mark_mapper/model/model_generator.rb +40 -0
  119. data/lib/rails/generators/mark_mapper/model/templates/model.rb +17 -0
  120. data/spec/config/mark_mapper.yml +6 -0
  121. data/spec/examples_spec.rb +25 -0
  122. data/spec/functional/accessible_spec.rb +198 -0
  123. data/spec/functional/associations/belongs_to_polymorphic_proxy_spec.rb +64 -0
  124. data/spec/functional/associations/belongs_to_proxy_spec.rb +255 -0
  125. data/spec/functional/associations/in_array_proxy_spec.rb +349 -0
  126. data/spec/functional/associations/many_documents_as_proxy_spec.rb +230 -0
  127. data/spec/functional/associations/many_documents_proxy_spec.rb +968 -0
  128. data/spec/functional/associations/many_embedded_polymorphic_proxy_spec.rb +238 -0
  129. data/spec/functional/associations/many_embedded_proxy_spec.rb +288 -0
  130. data/spec/functional/associations/many_polymorphic_proxy_spec.rb +302 -0
  131. data/spec/functional/associations/one_as_proxy_spec.rb +489 -0
  132. data/spec/functional/associations/one_embedded_polymorphic_proxy_spec.rb +207 -0
  133. data/spec/functional/associations/one_embedded_proxy_spec.rb +100 -0
  134. data/spec/functional/associations/one_proxy_spec.rb +406 -0
  135. data/spec/functional/associations_spec.rb +48 -0
  136. data/spec/functional/caching_spec.rb +75 -0
  137. data/spec/functional/callbacks_spec.rb +330 -0
  138. data/spec/functional/counter_cache_spec.rb +235 -0
  139. data/spec/functional/dirty_spec.rb +316 -0
  140. data/spec/functional/document_spec.rb +310 -0
  141. data/spec/functional/dumpable_spec.rb +24 -0
  142. data/spec/functional/dynamic_querying_spec.rb +75 -0
  143. data/spec/functional/embedded_document_spec.rb +316 -0
  144. data/spec/functional/equality_spec.rb +20 -0
  145. data/spec/functional/extensions_spec.rb +16 -0
  146. data/spec/functional/identity_map_spec.rb +483 -0
  147. data/spec/functional/keys_spec.rb +339 -0
  148. data/spec/functional/logger_spec.rb +20 -0
  149. data/spec/functional/modifiers_spec.rb +446 -0
  150. data/spec/functional/options_hash_spec.rb +41 -0
  151. data/spec/functional/pagination_spec.rb +89 -0
  152. data/spec/functional/partial_updates_spec.rb +530 -0
  153. data/spec/functional/protected_spec.rb +199 -0
  154. data/spec/functional/querying_spec.rb +984 -0
  155. data/spec/functional/rails_spec.rb +55 -0
  156. data/spec/functional/sci_spec.rb +374 -0
  157. data/spec/functional/scopes_spec.rb +204 -0
  158. data/spec/functional/static_keys_spec.rb +153 -0
  159. data/spec/functional/timestamps_spec.rb +97 -0
  160. data/spec/functional/touch_spec.rb +125 -0
  161. data/spec/functional/userstamps_spec.rb +46 -0
  162. data/spec/functional/validations_spec.rb +416 -0
  163. data/spec/quality_spec.rb +51 -0
  164. data/spec/spec_helper.rb +150 -0
  165. data/spec/support/matchers.rb +15 -0
  166. data/spec/support/models.rb +256 -0
  167. data/spec/symbol_operator_spec.rb +70 -0
  168. data/spec/symbol_spec.rb +9 -0
  169. data/spec/unit/associations/base_spec.rb +146 -0
  170. data/spec/unit/associations/belongs_to_association_spec.rb +30 -0
  171. data/spec/unit/associations/many_association_spec.rb +64 -0
  172. data/spec/unit/associations/one_association_spec.rb +48 -0
  173. data/spec/unit/associations/proxy_spec.rb +103 -0
  174. data/spec/unit/clone_spec.rb +79 -0
  175. data/spec/unit/config_generator_spec.rb +24 -0
  176. data/spec/unit/criteria_hash_spec.rb +218 -0
  177. data/spec/unit/document_spec.rb +251 -0
  178. data/spec/unit/dynamic_finder_spec.rb +125 -0
  179. data/spec/unit/embedded_document_spec.rb +676 -0
  180. data/spec/unit/equality_spec.rb +38 -0
  181. data/spec/unit/exceptions_spec.rb +12 -0
  182. data/spec/unit/extensions_spec.rb +368 -0
  183. data/spec/unit/identity_map_middleware_spec.rb +134 -0
  184. data/spec/unit/inspect_spec.rb +47 -0
  185. data/spec/unit/key_spec.rb +276 -0
  186. data/spec/unit/keys_spec.rb +155 -0
  187. data/spec/unit/mark_mapper_spec.rb +37 -0
  188. data/spec/unit/model_generator_spec.rb +45 -0
  189. data/spec/unit/normalizers/criteria_hash_key_spec.rb +37 -0
  190. data/spec/unit/normalizers/criteria_hash_value_spec.rb +200 -0
  191. data/spec/unit/normalizers/fields_value_spec.rb +45 -0
  192. data/spec/unit/normalizers/hash_key_spec.rb +15 -0
  193. data/spec/unit/normalizers/integer_spec.rb +24 -0
  194. data/spec/unit/normalizers/options_hash_value_spec.rb +99 -0
  195. data/spec/unit/normalizers/sort_value_spec.rb +98 -0
  196. data/spec/unit/options_hash_spec.rb +64 -0
  197. data/spec/unit/pagination/collection_spec.rb +30 -0
  198. data/spec/unit/pagination/paginator_spec.rb +118 -0
  199. data/spec/unit/pagination_spec.rb +11 -0
  200. data/spec/unit/plugins_spec.rb +89 -0
  201. data/spec/unit/query_spec.rb +837 -0
  202. data/spec/unit/rails_compatibility_spec.rb +40 -0
  203. data/spec/unit/rails_reflect_on_association_spec.rb +118 -0
  204. data/spec/unit/rails_spec.rb +188 -0
  205. data/spec/unit/serialization_spec.rb +169 -0
  206. data/spec/unit/serializers/json_serializer_spec.rb +218 -0
  207. data/spec/unit/serializers/xml_serializer_spec.rb +198 -0
  208. data/spec/unit/time_zones_spec.rb +44 -0
  209. data/spec/unit/translation_spec.rb +27 -0
  210. data/spec/unit/validations_spec.rb +588 -0
  211. metadata +307 -0
@@ -0,0 +1,98 @@
1
+ require 'spec_helper'
2
+ require 'mark_mapper/normalizers/sort_value'
3
+
4
+ describe MarkMapper::Normalizers::SortValue do
5
+ let(:key_normalizer) {
6
+ MarkMapper::Normalizers::HashKey.new({:id => :_id})
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 "doesn't convert keys like :sort to :order via key normalizer" do
92
+ subject.call(:order.asc).should eq([['order', 1]])
93
+ end
94
+
95
+ it "converts string with $natural correctly" do
96
+ subject.call('$natural desc').should eq([['$natural', -1]])
97
+ end
98
+ end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ describe MarkMapper::OptionsHash do
4
+ describe "#initialize_copy" do
5
+ before do
6
+ @original = described_class.new(:fields => {:name => true}, :sort => :name, :limit => 10)
7
+ @cloned = @original.clone
8
+ end
9
+
10
+ it "duplicates source hash" do
11
+ @cloned.source.should_not equal(@original.source)
12
+ end
13
+
14
+ it "clones duplicable? values" do
15
+ @cloned[:fields].should_not equal(@original[:fields])
16
+ @cloned[:sort].should_not equal(@original[:sort])
17
+ end
18
+ end
19
+
20
+ describe "#fields?" do
21
+ it "returns true if fields have been selected" do
22
+ described_class.new(:fields => :name).fields?.should be(true)
23
+ end
24
+
25
+ it "returns false if no fields have been selected" do
26
+ described_class.new.fields?.should be(false)
27
+ end
28
+ end
29
+
30
+ describe "#merge" do
31
+ before do
32
+ @o1 = described_class.new(:skip => 5, :sort => :name)
33
+ @o2 = described_class.new(:limit => 10, :skip => 15)
34
+ @merged = @o1.merge(@o2)
35
+ end
36
+
37
+ it "overrides options in first with options in second" do
38
+ @merged.should == described_class.new(:limit => 10, :skip => 15, :sort => :name)
39
+ end
40
+
41
+ it "returns new instance and not change either of the merged" do
42
+ @o1[:skip].should == 5
43
+ @o2[:sort].should be_nil
44
+ @merged.should_not equal(@o1)
45
+ @merged.should_not equal(@o2)
46
+ end
47
+ end
48
+
49
+ describe "#merge!" do
50
+ before do
51
+ @o1 = described_class.new(:skip => 5, :sort => :name)
52
+ @o2 = described_class.new(:limit => 10, :skip => 15)
53
+ @merged = @o1.merge!(@o2)
54
+ end
55
+
56
+ it "overrides options in first with options in second" do
57
+ @merged.should == described_class.new(:limit => 10, :skip => 15, :sort => :name)
58
+ end
59
+
60
+ it "just updates the first" do
61
+ @merged.should equal(@o1)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe MarkMapper::Pagination::Collection do
4
+ context "Object decorated with Collection with paginator set" do
5
+ before do
6
+ @object = [1, 2, 3, 4]
7
+ @object_id = @object.object_id
8
+ @paginator = MarkMapper::Pagination::Paginator.new(20, 2, 10)
9
+ end
10
+ subject { MarkMapper::Pagination::Collection.new(@object, @paginator) }
11
+
12
+ it "knows paginator" do
13
+ subject.paginator.should == @paginator
14
+ end
15
+
16
+ [:total_entries, :current_page, :per_page, :total_pages, :out_of_bounds?,
17
+ :previous_page, :next_page, :skip, :limit, :offset].each do |method|
18
+ it "delegates #{method} to paginator" do
19
+ subject.send(method).should == @paginator.send(method)
20
+ end
21
+ end
22
+
23
+ it "does not interfere with other methods on the object" do
24
+ @object.object_id.should == @object_id
25
+ @object.should == [1, 2, 3, 4]
26
+ @object.size.should == 4
27
+ @object.select { |o| o > 2 }.should == [3, 4]
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,118 @@
1
+ require 'spec_helper'
2
+
3
+ describe MarkMapper::Pagination::Paginator do
4
+ describe "#initialize" do
5
+ context "with total and page" do
6
+ before { @paginator = described_class.new(20, 2) }
7
+ subject { @paginator }
8
+
9
+ it "sets total" do
10
+ subject.total_entries.should == 20
11
+ end
12
+
13
+ it "sets page" do
14
+ subject.current_page.should == 2
15
+ end
16
+
17
+ it "defaults per_page to 25" do
18
+ subject.per_page.should == 25
19
+ end
20
+ end
21
+
22
+ context "with total, page and per_page" do
23
+ before { @paginator = described_class.new(20, 2, 10) }
24
+ subject { @paginator }
25
+
26
+ it "sets total" do
27
+ subject.total_entries.should == 20
28
+ end
29
+
30
+ it "sets page" do
31
+ subject.current_page.should == 2
32
+ end
33
+
34
+ it "sets per_page" do
35
+ subject.per_page.should == 10
36
+ end
37
+ end
38
+
39
+ context "with string values for total, page and per_page" do
40
+ before { @paginator = described_class.new('20', '2', '10') }
41
+ subject { @paginator }
42
+
43
+ it "sets total" do
44
+ subject.total_entries.should == 20
45
+ end
46
+
47
+ it "sets page" do
48
+ subject.current_page.should == 2
49
+ end
50
+
51
+ it "sets per_page" do
52
+ subject.per_page.should == 10
53
+ end
54
+ end
55
+
56
+ context "with page less than 1" do
57
+ before { @paginator = described_class.new(20, -2, 10) }
58
+ subject { @paginator }
59
+
60
+ it "sets page to 1" do
61
+ subject.current_page.should == 1
62
+ end
63
+ end
64
+ end
65
+
66
+ it "aliases limit to per_page" do
67
+ described_class.new(30, 2, 30).limit.should == 30
68
+ end
69
+
70
+ it "knows total number of pages" do
71
+ described_class.new(43, 2, 7).total_pages.should == 7
72
+ described_class.new(40, 2, 10).total_pages.should == 4
73
+ end
74
+
75
+ describe "#out_of_bounds?" do
76
+ it "returns true if current_page is greater than total_pages" do
77
+ described_class.new(2, 3, 1).should be_out_of_bounds
78
+ end
79
+
80
+ it "returns false if current page is less than total_pages" do
81
+ described_class.new(2, 1, 1).should_not be_out_of_bounds
82
+ end
83
+
84
+ it "returns false if current page equals total_pages" do
85
+ described_class.new(2, 2, 1).should_not be_out_of_bounds
86
+ end
87
+ end
88
+
89
+ describe "#previous_page" do
90
+ it "returns nil if there is no page less than current" do
91
+ described_class.new(2, 1, 1).previous_page.should be_nil
92
+ end
93
+
94
+ it "returns number less than current page if there is one" do
95
+ described_class.new(2, 2, 1).previous_page.should == 1
96
+ end
97
+ end
98
+
99
+ describe "#next_page" do
100
+ it "returns nil if no page greater than current page" do
101
+ described_class.new(2, 2, 1).next_page.should be_nil
102
+ end
103
+
104
+ it "returns number greater than current page if there is one" do
105
+ described_class.new(2, 1, 1).next_page.should == 2
106
+ end
107
+ end
108
+
109
+ describe "#skip" do
110
+ it "works" do
111
+ described_class.new(30, 3, 10).skip.should == 20
112
+ end
113
+
114
+ it "returns aliased to offset for will paginate" do
115
+ described_class.new(30, 3, 10).offset.should == 20
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Pagination" do
4
+ it "should default per_page to 25" do
5
+ Doc().per_page.should == 25
6
+ end
7
+
8
+ it "should allow overriding per_page" do
9
+ Doc() { def self.per_page; 1 end }.per_page.should == 1
10
+ end
11
+ end
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Plugins" do
4
+ it "should default plugins to empty array" do
5
+ Class.new { extend MarkMapper::Plugins }.plugins.should == []
6
+ end
7
+
8
+ context "a plugin" do
9
+ module MyConcern
10
+ extend ActiveSupport::Concern
11
+
12
+ included do
13
+ attr_accessor :from_concern
14
+ end
15
+
16
+ module ClassMethods
17
+ def class_foo
18
+ 'class_foo'
19
+ end
20
+ end
21
+
22
+ def instance_foo
23
+ 'instance_foo'
24
+ end
25
+ end
26
+
27
+ before do
28
+ @document = Class.new do
29
+ extend MarkMapper::Plugins
30
+ plugin MyConcern
31
+ end
32
+ end
33
+
34
+ it "should include instance methods" do
35
+ @document.new.instance_foo.should == 'instance_foo'
36
+ end
37
+
38
+ it "should extend class methods" do
39
+ @document.class_foo.should == 'class_foo'
40
+ end
41
+
42
+ it "should pass model to configure" do
43
+ @document.new.should respond_to(:from_concern)
44
+ end
45
+
46
+ it "should add plugin to plugins" do
47
+ @document.plugins.should include(MyConcern)
48
+ end
49
+
50
+ context "Document" do
51
+ before do
52
+ MarkMapper::Document.plugins.delete(MyConcern)
53
+ end
54
+
55
+ it 'should allow plugins on Document' do
56
+ MarkMapper::Document.plugin(MyConcern)
57
+ Doc().should respond_to(:class_foo)
58
+ Doc().new.should respond_to(:instance_foo)
59
+ end
60
+
61
+ it 'should add plugins to classes that include Document before they are added' do
62
+ article = Doc()
63
+ MarkMapper::Document.plugin(MyConcern)
64
+ article.should respond_to(:class_foo)
65
+ article.new.should respond_to(:instance_foo)
66
+ end
67
+ end
68
+
69
+ context "EmbeddedDocument" do
70
+ before do
71
+ MarkMapper::EmbeddedDocument.plugins.delete(MyConcern)
72
+ end
73
+
74
+ it 'should allow plugins on EmbeddedDocument' do
75
+ MarkMapper::EmbeddedDocument.plugin(MyConcern)
76
+ article = EDoc()
77
+ article.should respond_to(:class_foo)
78
+ article.new.should respond_to(:instance_foo)
79
+ end
80
+
81
+ it 'should add plugins to classes that include EmbeddedDocument before they are added' do
82
+ article = EDoc()
83
+ MarkMapper::EmbeddedDocument.plugin(MyConcern)
84
+ article.should respond_to(:class_foo)
85
+ article.new.should respond_to(:instance_foo)
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,837 @@
1
+ require 'spec_helper'
2
+
3
+ describe MarkMapper::Query do
4
+ before do
5
+ @chris = { "_id" => 'chris', "age" => 26, "name" => 'Chris' }
6
+ @steve = { "_id" => 'steve', "age" => 29, "name" => 'Steve' }
7
+ @john = { "_id" => 'john', "age" => 28, "name" => 'John' }
8
+
9
+ @collection = @database.collection("users")
10
+ @collection.drop
11
+ @collection.insert(@chris)
12
+ @collection.insert(@steve)
13
+ @collection.insert(@john)
14
+ end
15
+
16
+ context "#initialize" do
17
+ before { @query = described_class.new(@collection) }
18
+ subject { @query }
19
+
20
+ it "defaults options to options hash" do
21
+ @query.options.should be_instance_of(MarkMapper::OptionsHash)
22
+ end
23
+
24
+ it "defaults criteria to criteria hash" do
25
+ @query.criteria.should be_instance_of(MarkMapper::CriteriaHash)
26
+ end
27
+ end
28
+
29
+ context "#initialize_copy" do
30
+ before do
31
+ @original = described_class.new(@collection)
32
+ @cloned = @original.clone
33
+ end
34
+
35
+ it "duplicates options" do
36
+ @cloned.options.should_not equal(@original.options)
37
+ end
38
+
39
+ it "duplicates criteria" do
40
+ @cloned.criteria.should_not equal(@original.criteria)
41
+ end
42
+ end
43
+
44
+ context "#[]=" do
45
+ before { @query = described_class.new(@collection) }
46
+ subject { @query }
47
+
48
+ it "sets key on options for option" do
49
+ subject[:skip] = 1
50
+ subject[:skip].should == 1
51
+ end
52
+
53
+ it "sets key on criteria for criteria" do
54
+ subject[:foo] = 'bar'
55
+ subject[:foo].should == 'bar'
56
+ end
57
+ end
58
+
59
+ context "#find_each" do
60
+ it "returns a cursor" do
61
+ cursor = described_class.new(@collection).find_each
62
+ cursor.should be_instance_of(MarkLogic::Cursor)
63
+ end
64
+
65
+ it "works with and normalize criteria" do
66
+ cursor = described_class.new(@collection).find_each(:id.eq => ['john'])
67
+ cursor.to_a.should == [@john]
68
+ end
69
+
70
+ it "works with and normalize options" do
71
+ cursor = described_class.new(@collection).find_each(:order => :name.asc)
72
+ cursor.to_a.should == [@chris, @john, @steve]
73
+ end
74
+
75
+ it "yields elements to a block if given" do
76
+ yielded_elements = Set.new
77
+ described_class.new(@collection).find_each { |doc| yielded_elements << doc }
78
+ yielded_elements.should == [@chris, @john, @steve].to_set
79
+ end
80
+
81
+ it "is Ruby-like and returns a reset cursor if a block is given" do
82
+ cursor = described_class.new(@collection).find_each {}
83
+ cursor.should be_instance_of(MarkLogic::Cursor)
84
+ cursor.next.should be_instance_of(Hash)
85
+ end
86
+ end
87
+
88
+ context "#find_one" do
89
+ it "works with and normalize criteria" do
90
+ described_class.new(@collection).find_one(:id.eq => ['john']).should == @john
91
+ end
92
+
93
+ it "works with and normalize options" do
94
+ described_class.new(@collection).find_one(:order => :age.desc).should == @steve
95
+ end
96
+ end
97
+
98
+ context "#find" do
99
+ before do
100
+ @query = described_class.new(@collection)
101
+ end
102
+ subject { @query }
103
+
104
+ it "works with single id" do
105
+ @query.find('chris').should == @chris
106
+ end
107
+
108
+ it "works with multiple ids" do
109
+ @query.find('chris', 'john').should =~ [@chris, @john]
110
+ end
111
+
112
+ it "works with array of one id" do
113
+ @query.find(['chris']).should == [@chris]
114
+ end
115
+
116
+ it "works with array of ids" do
117
+ @query.find(['chris', 'john']).should =~ [@chris, @john]
118
+ end
119
+
120
+ it "ignores those not found" do
121
+ @query.find('john', 'frank').should == [@john]
122
+ end
123
+
124
+ it "returns nil for nil" do
125
+ @query.find(nil).should be_nil
126
+ end
127
+
128
+ it "returns nil for *nil" do
129
+ @query.find(*nil).should be_nil
130
+ end
131
+
132
+ it "normalizes if using object id" do
133
+ id = @collection.insert(:name => 'Frank')
134
+ @query.object_ids([:_id])
135
+ doc = @query.find(id.to_s)
136
+ doc['name'].should == 'Frank'
137
+ end
138
+ end
139
+
140
+ context "#per_page" do
141
+ it "defaults to 25" do
142
+ described_class.new(@collection).per_page.should == 25
143
+ end
144
+
145
+ it "is changeable and chainable" do
146
+ query = described_class.new(@collection)
147
+ query.per_page(10).per_page.should == 10
148
+ end
149
+ end
150
+
151
+ context "#paginate" do
152
+ before do
153
+ @query = described_class.new(@collection).sort(:age).per_page(1)
154
+ end
155
+ subject { @query }
156
+
157
+ it "defaults to page 1" do
158
+ subject.paginate.should == [@chris]
159
+ end
160
+
161
+ it "works with other pages" do
162
+ subject.paginate(:page => 2).should == [@john]
163
+ subject.paginate(:page => 3).should == [@steve]
164
+ end
165
+
166
+ it "works with string page number" do
167
+ subject.paginate(:page => '2').should == [@john]
168
+ end
169
+
170
+ it "allows changing per_page" do
171
+ subject.paginate(:per_page => 2).should == [@chris, @john]
172
+ end
173
+
174
+ it "decorates return value" do
175
+ docs = subject.paginate
176
+ docs.should respond_to(:paginator)
177
+ docs.should respond_to(:total_entries)
178
+ end
179
+
180
+ it "does not modify the original query" do
181
+ subject.paginate(:name => 'John')
182
+ subject[:page].should be_nil
183
+ subject[:per_page].should be_nil
184
+ subject[:name].should be_nil
185
+ end
186
+
187
+ it "allows total_entries overrides" do
188
+ docs = subject.paginate(:total_entries => 1)
189
+ docs.total_entries.should == 1
190
+ end
191
+
192
+ context "with options" do
193
+ before do
194
+ @result = @query.sort(:age).paginate(:age.gt => 27, :per_page => 10)
195
+ end
196
+ subject { @result }
197
+
198
+ it "only returns matching" do
199
+ subject.should == [@john, @steve]
200
+ end
201
+
202
+ it "correctly counts matching" do
203
+ subject.total_entries.should == 2
204
+ end
205
+ end
206
+ end
207
+
208
+ context "#all" do
209
+ it "works with no arguments" do
210
+ docs = described_class.new(@collection).all
211
+ docs.size.should == 3
212
+ docs.should include(@john)
213
+ docs.should include(@steve)
214
+ docs.should include(@chris)
215
+ end
216
+
217
+ it "works with and normalize criteria" do
218
+ docs = described_class.new(@collection).all(:id.eq => ['steve'])
219
+ docs.should == [@steve]
220
+ end
221
+
222
+ it "works with and normalize options" do
223
+ docs = described_class.new(@collection).all(:order => :name.asc)
224
+ docs.should == [@chris, @john, @steve]
225
+ end
226
+
227
+ it "does not modify original query object" do
228
+ query = described_class.new(@collection)
229
+ query.all(:name => 'Steve')
230
+ query[:name].should be_nil
231
+ end
232
+ end
233
+
234
+ context "#first" do
235
+ it "works with and normalize criteria" do
236
+ described_class.new(@collection).first(:age.lt => 27).should == @chris
237
+ end
238
+
239
+ it "works with and normalize options" do
240
+ described_class.new(@collection).first(:age.le => 29, :order => :name.desc).should == @steve
241
+ end
242
+
243
+ it "does not modify original query object" do
244
+ query = described_class.new(@collection)
245
+ query.first(:name => 'Steve')
246
+ query[:name].should be_nil
247
+ end
248
+ end
249
+
250
+ context "#last" do
251
+ it "works with and normalize criteria" do
252
+ described_class.new(@collection).last(:age.le => 29, :order => :name.asc).should == @steve
253
+ end
254
+
255
+ it "works with and normalize options" do
256
+ described_class.new(@collection).last(:age.le => 26, :order => :name.desc).should == @chris
257
+ end
258
+
259
+ it "uses _id if a sort key is not specified" do
260
+ described_class.new(@collection).last.should == [@steve, @chris, @john].sort {|a, b| a["_id"] <=> b["_id"] }.last
261
+ end
262
+
263
+ it "does not modify original query object" do
264
+ query = described_class.new(@collection)
265
+ query.last(:name => 'Steve')
266
+ query[:name].should be_nil
267
+ end
268
+ end
269
+
270
+ context "#count" do
271
+ it "works with no arguments" do
272
+ described_class.new(@collection).count.should == 3
273
+ end
274
+
275
+ it "works with and normalize criteria" do
276
+ described_class.new(@collection).count(:age.le => 28).should == 2
277
+ end
278
+
279
+ it "does not modify original query object" do
280
+ query = described_class.new(@collection)
281
+ query.count(:name => 'Steve')
282
+ query[:name].should be_nil
283
+ end
284
+ end
285
+
286
+ context "#size" do
287
+ it "works just like count without options" do
288
+ described_class.new(@collection).size.should == 3
289
+ end
290
+ end
291
+
292
+ # context "#distinct" do
293
+ # before do
294
+ # # same age as John
295
+ # @mark = { "_id" => 'mark', "age" => 28, "name" => 'Mark' }
296
+ # @collection.insert(@mark)
297
+ # end
298
+
299
+ # it "works with just a key" do
300
+ # described_class.new(@collection).distinct(:age).sort.should == [26, 28, 29]
301
+ # end
302
+
303
+ # it "works with criteria" do
304
+ # described_class.new(@collection).distinct(:age, :age.gt => 26).sort.should == [28, 29]
305
+ # end
306
+
307
+ # it "does not modify the original query object" do
308
+ # query = described_class.new(@collection)
309
+ # query.distinct(:age, :name => 'Mark').should == [28]
310
+ # query[:name].should be_nil
311
+ # end
312
+ # end
313
+
314
+ context "#remove" do
315
+ it "works with no arguments" do
316
+ lambda { described_class.new(@collection).remove }.should change { @collection.count }.by(-3)
317
+ end
318
+
319
+ it "works with and normalize criteria" do
320
+ lambda { described_class.new(@collection).remove(:age.le => 28) }.should change { @collection.count }
321
+ end
322
+
323
+ it "does not modify original query object" do
324
+ query = described_class.new(@collection)
325
+ query.remove(:name => 'Steve')
326
+ query[:name].should be_nil
327
+ end
328
+ end
329
+
330
+ # context "#update" do
331
+ # before do
332
+ # @query = described_class.new(@collection).where('_id' => 'john')
333
+ # end
334
+
335
+ # it "works with document" do
336
+ # @query.update('$set' => {'age' => 29})
337
+ # doc = @query.first('_id' => 'john')
338
+ # doc['age'].should be(29)
339
+ # end
340
+
341
+ # it "works with document and driver options" do
342
+ # @query.update({'$set' => {'age' => 30}}, :multi => true)
343
+ # @query.each do |doc|
344
+ # doc['age'].should be(30)
345
+ # end
346
+ # end
347
+ # end
348
+
349
+ context "#[]" do
350
+ it "returns value if key in criteria (symbol)" do
351
+ described_class.new(@collection, :count => 1)[:count].should == 1
352
+ end
353
+
354
+ it "returns value if key in criteria (string)" do
355
+ described_class.new(@collection, :count => 1)['count'].should == 1
356
+ end
357
+
358
+ it "returns nil if key not in criteria" do
359
+ described_class.new(@collection)[:count].should be_nil
360
+ end
361
+ end
362
+
363
+ context "#[]=" do
364
+ before { @query = described_class.new(@collection) }
365
+
366
+ it "sets the value of the given criteria key" do
367
+ @query[:count] = 1
368
+ @query[:count].should == 1
369
+ end
370
+
371
+ it "overwrites value if key already exists" do
372
+ @query[:count] = 1
373
+ @query[:count] = 2
374
+ @query[:count].should == 2
375
+ end
376
+
377
+ it "normalizes value" do
378
+ now = Time.now
379
+ @query[:published_at] = now
380
+ @query[:published_at].should == now.utc
381
+ end
382
+ end
383
+
384
+ # context "#fields" do
385
+ # before { @query = described_class.new(@collection) }
386
+ # subject { @query }
387
+
388
+ # it "works" do
389
+ # subject.fields(:name).first(:id => 'john').keys.should == ['_id', 'name']
390
+ # end
391
+
392
+ # it "returns new instance of query" do
393
+ # new_query = subject.fields(:name)
394
+ # new_query.should_not equal(subject)
395
+ # subject[:fields].should be_nil
396
+ # end
397
+
398
+ # it "works with hash" do
399
+ # subject.fields(:name => 0).
400
+ # first(:id => 'john').keys.sort.
401
+ # should == ['_id', 'age']
402
+ # end
403
+ # end
404
+
405
+ # context "#ignore" do
406
+ # before { @query = described_class.new(@collection) }
407
+ # subject { @query }
408
+
409
+ # it "includes a list of keys to ignore" do
410
+ # new_query = subject.ignore(:name).first(:id => 'john')
411
+ # new_query.keys.should == ['_id', 'age']
412
+ # end
413
+ # end
414
+
415
+ # context "#only" do
416
+ # before { @query = described_class.new(@collection) }
417
+ # subject { @query }
418
+
419
+ # it "includes a list of keys with others excluded" do
420
+ # new_query = subject.only(:name).first(:id => 'john')
421
+ # new_query.keys.should == ['_id', 'name']
422
+ # end
423
+
424
+ # end
425
+
426
+ context "#skip" do
427
+ before { @query = described_class.new(@collection) }
428
+ subject { @query }
429
+
430
+ it "works" do
431
+ subject.skip(2).all(:order => :age).should == [@steve]
432
+ end
433
+
434
+ it "sets skip option" do
435
+ subject.skip(5).options[:skip].should == 5
436
+ end
437
+
438
+ it "overrides existing skip" do
439
+ subject.skip(5).skip(10).options[:skip].should == 10
440
+ end
441
+
442
+ it "returns nil for nil" do
443
+ subject.skip.options[:skip].should be_nil
444
+ end
445
+
446
+ it "returns new instance of query" do
447
+ new_query = subject.skip(2)
448
+ new_query.should_not equal(subject)
449
+ subject[:skip].should be_nil
450
+ end
451
+
452
+ it "aliases to offset" do
453
+ subject.offset(5).options[:skip].should == 5
454
+ end
455
+ end
456
+
457
+ context "#limit" do
458
+ before { @query = described_class.new(@collection) }
459
+ subject { @query }
460
+
461
+ it "works" do
462
+ subject.limit(2).all(:order => :age).should == [@chris, @john]
463
+ end
464
+
465
+ it "sets limit option" do
466
+ subject.limit(5).options[:limit].should == 5
467
+ end
468
+
469
+ it "overwrites existing limit" do
470
+ subject.limit(5).limit(15).options[:limit].should == 15
471
+ end
472
+
473
+ it "returns new instance of query" do
474
+ new_query = subject.limit(2)
475
+ new_query.should_not equal(subject)
476
+ subject[:limit].should be_nil
477
+ end
478
+ end
479
+
480
+ context "#sort" do
481
+ before { @query = described_class.new(@collection) }
482
+ subject { @query }
483
+
484
+ it "works" do
485
+ subject.sort(:age).all.should == [@chris, @john, @steve]
486
+ subject.sort(:age.desc).all.should == [@steve, @john, @chris]
487
+ end
488
+
489
+ it "works with symbol operators" do
490
+ subject.sort(:foo.asc, :bar.desc).options[:sort].should == [['foo', 1], ['bar', -1]]
491
+ end
492
+
493
+ it "works with string" do
494
+ subject.sort('foo, bar desc').options[:sort].should == [['foo', 1], ['bar', -1]]
495
+ end
496
+
497
+ it "works with just a symbol" do
498
+ subject.sort(:foo).options[:sort].should == [['foo', 1]]
499
+ end
500
+
501
+ it "works with symbol descending" do
502
+ subject.sort(:foo.desc).options[:sort].should == [['foo', -1]]
503
+ end
504
+
505
+ it "works with multiple symbols" do
506
+ subject.sort(:foo, :bar).options[:sort].should == [['foo', 1], ['bar', 1]]
507
+ end
508
+
509
+ it "returns new instance of query" do
510
+ new_query = subject.sort(:name)
511
+ new_query.should_not equal(subject)
512
+ subject[:sort].should be_nil
513
+ end
514
+
515
+ it "is aliased to order" do
516
+ subject.order(:foo).options[:sort].should == [['foo', 1]]
517
+ subject.order(:foo, :bar).options[:sort].should == [['foo', 1], ['bar', 1]]
518
+ end
519
+ end
520
+
521
+ context "#reverse" do
522
+ before { @query = described_class.new(@collection) }
523
+ subject { @query }
524
+
525
+ it "works" do
526
+ subject.sort(:age).reverse.all.should == [@steve, @john, @chris]
527
+ end
528
+
529
+ it "does not error if no sort provided" do
530
+ expect {
531
+ subject.reverse
532
+ }.to_not raise_error
533
+ end
534
+
535
+ it "reverses the sort order" do
536
+ subject.sort('foo asc, bar desc').
537
+ reverse.options[:sort].should == [['foo', -1], ['bar', 1]]
538
+ end
539
+
540
+ it "returns new instance of query" do
541
+ sorted_query = subject.sort(:name)
542
+ new_query = sorted_query.reverse
543
+ new_query.should_not equal(sorted_query)
544
+ sorted_query[:sort].should == [['name', 1]]
545
+ end
546
+ end
547
+
548
+ context "#amend" do
549
+ it "normalizes and update options" do
550
+ described_class.new(@collection).amend(:order => :age.desc).options[:sort].should == [['age', -1]]
551
+ end
552
+
553
+ it "works with simple stuff" do
554
+ described_class.new(@collection).
555
+ amend(:foo => 'bar').
556
+ amend(:baz => 'wick').
557
+ criteria.source.should eq(:foo => 'bar', :baz => 'wick')
558
+ end
559
+ end
560
+
561
+ context "#where" do
562
+ before { @query = described_class.new(@collection) }
563
+ subject { @query }
564
+
565
+ it "works" do
566
+ subject.where(:age.lt => 29).where(:name => 'Chris').all.should == [@chris]
567
+ end
568
+
569
+ # it "works with literal regexp" do
570
+ # subject.where(:name => /^c/i).all.should == [@chris]
571
+ # end
572
+
573
+ it "updates criteria" do
574
+ subject.
575
+ where(:moo => 'cow').
576
+ where(:foo => 'bar').
577
+ criteria.source.should eq(:foo => 'bar', :moo => 'cow')
578
+ end
579
+
580
+ it "gets normalized" do
581
+ subject.
582
+ where(:moo => 'cow').
583
+ where(:foo.eq => ['bar']).
584
+ criteria.source.should eq(:moo => 'cow', :foo => {:$eq => ['bar']})
585
+ end
586
+
587
+ it "normalizes merged criteria" do
588
+ subject.
589
+ where(:foo => 'bar').
590
+ where(:foo => 'baz').
591
+ criteria.source.should eq({:foo => {:$eq => %w[bar baz]}})
592
+ end
593
+
594
+ it "returns new instance of query" do
595
+ new_query = subject.where(:name => 'John')
596
+ new_query.should_not equal(subject)
597
+ subject[:name].should be_nil
598
+ end
599
+ end
600
+
601
+ context "#filter" do
602
+ before { @query = described_class.new(@collection) }
603
+ subject { @query }
604
+
605
+ it "works the same as where" do
606
+ subject.filter(:age.lt => 29).filter(:name => 'Chris').all.should == [@chris]
607
+ end
608
+ end
609
+
610
+ context "#empty?" do
611
+ it "returns true if empty" do
612
+ @collection.remove
613
+ described_class.new(@collection).should be_empty
614
+ end
615
+
616
+ it "returns false if not empty" do
617
+ described_class.new(@collection).should_not be_empty
618
+ end
619
+ end
620
+
621
+ context "#exists?" do
622
+ it "returns true if found" do
623
+ described_class.new(@collection).exists?(:name => 'John').should be(true)
624
+ end
625
+
626
+ it "returns false if not found" do
627
+ described_class.new(@collection).exists?(:name => 'Billy Bob').should be(false)
628
+ end
629
+ end
630
+
631
+ context "#exist?" do
632
+ it "returns true if found" do
633
+ described_class.new(@collection).exist?(:name => 'John').should be(true)
634
+ end
635
+
636
+ it "returns false if not found" do
637
+ described_class.new(@collection).exist?(:name => 'Billy Bob').should be(false)
638
+ end
639
+ end
640
+
641
+ context "#include?" do
642
+ it "returns true if included" do
643
+ described_class.new(@collection).include?(@john).should be(true)
644
+ end
645
+
646
+ it "returns false if not included" do
647
+ described_class.new(@collection).include?(['_id', 'frankyboy']).should be(false)
648
+ end
649
+ end
650
+
651
+ context "#to_a" do
652
+ it "returns all documents the query matches" do
653
+ described_class.new(@collection).sort(:name).to_a.
654
+ should == [@chris, @john, @steve]
655
+
656
+ described_class.new(@collection).where(:name => 'John').sort(:name).to_a.
657
+ should == [@john]
658
+ end
659
+ end
660
+
661
+ context "#each" do
662
+ it "iterates through matching documents" do
663
+ docs = []
664
+ described_class.new(@collection).sort(:name).each do |doc|
665
+ docs << doc
666
+ end
667
+ docs.should == [@chris, @john, @steve]
668
+ end
669
+
670
+ it "returns a working enumerator" do
671
+ query = described_class.new(@collection)
672
+ query.each.methods.map(&:to_sym).include?(:group_by).should be(true)
673
+ query.each.next.should be_instance_of(Hash)
674
+ end
675
+ end
676
+
677
+ context "enumerables" do
678
+ it "works" do
679
+ query = described_class.new(@collection).sort(:name)
680
+ query.map { |doc| doc['name'] }.should == %w(Chris John Steve)
681
+ query.collect { |doc| doc['name'] }.should == %w(Chris John Steve)
682
+ query.detect { |doc| doc['name'] == 'John' }.should == @john
683
+ query.min { |a, b| a['age'] <=> b['age'] }.should == @chris
684
+ end
685
+ end
686
+
687
+ # context "#object_ids" do
688
+ # before { @query = described_class.new(@collection) }
689
+ # subject { @query }
690
+
691
+ # it "sets criteria's object_ids" do
692
+ # expect(subject.criteria).to receive(:object_ids=).with([:foo, :bar])
693
+ # subject.object_ids(:foo, :bar)
694
+ # end
695
+
696
+ # it "returns current object ids if keys argument is empty" do
697
+ # subject.object_ids(:foo, :bar)
698
+ # subject.object_ids.should == [:foo, :bar]
699
+ # end
700
+ # end
701
+
702
+ context "#merge" do
703
+ it "overwrites options" do
704
+ query1 = described_class.new(@collection, :skip => 5, :limit => 5)
705
+ query2 = described_class.new(@collection, :skip => 10, :limit => 10)
706
+ new_query = query1.merge(query2)
707
+ new_query.options[:skip].should == 10
708
+ new_query.options[:limit].should == 10
709
+ end
710
+
711
+ it "merges criteria" do
712
+ query1 = described_class.new(@collection, :foo => 'bar')
713
+ query2 = described_class.new(@collection, :foo => 'baz', :fent => 'wick')
714
+ new_query = query1.merge(query2)
715
+ new_query.criteria[:fent].should == 'wick'
716
+ new_query.criteria[:foo].should == {:$eq => %w[bar baz]}
717
+ end
718
+
719
+ it "does not affect either of the merged queries" do
720
+ query1 = described_class.new(@collection, :foo => 'bar', :limit => 5)
721
+ query2 = described_class.new(@collection, :foo => 'baz', :limit => 10)
722
+ new_query = query1.merge(query2)
723
+ query1[:foo].should == 'bar'
724
+ query1[:limit].should == 5
725
+ query2[:foo].should == 'baz'
726
+ query2[:limit].should == 10
727
+ end
728
+ end
729
+
730
+ context "Criteria/option auto-detection" do
731
+ it "knows :conditions are criteria" do
732
+ query = described_class.new(@collection, :conditions => {:foo => 'bar'})
733
+ query.criteria.source.should eq(:foo => 'bar')
734
+ query.options.keys.should_not include(:conditions)
735
+ end
736
+
737
+ {
738
+ :fields => ['foo'],
739
+ :sort => [['foo', 1]],
740
+ :skip => 0,
741
+ :limit => 0
742
+ }.each do |option, value|
743
+ it "knows #{option} is an option" do
744
+ query = described_class.new(@collection, option => value)
745
+ query.options[option].should == value
746
+ query.criteria.keys.should_not include(option)
747
+ end
748
+ end
749
+
750
+ it "knows select is an option and remove it from options" do
751
+ query = described_class.new(@collection, :select => 'foo')
752
+ query.options[:fields].should == ['foo']
753
+ query.criteria.keys.should_not include(:select)
754
+ query.options.keys.should_not include(:select)
755
+ end
756
+
757
+ it "knows order is an option and remove it from options" do
758
+ query = described_class.new(@collection, :order => 'foo')
759
+ query.options[:sort].should == [['foo', 1]]
760
+ query.criteria.keys.should_not include(:order)
761
+ query.options.keys.should_not include(:order)
762
+ end
763
+
764
+ it "knows offset is an option and remove it from options" do
765
+ query = described_class.new(@collection, :offset => 0)
766
+ query.options[:skip].should == 0
767
+ query.criteria.keys.should_not include(:offset)
768
+ query.options.keys.should_not include(:offset)
769
+ end
770
+
771
+ it "works with full range of things" do
772
+ query = described_class.new(@collection, {
773
+ :foo => 'bar',
774
+ :baz => true,
775
+ :sort => [['foo', 1]],
776
+ :fields => ['foo', 'baz'],
777
+ :limit => 10,
778
+ :skip => 10,
779
+ })
780
+ query.criteria.source.should eq(:foo => 'bar', :baz => true)
781
+ query.options.source.should eq({
782
+ :sort => [['foo', 1]],
783
+ :fields => ['foo', 'baz'],
784
+ :limit => 10,
785
+ :skip => 10,
786
+ })
787
+ end
788
+ end
789
+
790
+ # it "inspects pretty" do
791
+ # inspect = described_class.new(@collection, :baz => 'wick', :foo => 'bar').inspect
792
+ # inspect.should == '#<MarkMapper::Query baz: "wick", foo: "bar">'
793
+ # end
794
+
795
+ it "delegates simple? to criteria" do
796
+ query = described_class.new(@collection)
797
+ expect(query.criteria).to receive(:simple?)
798
+ query.simple?
799
+ end
800
+
801
+ it "delegates fields? to options" do
802
+ query = described_class.new(@collection)
803
+ expect(query.options).to receive(:fields?)
804
+ query.fields?
805
+ end
806
+
807
+ # context "#explain" do
808
+ # before { @query = described_class.new(@collection) }
809
+ # subject { @query }
810
+
811
+ # it "works" do
812
+ # explain = subject.where(:age.lt => 28).explain
813
+ # explain['cursor'].should == 'BasicCursor'
814
+ # explain['nscanned'].should == 3
815
+ # end
816
+ # end
817
+
818
+ context "Transforming documents" do
819
+ before do
820
+ transformer = lambda { |doc| @user_class.new(doc['_id'], doc['name'], doc['age']) }
821
+ @user_class = Struct.new(:id, :name, :age)
822
+ @query = described_class.new(@collection, :transformer => transformer)
823
+ end
824
+
825
+ it "works with find_one" do
826
+ result = @query.find_one('_id' => 'john')
827
+ result.should be_instance_of(@user_class)
828
+ end
829
+
830
+ it "works with find_each" do
831
+ results = @query.find_each
832
+ results.each do |result|
833
+ result.should be_instance_of(@user_class)
834
+ end
835
+ end
836
+ end
837
+ end