mark_mapper 0.0.1

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