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.
- data/LICENSE +22 -0
- data/README.md +34 -0
- data/Rakefile +47 -0
- data/bin/console +45 -0
- data/lib/mongomodel.rb +92 -0
- data/lib/mongomodel/attributes/mongo.rb +40 -0
- data/lib/mongomodel/attributes/store.rb +30 -0
- data/lib/mongomodel/attributes/typecasting.rb +51 -0
- data/lib/mongomodel/concerns/abstract_class.rb +17 -0
- data/lib/mongomodel/concerns/activemodel.rb +11 -0
- data/lib/mongomodel/concerns/associations.rb +103 -0
- data/lib/mongomodel/concerns/associations/base/association.rb +33 -0
- data/lib/mongomodel/concerns/associations/base/definition.rb +56 -0
- data/lib/mongomodel/concerns/associations/base/proxy.rb +58 -0
- data/lib/mongomodel/concerns/associations/belongs_to.rb +68 -0
- data/lib/mongomodel/concerns/associations/has_many_by_foreign_key.rb +159 -0
- data/lib/mongomodel/concerns/associations/has_many_by_ids.rb +175 -0
- data/lib/mongomodel/concerns/attribute_methods.rb +55 -0
- data/lib/mongomodel/concerns/attribute_methods/before_type_cast.rb +29 -0
- data/lib/mongomodel/concerns/attribute_methods/dirty.rb +35 -0
- data/lib/mongomodel/concerns/attribute_methods/protected.rb +127 -0
- data/lib/mongomodel/concerns/attribute_methods/query.rb +22 -0
- data/lib/mongomodel/concerns/attribute_methods/read.rb +29 -0
- data/lib/mongomodel/concerns/attribute_methods/write.rb +29 -0
- data/lib/mongomodel/concerns/attributes.rb +85 -0
- data/lib/mongomodel/concerns/callbacks.rb +294 -0
- data/lib/mongomodel/concerns/logging.rb +15 -0
- data/lib/mongomodel/concerns/pretty_inspect.rb +29 -0
- data/lib/mongomodel/concerns/properties.rb +69 -0
- data/lib/mongomodel/concerns/record_status.rb +42 -0
- data/lib/mongomodel/concerns/timestamps.rb +32 -0
- data/lib/mongomodel/concerns/validations.rb +38 -0
- data/lib/mongomodel/concerns/validations/associated.rb +46 -0
- data/lib/mongomodel/document.rb +20 -0
- data/lib/mongomodel/document/callbacks.rb +46 -0
- data/lib/mongomodel/document/dynamic_finders.rb +88 -0
- data/lib/mongomodel/document/finders.rb +82 -0
- data/lib/mongomodel/document/indexes.rb +91 -0
- data/lib/mongomodel/document/optimistic_locking.rb +48 -0
- data/lib/mongomodel/document/persistence.rb +143 -0
- data/lib/mongomodel/document/scopes.rb +161 -0
- data/lib/mongomodel/document/validations.rb +68 -0
- data/lib/mongomodel/document/validations/uniqueness.rb +78 -0
- data/lib/mongomodel/embedded_document.rb +42 -0
- data/lib/mongomodel/locale/en.yml +55 -0
- data/lib/mongomodel/support/collection.rb +109 -0
- data/lib/mongomodel/support/configuration.rb +35 -0
- data/lib/mongomodel/support/core_extensions.rb +10 -0
- data/lib/mongomodel/support/exceptions.rb +25 -0
- data/lib/mongomodel/support/mongo_options.rb +177 -0
- data/lib/mongomodel/support/types.rb +35 -0
- data/lib/mongomodel/support/types/array.rb +11 -0
- data/lib/mongomodel/support/types/boolean.rb +25 -0
- data/lib/mongomodel/support/types/custom.rb +38 -0
- data/lib/mongomodel/support/types/date.rb +20 -0
- data/lib/mongomodel/support/types/float.rb +13 -0
- data/lib/mongomodel/support/types/hash.rb +18 -0
- data/lib/mongomodel/support/types/integer.rb +13 -0
- data/lib/mongomodel/support/types/object.rb +21 -0
- data/lib/mongomodel/support/types/string.rb +9 -0
- data/lib/mongomodel/support/types/symbol.rb +9 -0
- data/lib/mongomodel/support/types/time.rb +12 -0
- data/lib/mongomodel/version.rb +3 -0
- data/spec/mongomodel/attributes/store_spec.rb +273 -0
- data/spec/mongomodel/concerns/activemodel_spec.rb +61 -0
- data/spec/mongomodel/concerns/associations/belongs_to_spec.rb +153 -0
- data/spec/mongomodel/concerns/associations/has_many_by_foreign_key_spec.rb +165 -0
- data/spec/mongomodel/concerns/associations/has_many_by_ids_spec.rb +192 -0
- data/spec/mongomodel/concerns/attribute_methods/before_type_cast_spec.rb +46 -0
- data/spec/mongomodel/concerns/attribute_methods/dirty_spec.rb +131 -0
- data/spec/mongomodel/concerns/attribute_methods/protected_spec.rb +86 -0
- data/spec/mongomodel/concerns/attribute_methods/query_spec.rb +27 -0
- data/spec/mongomodel/concerns/attribute_methods/read_spec.rb +52 -0
- data/spec/mongomodel/concerns/attribute_methods/write_spec.rb +43 -0
- data/spec/mongomodel/concerns/attributes_spec.rb +152 -0
- data/spec/mongomodel/concerns/callbacks_spec.rb +90 -0
- data/spec/mongomodel/concerns/logging_spec.rb +20 -0
- data/spec/mongomodel/concerns/pretty_inspect_spec.rb +68 -0
- data/spec/mongomodel/concerns/properties_spec.rb +29 -0
- data/spec/mongomodel/concerns/timestamps_spec.rb +170 -0
- data/spec/mongomodel/concerns/validations_spec.rb +159 -0
- data/spec/mongomodel/document/callbacks_spec.rb +80 -0
- data/spec/mongomodel/document/dynamic_finders_spec.rb +183 -0
- data/spec/mongomodel/document/finders_spec.rb +231 -0
- data/spec/mongomodel/document/indexes_spec.rb +121 -0
- data/spec/mongomodel/document/optimistic_locking_spec.rb +57 -0
- data/spec/mongomodel/document/persistence_spec.rb +319 -0
- data/spec/mongomodel/document/scopes_spec.rb +204 -0
- data/spec/mongomodel/document/validations/uniqueness_spec.rb +217 -0
- data/spec/mongomodel/document/validations_spec.rb +132 -0
- data/spec/mongomodel/document_spec.rb +74 -0
- data/spec/mongomodel/embedded_document_spec.rb +66 -0
- data/spec/mongomodel/mongomodel_spec.rb +33 -0
- data/spec/mongomodel/support/collection_spec.rb +248 -0
- data/spec/mongomodel/support/mongo_options_spec.rb +295 -0
- data/spec/mongomodel/support/property_spec.rb +83 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/specdoc.opts +6 -0
- data/spec/support/callbacks.rb +44 -0
- data/spec/support/helpers/define_class.rb +24 -0
- data/spec/support/helpers/specs_for.rb +11 -0
- data/spec/support/matchers/be_a_subclass_of.rb +5 -0
- data/spec/support/matchers/respond_to_boolean.rb +17 -0
- data/spec/support/matchers/run_callbacks.rb +20 -0
- data/spec/support/models.rb +23 -0
- data/spec/support/time.rb +6 -0
- 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
|