mongomodel 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 (108) hide show
  1. data/LICENSE +22 -0
  2. data/README.md +34 -0
  3. data/Rakefile +47 -0
  4. data/bin/console +45 -0
  5. data/lib/mongomodel.rb +92 -0
  6. data/lib/mongomodel/attributes/mongo.rb +40 -0
  7. data/lib/mongomodel/attributes/store.rb +30 -0
  8. data/lib/mongomodel/attributes/typecasting.rb +51 -0
  9. data/lib/mongomodel/concerns/abstract_class.rb +17 -0
  10. data/lib/mongomodel/concerns/activemodel.rb +11 -0
  11. data/lib/mongomodel/concerns/associations.rb +103 -0
  12. data/lib/mongomodel/concerns/associations/base/association.rb +33 -0
  13. data/lib/mongomodel/concerns/associations/base/definition.rb +56 -0
  14. data/lib/mongomodel/concerns/associations/base/proxy.rb +58 -0
  15. data/lib/mongomodel/concerns/associations/belongs_to.rb +68 -0
  16. data/lib/mongomodel/concerns/associations/has_many_by_foreign_key.rb +159 -0
  17. data/lib/mongomodel/concerns/associations/has_many_by_ids.rb +175 -0
  18. data/lib/mongomodel/concerns/attribute_methods.rb +55 -0
  19. data/lib/mongomodel/concerns/attribute_methods/before_type_cast.rb +29 -0
  20. data/lib/mongomodel/concerns/attribute_methods/dirty.rb +35 -0
  21. data/lib/mongomodel/concerns/attribute_methods/protected.rb +127 -0
  22. data/lib/mongomodel/concerns/attribute_methods/query.rb +22 -0
  23. data/lib/mongomodel/concerns/attribute_methods/read.rb +29 -0
  24. data/lib/mongomodel/concerns/attribute_methods/write.rb +29 -0
  25. data/lib/mongomodel/concerns/attributes.rb +85 -0
  26. data/lib/mongomodel/concerns/callbacks.rb +294 -0
  27. data/lib/mongomodel/concerns/logging.rb +15 -0
  28. data/lib/mongomodel/concerns/pretty_inspect.rb +29 -0
  29. data/lib/mongomodel/concerns/properties.rb +69 -0
  30. data/lib/mongomodel/concerns/record_status.rb +42 -0
  31. data/lib/mongomodel/concerns/timestamps.rb +32 -0
  32. data/lib/mongomodel/concerns/validations.rb +38 -0
  33. data/lib/mongomodel/concerns/validations/associated.rb +46 -0
  34. data/lib/mongomodel/document.rb +20 -0
  35. data/lib/mongomodel/document/callbacks.rb +46 -0
  36. data/lib/mongomodel/document/dynamic_finders.rb +88 -0
  37. data/lib/mongomodel/document/finders.rb +82 -0
  38. data/lib/mongomodel/document/indexes.rb +91 -0
  39. data/lib/mongomodel/document/optimistic_locking.rb +48 -0
  40. data/lib/mongomodel/document/persistence.rb +143 -0
  41. data/lib/mongomodel/document/scopes.rb +161 -0
  42. data/lib/mongomodel/document/validations.rb +68 -0
  43. data/lib/mongomodel/document/validations/uniqueness.rb +78 -0
  44. data/lib/mongomodel/embedded_document.rb +42 -0
  45. data/lib/mongomodel/locale/en.yml +55 -0
  46. data/lib/mongomodel/support/collection.rb +109 -0
  47. data/lib/mongomodel/support/configuration.rb +35 -0
  48. data/lib/mongomodel/support/core_extensions.rb +10 -0
  49. data/lib/mongomodel/support/exceptions.rb +25 -0
  50. data/lib/mongomodel/support/mongo_options.rb +177 -0
  51. data/lib/mongomodel/support/types.rb +35 -0
  52. data/lib/mongomodel/support/types/array.rb +11 -0
  53. data/lib/mongomodel/support/types/boolean.rb +25 -0
  54. data/lib/mongomodel/support/types/custom.rb +38 -0
  55. data/lib/mongomodel/support/types/date.rb +20 -0
  56. data/lib/mongomodel/support/types/float.rb +13 -0
  57. data/lib/mongomodel/support/types/hash.rb +18 -0
  58. data/lib/mongomodel/support/types/integer.rb +13 -0
  59. data/lib/mongomodel/support/types/object.rb +21 -0
  60. data/lib/mongomodel/support/types/string.rb +9 -0
  61. data/lib/mongomodel/support/types/symbol.rb +9 -0
  62. data/lib/mongomodel/support/types/time.rb +12 -0
  63. data/lib/mongomodel/version.rb +3 -0
  64. data/spec/mongomodel/attributes/store_spec.rb +273 -0
  65. data/spec/mongomodel/concerns/activemodel_spec.rb +61 -0
  66. data/spec/mongomodel/concerns/associations/belongs_to_spec.rb +153 -0
  67. data/spec/mongomodel/concerns/associations/has_many_by_foreign_key_spec.rb +165 -0
  68. data/spec/mongomodel/concerns/associations/has_many_by_ids_spec.rb +192 -0
  69. data/spec/mongomodel/concerns/attribute_methods/before_type_cast_spec.rb +46 -0
  70. data/spec/mongomodel/concerns/attribute_methods/dirty_spec.rb +131 -0
  71. data/spec/mongomodel/concerns/attribute_methods/protected_spec.rb +86 -0
  72. data/spec/mongomodel/concerns/attribute_methods/query_spec.rb +27 -0
  73. data/spec/mongomodel/concerns/attribute_methods/read_spec.rb +52 -0
  74. data/spec/mongomodel/concerns/attribute_methods/write_spec.rb +43 -0
  75. data/spec/mongomodel/concerns/attributes_spec.rb +152 -0
  76. data/spec/mongomodel/concerns/callbacks_spec.rb +90 -0
  77. data/spec/mongomodel/concerns/logging_spec.rb +20 -0
  78. data/spec/mongomodel/concerns/pretty_inspect_spec.rb +68 -0
  79. data/spec/mongomodel/concerns/properties_spec.rb +29 -0
  80. data/spec/mongomodel/concerns/timestamps_spec.rb +170 -0
  81. data/spec/mongomodel/concerns/validations_spec.rb +159 -0
  82. data/spec/mongomodel/document/callbacks_spec.rb +80 -0
  83. data/spec/mongomodel/document/dynamic_finders_spec.rb +183 -0
  84. data/spec/mongomodel/document/finders_spec.rb +231 -0
  85. data/spec/mongomodel/document/indexes_spec.rb +121 -0
  86. data/spec/mongomodel/document/optimistic_locking_spec.rb +57 -0
  87. data/spec/mongomodel/document/persistence_spec.rb +319 -0
  88. data/spec/mongomodel/document/scopes_spec.rb +204 -0
  89. data/spec/mongomodel/document/validations/uniqueness_spec.rb +217 -0
  90. data/spec/mongomodel/document/validations_spec.rb +132 -0
  91. data/spec/mongomodel/document_spec.rb +74 -0
  92. data/spec/mongomodel/embedded_document_spec.rb +66 -0
  93. data/spec/mongomodel/mongomodel_spec.rb +33 -0
  94. data/spec/mongomodel/support/collection_spec.rb +248 -0
  95. data/spec/mongomodel/support/mongo_options_spec.rb +295 -0
  96. data/spec/mongomodel/support/property_spec.rb +83 -0
  97. data/spec/spec.opts +6 -0
  98. data/spec/spec_helper.rb +21 -0
  99. data/spec/specdoc.opts +6 -0
  100. data/spec/support/callbacks.rb +44 -0
  101. data/spec/support/helpers/define_class.rb +24 -0
  102. data/spec/support/helpers/specs_for.rb +11 -0
  103. data/spec/support/matchers/be_a_subclass_of.rb +5 -0
  104. data/spec/support/matchers/respond_to_boolean.rb +17 -0
  105. data/spec/support/matchers/run_callbacks.rb +20 -0
  106. data/spec/support/models.rb +23 -0
  107. data/spec/support/time.rb +6 -0
  108. metadata +232 -0
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+
3
+ module MongoModel
4
+ specs_for(Document) do
5
+ it "should inherit from EmbeddedDocument" do
6
+ Document.ancestors.should include(EmbeddedDocument)
7
+ end
8
+
9
+ it "should have an id property" do
10
+ property = Document.properties[:id]
11
+ property.name.should == :id
12
+ property.as.should == '_id'
13
+ property.default(mock('instance')).should_not be_nil
14
+ end
15
+
16
+ describe "single collection inheritance" do
17
+ define_class(:Event, Document)
18
+ define_class(:SpecialEvent, :Event)
19
+ define_class(:VerySpecialEvent, :SpecialEvent)
20
+
21
+ let(:missing) do
22
+ e = Event.new
23
+ e.type = 'MissingClass'
24
+ e.save!
25
+ e
26
+ end
27
+
28
+ before(:each) do
29
+ @event = Event.create!
30
+ @special = SpecialEvent.create!
31
+ @very_special = VerySpecialEvent.create!
32
+ @missing = missing
33
+ end
34
+
35
+ it "should belong to the same collection as its parent" do
36
+ SpecialEvent.collection_name.should == Event.collection_name
37
+ VerySpecialEvent.collection_name.should == Event.collection_name
38
+ end
39
+
40
+ it "should be an instance of the correct class when loaded" do
41
+ Event.find(@event.id).should be_an_instance_of(Event)
42
+ Event.find(@special.id).should be_an_instance_of(SpecialEvent)
43
+ Event.find(@very_special.id).should be_an_instance_of(VerySpecialEvent)
44
+
45
+ SpecialEvent.find(@special.id).should be_an_instance_of(SpecialEvent)
46
+ SpecialEvent.find(@very_special.id).should be_an_instance_of(VerySpecialEvent)
47
+
48
+ VerySpecialEvent.find(@very_special.id).should be_an_instance_of(VerySpecialEvent)
49
+ end
50
+
51
+ it "should default to superclass type if type missing" do
52
+ Event.find(@missing.id).should be_an_instance_of(Event)
53
+ end
54
+
55
+ it "should consider document missing when finding from subclass using id of parent instance" do
56
+ lambda { SpecialEvent.find(@event.id) }.should raise_error(MongoModel::DocumentNotFound)
57
+ lambda { VerySpecialEvent.find(@special.id) }.should raise_error(MongoModel::DocumentNotFound)
58
+ end
59
+
60
+ describe "loading documents" do
61
+ it "should load all documents from root class" do
62
+ Event.all.should include(@event, @special, @very_special, @missing)
63
+ end
64
+
65
+ it "should only load subclass documents from subclass" do
66
+ SpecialEvent.all.should include(@special, @very_special)
67
+ SpecialEvent.all.should_not include(@event, @missing)
68
+
69
+ VerySpecialEvent.all.should == [@very_special]
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ module MongoModel
4
+ specs_for(EmbeddedDocument, Document) do
5
+ describe "equality" do
6
+ define_class(:DocumentA, described_class) do
7
+ property :name, String
8
+ end
9
+
10
+ define_class(:DocumentB, described_class) do
11
+ property :name, String
12
+ end
13
+
14
+ subject { DocumentA.new(:id => 'test', :name => 'Test') }
15
+
16
+ it "should be equal to another document of the same class with identical attributes" do
17
+ subject.should == DocumentA.new(:id => 'test', :name => 'Test')
18
+ end
19
+
20
+ it "should not be equal to another document of the same class with different attributes" do
21
+ subject.should_not == DocumentA.new(:id => 'test', :name => 'Different')
22
+ subject.should_not == DocumentA.new(:id => 'test', :name => 'Test', :special_attribute => 'Different')
23
+ end
24
+
25
+ it "should not be equal to another document of a different class with identical attributes" do
26
+ subject.should_not == DocumentB.new(:id => 'test', :name => 'Different')
27
+ end
28
+ end
29
+
30
+ it "should be an abstract class" do
31
+ described_class.should be_an_abstract_class
32
+ end
33
+
34
+ describe "subclasses" do
35
+ define_class(:TestDocument, described_class)
36
+
37
+ it "should not be an abstract class" do
38
+ TestDocument.should_not be_an_abstract_class
39
+ end
40
+ end
41
+ end
42
+
43
+ specs_for(EmbeddedDocument) do
44
+ describe "single collection inheritance" do
45
+ define_class(:Event, EmbeddedDocument)
46
+ define_class(:SpecialEvent, :Event)
47
+
48
+ define_class(:Parent, Document) do
49
+ property :event, Event
50
+ end
51
+
52
+ let(:event) { Event.new }
53
+ let(:special) { SpecialEvent.new }
54
+ let(:parent) { Parent.new(:event => special) }
55
+ let(:reloaded) { parent.save!; Parent.find(parent.id) }
56
+
57
+ it "should not typecast to parent type when assigning to property" do
58
+ parent.event.should be_an_instance_of(SpecialEvent)
59
+ end
60
+
61
+ it "should be an instance of the correct class when reloaded" do
62
+ reloaded.event.should be_an_instance_of(SpecialEvent)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe MongoModel do
4
+ describe "setting a custom database configuration" do
5
+ before(:each) do
6
+ MongoModel.configuration = {
7
+ 'host' => '127.0.0.1',
8
+ 'database' => 'mydb'
9
+ }
10
+ end
11
+
12
+ it "should should merge configuration with defaults" do
13
+ MongoModel.configuration.host.should == '127.0.0.1'
14
+ MongoModel.configuration.port.should == 27017
15
+ MongoModel.configuration.database.should == 'mydb'
16
+ end
17
+
18
+ it "should establish database connection to given database" do
19
+ database = MongoModel.database
20
+ connection = database.connection
21
+
22
+ connection.host.should == '127.0.0.1'
23
+ connection.port.should == 27017
24
+ database.name.should == 'mydb'
25
+ end
26
+ end
27
+
28
+ it "should have a logger accessor" do
29
+ logger = mock('logger')
30
+ MongoModel.logger = logger
31
+ MongoModel.logger.should == logger
32
+ end
33
+ end
@@ -0,0 +1,248 @@
1
+ require 'spec_helper'
2
+
3
+ module MongoModel
4
+ describe Collection do
5
+ define_class(:TestDocument, EmbeddedDocument)
6
+ let(:doc) { TestDocument.new }
7
+
8
+ subject { Collection }
9
+
10
+ it { should be_a_subclass_of(Array) }
11
+
12
+ it "should have type Object" do
13
+ subject.type.should == Object
14
+ end
15
+
16
+ it "should not show its type when inspecting" do
17
+ subject.inspect.should == "Collection"
18
+ end
19
+
20
+ it "should allow any object to be added to the collection" do
21
+ collection = subject.new
22
+ collection << 123
23
+ collection << "Hello"
24
+ collection << doc
25
+ collection.should == [123, "Hello", doc]
26
+ end
27
+
28
+ it "should convert to mongo representation" do
29
+ collection = subject.new([123, "Hello", doc])
30
+ collection.to_mongo.should == [123, "Hello", { "_type" => 'TestDocument' }]
31
+ end
32
+
33
+ it "should load from mongo representation" do
34
+ collection = subject.from_mongo([123, "Hello", { "_type" => 'TestDocument' }])
35
+ collection.should be_a(subject)
36
+ collection.should == [123, "Hello", doc]
37
+ end
38
+
39
+ it "should cache collection types" do
40
+ Collection[String].should equal(Collection[String])
41
+ Collection[TestDocument].should equal(Collection[TestDocument])
42
+ end
43
+
44
+ describe "a Collection of Strings" do
45
+ subject { Collection[String] }
46
+
47
+ it { should be_a_subclass_of(Collection) }
48
+ it { should be_a_subclass_of(Array) }
49
+
50
+ it "should have type String" do
51
+ subject.type.should == String
52
+ end
53
+
54
+ it "should show its type when inspecting" do
55
+ subject.inspect.should == "Collection[String]"
56
+ end
57
+
58
+ it "should convert to mongo representation" do
59
+ collection = subject.new(["a", "bcd", "efg"])
60
+ collection.to_mongo.should == ["a", "bcd", "efg"]
61
+ end
62
+
63
+ it "should load from mongo representation" do
64
+ collection = subject.from_mongo(["a", "bcd", "efg"])
65
+ collection.should be_a(subject)
66
+ collection.should == ["a", "bcd", "efg"]
67
+ end
68
+
69
+ describe "casting" do
70
+ subject { Collection[String].new }
71
+
72
+ it "should cast elements when instantiating" do
73
+ Collection[String].new(["abc", 123, 56.2]).should == ["abc", "123", "56.2"]
74
+ end
75
+
76
+ it "should cast elements on <<" do
77
+ subject << "abc"
78
+ subject << 123
79
+ subject << 56.2
80
+ subject.should == ["abc", "123", "56.2"]
81
+ end
82
+
83
+ it "should cast elements on []=" do
84
+ subject[0] = "abc"
85
+ subject[1] = 123
86
+ subject[2] = 56.2
87
+ subject.should == ["abc", "123", "56.2"]
88
+ end
89
+
90
+ it "should cast elements on +" do
91
+ result = subject + ["abc", 123, 56.2]
92
+ result.should be_an_instance_of(Collection[String])
93
+ result.should == ["abc", "123", "56.2"]
94
+ end
95
+
96
+ it "should cast elements on concat" do
97
+ subject.concat(["abc", 123, 56.2])
98
+ subject.should == ["abc", "123", "56.2"]
99
+ end
100
+
101
+ it "should cast elements on delete" do
102
+ subject.push("abc", 123, 56.2)
103
+ subject.delete(123)
104
+ subject.delete(56.2)
105
+ subject.should == ["abc"]
106
+ end
107
+
108
+ it "should cast elements on index" do
109
+ subject.push("abc", 123, 56.2)
110
+ subject.index(123).should == 1
111
+ subject.index(56.2).should == 2
112
+ end
113
+
114
+ it "should cast elements on insert" do
115
+ subject.insert(0, 56.2)
116
+ subject.insert(0, "abc")
117
+ subject.insert(1, 123)
118
+ subject.should == ["abc", "123", "56.2"]
119
+ end
120
+
121
+ it "should cast elements on push" do
122
+ subject.push("abc", 123, 56.2)
123
+ subject.should == ["abc", "123", "56.2"]
124
+ end
125
+
126
+ it "should cast elements on rindex" do
127
+ subject.push("abc", 123, 56.2, 123)
128
+ subject.rindex(123).should == 3
129
+ subject.rindex(56.2).should == 2
130
+ end
131
+
132
+ it "should cast elements on unshift" do
133
+ subject.unshift("abc")
134
+ subject.unshift(123, 56.2)
135
+ subject.should == ["123", "56.2", "abc"]
136
+ end
137
+
138
+ it "should cast elements on include?" do
139
+ subject.push("abc", 123, 56.2)
140
+ subject.should include(123)
141
+ subject.should include(56.2)
142
+ subject.should_not include(999)
143
+ end
144
+ end
145
+ end
146
+
147
+ describe "a Collection of embedded documents" do
148
+ subject { Collection[TestDocument] }
149
+
150
+ it { should be_a_subclass_of(Collection) }
151
+ it { should be_a_subclass_of(Array) }
152
+
153
+ it "should have type TestDocument" do
154
+ subject.type.should == TestDocument
155
+ end
156
+
157
+ it "should show its type when inspecting" do
158
+ subject.inspect.should == "Collection[TestDocument]"
159
+ end
160
+
161
+ it "should convert to mongo representation" do
162
+ collection = subject.new([doc])
163
+ collection.to_mongo.should == [{ "_type" => 'TestDocument' }]
164
+ end
165
+
166
+ it "should load from mongo representation" do
167
+ collection = subject.from_mongo([{ "_type" => 'TestDocument' }])
168
+ collection.should be_a(subject)
169
+ collection.should == [doc]
170
+ end
171
+ end
172
+
173
+ describe "a Collection of CustomClasses" do
174
+ subject { Collection[CustomClass] }
175
+
176
+ it { should be_a_subclass_of(Collection) }
177
+ it { should be_a_subclass_of(Array) }
178
+
179
+ it "should have type CustomClass" do
180
+ subject.type.should == CustomClass
181
+ end
182
+
183
+ it "should show its type when inspecting" do
184
+ subject.inspect.should == "Collection[CustomClass]"
185
+ end
186
+
187
+ it "should convert to mongo representation" do
188
+ collection = subject.new([CustomClass.new("abc"), CustomClass.new("123")])
189
+ collection.to_mongo.should == [{ :name => "abc" }, { :name => "123" }]
190
+ end
191
+
192
+ it "should load from mongo representation" do
193
+ collection = subject.from_mongo([{ :name => "abc" }, { :name => "123" }])
194
+ collection.should be_a(subject)
195
+ collection.should == [CustomClass.new("abc"), CustomClass.new("123")]
196
+ end
197
+ end
198
+ end
199
+
200
+ specs_for(Document, EmbeddedDocument) do
201
+ describe "defining a Collection property with default value" do
202
+ define_class(:TestDocument, described_class) do
203
+ property :test_collection, Collection[CustomClass], :default => ['abc', 'def']
204
+ end
205
+
206
+ subject { TestDocument.new }
207
+
208
+ it "should cast items to collection type" do
209
+ subject.test_collection.should == [CustomClass.new('abc'), CustomClass.new('def')]
210
+ end
211
+
212
+ it "should allow items to be added to collection" do
213
+ subject.test_collection << '123'
214
+ subject.test_collection.should == [CustomClass.new('abc'), CustomClass.new('def'), CustomClass.new('123')]
215
+ end
216
+
217
+ it "should not share collections between instances" do
218
+ subject.test_collection << '123'
219
+ TestDocument.new.test_collection.should_not == subject.test_collection
220
+ end
221
+ end
222
+
223
+ describe "with a Collection containing EmbeddedDocuments" do
224
+ define_class(:Embedded, EmbeddedDocument) do
225
+ property :number, Integer
226
+ end
227
+
228
+ define_class(:TestDocument, described_class) do
229
+ property :embedded, Embedded
230
+ property :embedded_collection, Collection[Embedded]
231
+ end
232
+
233
+ let(:embedded1) { Embedded.new(:number => 1) }
234
+ let(:embedded2) { Embedded.new(:number => 2) }
235
+ let(:embedded3) { Embedded.new(:number => 3) }
236
+
237
+ subject { TestDocument.new(:embedded => embedded1, :embedded_collection => [embedded2, embedded3]) }
238
+
239
+ it "should include the embedded properties in the embedded documents list" do
240
+ subject.embedded_documents.should include(embedded1)
241
+ end
242
+
243
+ it "should include the elements in the collection in the embedded documents list" do
244
+ subject.embedded_documents.should include(embedded2, embedded3)
245
+ end
246
+ end
247
+ end
248
+ end
@@ -0,0 +1,295 @@
1
+ require 'spec_helper'
2
+
3
+ module MongoModel
4
+ describe MongoOptions do
5
+ define_class(:TestDocument, Document)
6
+
7
+ shared_examples_for "options without conditions" do
8
+ it "should have an empty selector hash" do
9
+ subject.selector.should == {}
10
+ end
11
+ end
12
+
13
+ shared_examples_for "options with conditions only" do
14
+ it "should have an empty options hash" do
15
+ subject.options.should == {}
16
+ end
17
+ end
18
+
19
+ context "with blank options" do
20
+ subject { MongoOptions.new(TestDocument) }
21
+
22
+ it_should_behave_like "options without conditions"
23
+
24
+ it "should have an empty options hash" do
25
+ subject.options.should == {}
26
+ end
27
+ end
28
+
29
+ context "with basic conditions" do
30
+ subject { MongoOptions.new(TestDocument, :conditions => { :foo => 'bar' }) }
31
+
32
+ it_should_behave_like "options with conditions only"
33
+
34
+ it "should include the conditions in the selector" do
35
+ subject.selector.should == { :foo => 'bar' }
36
+ end
37
+ end
38
+
39
+ context "with conditions using an operator" do
40
+ subject { MongoOptions.new(TestDocument, :conditions => { :age.gt => 10 }) }
41
+
42
+ it_should_behave_like "options with conditions only"
43
+
44
+ it "should include the expanded conditions in the selector" do
45
+ subject.selector.should == { :age => { '$gt' => 10 } }
46
+ end
47
+ end
48
+
49
+ context "with conditions using a property" do
50
+ subject { MongoOptions.new(TestDocument, :conditions => { :id => '123' }) }
51
+
52
+ it_should_behave_like "options with conditions only"
53
+
54
+ it "should use the property as value in the selector" do
55
+ subject.selector.should == { '_id' => '123' }
56
+ end
57
+ end
58
+
59
+ context "with basic options (no conditions or order)" do
60
+ subject { MongoOptions.new(TestDocument, :offset => 20, :limit => 10, :select => [ :foo, :bar ]) }
61
+
62
+ it_should_behave_like "options without conditions"
63
+
64
+ it "should include converted options in options hash" do
65
+ subject.options.should == { :skip => 20, :limit => 10, :fields => [ :foo, :bar ]}
66
+ end
67
+ end
68
+
69
+ context "with string order" do
70
+ subject { MongoOptions.new(TestDocument, :order => 'foo DESC') }
71
+
72
+ it_should_behave_like "options without conditions"
73
+
74
+ it "should convert order to sort in options hash" do
75
+ subject.options.should == { :sort => [ ['foo', :descending] ] }
76
+ end
77
+ end
78
+
79
+ context "with symbol order" do
80
+ subject { MongoOptions.new(TestDocument, :order => :bar) }
81
+
82
+ it_should_behave_like "options without conditions"
83
+
84
+ it "should convert order to sort in options hash" do
85
+ subject.options.should == { :sort => [ ['bar', :ascending] ]}
86
+ end
87
+ end
88
+
89
+ context "with symbol(asc) order" do
90
+ subject { MongoOptions.new(TestDocument, :order => :bar.asc) }
91
+
92
+ it_should_behave_like "options without conditions"
93
+
94
+ it "should convert order to sort in options hash" do
95
+ subject.options.should == { :sort => [ ['bar', :ascending] ]}
96
+ end
97
+ end
98
+
99
+ context "with multiple orders in array" do
100
+ subject { MongoOptions.new(TestDocument, :order => ['foo ASC', :bar.desc]) }
101
+
102
+ it_should_behave_like "options without conditions"
103
+
104
+ it "should convert order to sort in options hash" do
105
+ subject.options.should == { :sort => [ ['foo', :ascending], ['bar', :descending]] }
106
+ end
107
+ end
108
+
109
+ context "with multiple orders in string" do
110
+ subject { MongoOptions.new(TestDocument, :order => 'foo DESC, baz') }
111
+
112
+ it_should_behave_like "options without conditions"
113
+
114
+ it "should convert order to sort in options hash" do
115
+ subject.options.should == { :sort => [ ['foo', :descending], ['baz', :ascending] ] }
116
+ end
117
+ end
118
+
119
+ context "with an order using a property" do
120
+ subject { MongoOptions.new(TestDocument, :order => :id.desc) }
121
+
122
+ it_should_behave_like "options without conditions"
123
+
124
+ it "should use property as value as sort column" do
125
+ subject.options.should == { :sort => [ ['_id', :descending] ] }
126
+ end
127
+ end
128
+
129
+ context "with conditions and options" do
130
+ subject { MongoOptions.new(TestDocument, :conditions => { :age => 18 }, :order => :id.desc, :limit => 5) }
131
+
132
+ it "should use conditions for selector" do
133
+ subject.selector.should == { :age => 18 }
134
+ end
135
+
136
+ it "should convert options" do
137
+ subject.options.should == { :sort => [ ['_id', :descending] ], :limit => 5 }
138
+ end
139
+
140
+ it "should convert to array" do
141
+ subject.to_a.should == [ subject.selector, subject.options ]
142
+ end
143
+ end
144
+ end
145
+
146
+ describe MongoOrder do
147
+ def c(field, order)
148
+ MongoOrder::Clause.new(field, order)
149
+ end
150
+
151
+ subject { MongoOrder.new(c(:name, :ascending), c(:age, :descending)) }
152
+
153
+ it "should convert to string" do
154
+ subject.to_s.should == "name ascending, age descending"
155
+ end
156
+
157
+ describe "#to_sort" do
158
+ it "should convert to mongo sort array" do
159
+ model = mock('model', :properties => mock('properties', :[] => nil))
160
+ subject.to_sort(model).should == [['name', :ascending], ['age', :descending]]
161
+ end
162
+ end
163
+
164
+ it "should be reversable" do
165
+ subject.reverse.should == MongoOrder.new(c(:name, :descending), c(:age, :ascending))
166
+ end
167
+
168
+ it "should equal another order object with identical clauses" do
169
+ subject.should == MongoOrder.new(c(:name, :ascending), c(:age, :descending))
170
+ end
171
+
172
+ it "should equal another order object with different clauses" do
173
+ subject.should_not == MongoOrder.new(c(:name, :ascending))
174
+ subject.should_not == MongoOrder.new(c(:age, :ascending), c(:name, :ascending))
175
+ end
176
+
177
+ describe "#parse" do
178
+ it "should not change a MongoOrder" do
179
+ MongoOrder.parse(subject).should == subject
180
+ end
181
+
182
+ it "should convert individual clause to MongoOrder" do
183
+ MongoOrder.parse(c(:name, :ascending)).should == MongoOrder.new(c(:name, :ascending))
184
+ end
185
+
186
+ it "should convert symbol to MongoOrder" do
187
+ MongoOrder.parse(:name).should == MongoOrder.new(c(:name, :ascending))
188
+ end
189
+
190
+ it "should convert array of clauses to MongoOrder" do
191
+ MongoOrder.parse([c(:name, :ascending), c(:age, :descending)]).should == MongoOrder.new(c(:name, :ascending), c(:age, :descending))
192
+ end
193
+
194
+ it "should convert array of symbols to MongoOrder" do
195
+ MongoOrder.parse([:name, :age]).should == MongoOrder.new(c(:name, :ascending), c(:age, :ascending))
196
+ end
197
+
198
+ it "should convert array of strings to MongoOrder" do
199
+ MongoOrder.parse(['name ASC', 'age DESC']).should == MongoOrder.new(c(:name, :ascending), c(:age, :descending))
200
+ end
201
+
202
+ it "should convert string (no order specified) to MongoOrder" do
203
+ MongoOrder.parse('name').should == MongoOrder.new(c(:name, :ascending))
204
+ end
205
+
206
+ it "should convert string (single order) to MongoOrder" do
207
+ MongoOrder.parse('name DESC').should == MongoOrder.new(c(:name, :descending))
208
+ end
209
+
210
+ it "should convert string (multiple orders) to MongoOrder" do
211
+ MongoOrder.parse('name DESC, age ASC').should == MongoOrder.new(c(:name, :descending), c(:age, :ascending))
212
+ end
213
+ end
214
+ end
215
+
216
+ describe MongoOrder::Clause do
217
+ subject { MongoOrder::Clause.new(:name, :ascending) }
218
+
219
+ it "should convert to string" do
220
+ subject.to_s.should == "name ascending"
221
+ end
222
+
223
+ it "should equal another clause with the same field and order" do
224
+ subject.should == MongoOrder::Clause.new(:name, :ascending)
225
+ end
226
+
227
+ it "should equal another clause with a different field or order" do
228
+ subject.should_not == MongoOrder::Clause.new(:age, :ascending)
229
+ subject.should_not == MongoOrder::Clause.new(:name, :descending)
230
+ end
231
+
232
+ it "should be reversable" do
233
+ subject.reverse.should == MongoOrder::Clause.new(:name, :descending)
234
+ end
235
+
236
+ describe "#to_sort" do
237
+ context "given property" do
238
+ it "should use property as value to convert to mongo sort" do
239
+ property = mock('property', :as => '_name')
240
+ subject.to_sort(property).should == ['_name', :ascending]
241
+ end
242
+ end
243
+
244
+ context "given nil" do
245
+ it "should convert to mongo sort" do
246
+ subject.to_sort(nil).should == ['name', :ascending]
247
+ end
248
+ end
249
+ end
250
+
251
+ describe "#parse" do
252
+ let(:asc) { MongoOrder::Clause.new(:name, :ascending) }
253
+ let(:desc) { MongoOrder::Clause.new(:name, :descending) }
254
+
255
+ it "should create Clause from string (no order)" do
256
+ MongoOrder::Clause.parse('name').should == asc
257
+ end
258
+
259
+ it "should create Clause from string (with order)" do
260
+ MongoOrder::Clause.parse('name ASC').should == asc
261
+ MongoOrder::Clause.parse('name asc').should == asc
262
+ MongoOrder::Clause.parse('name ascending').should == asc
263
+ MongoOrder::Clause.parse('name DESC').should == desc
264
+ MongoOrder::Clause.parse('name desc').should == desc
265
+ MongoOrder::Clause.parse('name descending').should == desc
266
+ end
267
+ end
268
+ end
269
+
270
+ describe MongoOperator do
271
+ subject { MongoOperator.new(:age, :gt) }
272
+
273
+ it "should convert to mongo selector" do
274
+ subject.to_mongo_selector(14).should == { '$gt' => 14 }
275
+ end
276
+
277
+ it "should be equal to a MongoOperator with the same field and operator" do
278
+ subject.should == MongoOperator.new(:age, :gt)
279
+ end
280
+
281
+ it "should not be equal to a MongoOperator with a different field/operator" do
282
+ subject.should_not == MongoOperator.new(:age, :lte)
283
+ subject.should_not == MongoOperator.new(:date, :gt)
284
+ end
285
+
286
+ it "should be created from symbol methods" do
287
+ :age.gt.should == MongoOperator.new(:age, :gt)
288
+ :date.lte.should == MongoOperator.new(:date, :lte)
289
+ end
290
+
291
+ it "should be equal within a hash" do
292
+ { :age.gt => 10 }.should == { :age.gt => 10 }
293
+ end
294
+ end
295
+ end