light_mongo 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/util.rb ADDED
@@ -0,0 +1,20 @@
1
+ # Pulled with vigour from Rails 2.3.5
2
+ # and rewritten to be carbon neutral.
3
+ module LightMongo
4
+ class Util
5
+ def self.blank?(object)
6
+ return true if object.is_a? NilClass
7
+ return true if object.is_a? FalseClass
8
+ return false if object.is_a? TrueClass
9
+ return object.empty? if object.is_a? Array
10
+ return object.empty? if object.is_a? Hash
11
+ return object !~ /\S/ if object.is_a? String
12
+ return false if object.is_a? Numeric
13
+ return respond_to?(:empty?) ? empty? : !self
14
+ end
15
+
16
+ def self.present?(object)
17
+ !blank?(object)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,219 @@
1
+ require File.dirname(__FILE__) + '/../../lib/light_mongo'
2
+
3
+ describe LightMongo::Document::Persistence do
4
+ before(:all) do
5
+ LightMongo.stub!(:database => mock(:database, :connection => nil))
6
+ @test_class_collection = mock(:test_class_collection)
7
+ Mongo::Collection.stub!(:new => @test_class_collection)
8
+
9
+ class TestClass
10
+ include LightMongo::Document
11
+ attr_accessor :name
12
+ end
13
+ end
14
+
15
+ describe "the module's inclusion" do
16
+ it "sets up the collection on a class-level" do
17
+ TestClass.collection.should == @test_class_collection
18
+ end
19
+ end
20
+
21
+ describe "#save" do
22
+ before(:each) do
23
+ @test_object_hash = mock(:test_object_hash)
24
+ @test_object = TestClass.new
25
+ @test_object.stub!(:to_hash => @test_object_hash)
26
+ end
27
+
28
+ it "saves the Document to the collection for that class" do
29
+ @test_class_collection.should_receive(:save).with(@test_object_hash)
30
+ @test_object.save
31
+ end
32
+ end
33
+
34
+ describe ".create(params)" do
35
+ before(:each) do
36
+ @name = mock(:name)
37
+ @test_object = TestClass.new(:name => @name)
38
+ @test_object.stub!(:save)
39
+ TestClass.stub!(:new).with(:name => @name).and_return(@test_object)
40
+ end
41
+
42
+ it "spawns a new Ruby object from the params" do
43
+ TestClass.create(:name => @name).name.should == @name
44
+ end
45
+
46
+ it "saves the Ruby object to the database" do
47
+ @test_object.should_receive(:save)
48
+ TestClass.create(:name => @name)
49
+ end
50
+ end
51
+
52
+ describe "#id" do
53
+ before(:each) do
54
+ @id = mock(:id)
55
+ @test_object = TestClass.new(:_id => @id)
56
+ end
57
+
58
+ it "delegates to the Mongo _id" do
59
+ @test_object.id.should == @id
60
+ end
61
+ end
62
+
63
+ describe "#==(other)" do
64
+ before(:each) do
65
+ @id = mock(:id)
66
+ @object_1 = TestClass.new(:_id => @id)
67
+ @object_2 = TestClass.new(:_id => @id)
68
+ end
69
+
70
+ it "compares on id" do
71
+ @object_1.should == @object_2
72
+ end
73
+ end
74
+
75
+ describe ".find_by_<index>(value)" do
76
+ before(:each) do
77
+ @test_class_collection.stub!(:create_index)
78
+ TestClass.index(:name)
79
+ @name = mock(:name)
80
+ @id = mock(:id)
81
+
82
+ @bson_hash = {:class_name => 'TestClass', :name => @name, :_id => @id}
83
+ end
84
+
85
+ it "finds all objects which match the given index pattern" do
86
+ @test_class_collection.should_receive(:find).with(:name => @name).and_return([@bson_hash])
87
+ test_object = TestClass.find_by_name(@name).first
88
+ test_object.id.should == @id
89
+ test_object.name.should == @name
90
+ test_object.class.should == TestClass
91
+ end
92
+ end
93
+
94
+ describe ".find(query=nil)" do
95
+ before(:each) do
96
+ @no_query_results = mock(:no_query_results)
97
+ @query_results = mock(:query_results)
98
+ TestClass.stub!(:new).with(@no_query_results).and_return(@no_query_object = mock(:no_query_object))
99
+ TestClass.stub!(:new).with(@query_results).and_return(@query_object = mock(:query_object))
100
+ end
101
+
102
+ it "maps MongoDB::Collection.find" do
103
+ @test_class_collection.should_receive(:find).and_return([@no_query_results])
104
+ TestClass.find.should == [@no_query_object]
105
+
106
+ @test_class_collection.should_receive(:find).with({'_id' => query = mock(:query)}).and_return([@query_results])
107
+ TestClass.find(query).should == [@query_object]
108
+ end
109
+ end
110
+
111
+ describe ".index(key, :as => (name | nil))" do
112
+ def self.it_sets_up_the_index_verbatim
113
+ it "sets up the index with key #{@key} and name #{@name}" do
114
+ @test_class_collection.should_receive(:create_index).with(@key)
115
+ set_up_class(@key, @name)
116
+ TestClass.should respond_to(('find_by_'+@name.to_s).to_sym)
117
+ end
118
+ end
119
+
120
+ def set_up_class(key, name=nil)
121
+ TestClass.index(key, :as => name)
122
+
123
+ @indexable_object = TestClass.new(:top_level_attribute => 'test')
124
+ end
125
+
126
+ context "when given no key" do
127
+ it "creates no index and no method" do
128
+ @test_class_collection.should_not_receive(:create_index)
129
+ set_up_class(nil)
130
+ set_up_class('')
131
+ TestClass.should_not respond_to(:find_by_)
132
+ end
133
+ end
134
+
135
+ context "when the given key is usable as a method name" do
136
+ before(:each) do
137
+ @key = :top_level_attribute
138
+ end
139
+
140
+ context "and no name is provided" do
141
+ it "uses the key as the lookup name" do
142
+ @test_class_collection.should_receive(:create_index).with(@key)
143
+ set_up_class(@key)
144
+ TestClass.should respond_to(('find_by_'+@key.to_s).to_sym)
145
+ end
146
+ end
147
+
148
+ context "and a name is provided" do
149
+ before(:each) do
150
+ @name = :environment
151
+ end
152
+
153
+ it_sets_up_the_index_verbatim
154
+ end
155
+ end
156
+
157
+ context "when the given key is unusuable as a method name" do
158
+ before(:each) do
159
+ @key = 'top_level_attribute.sub_attribute'
160
+ end
161
+
162
+ context "and no name is provided" do
163
+ it "adds an index on the key" do
164
+ @test_class_collection.should_receive(:create_index).with(@key)
165
+ set_up_class(@key)
166
+ end
167
+
168
+ it "does not create a method using the key" do
169
+ @test_class_collection.stub(:create_index)
170
+ set_up_class(@key)
171
+ TestClass.should_not respond_to(('find_by_'+@key.to_s).to_sym)
172
+ end
173
+ end
174
+
175
+ context "and a name is provided" do
176
+ before(:each) do
177
+ @name = :sub_environment
178
+ end
179
+
180
+ it_sets_up_the_index_verbatim
181
+ end
182
+ end
183
+ end
184
+
185
+ describe ".viable_method_name(method_name)" do
186
+ context "contains only alphanums and underscores" do
187
+ it "is valid" do
188
+ TestClass.viable_method_name('_jkdsf328_32').should be_true
189
+ end
190
+
191
+ context "and ends in an exclamation mark" do
192
+ it 'is valid' do
193
+ TestClass.viable_method_name('_jkdsf328_32!').should be_true
194
+ end
195
+ end
196
+
197
+ context "and ends in an question mark" do
198
+ it 'is valid' do
199
+ TestClass.viable_method_name('_jkdsf328_32?').should be_true
200
+ end
201
+ end
202
+ end
203
+
204
+ context "contains a non [alphanum_!?] anywhere" do
205
+ it "is invalid" do
206
+ TestClass.viable_method_name('_jkdsf328_32*').should be_false
207
+ TestClass.viable_method_name('_jkdsf^328_32').should be_false
208
+ end
209
+ end
210
+
211
+ context "contains a [!?] anywhere but the end" do
212
+ it "is invalid" do
213
+ TestClass.viable_method_name('_jkds!f328_32').should be_false
214
+ TestClass.viable_method_name('_jkds?f328_32').should be_false
215
+ end
216
+ end
217
+ end
218
+
219
+ end
@@ -0,0 +1,153 @@
1
+ require File.dirname(__FILE__) + '/../../../lib/light_mongo'
2
+
3
+ describe LightMongo::Document::Serialization::HashSerializer do
4
+ HashSerializer = LightMongo::Document::Serialization::HashSerializer
5
+
6
+ before(:each) do
7
+ @current_depth = 0
8
+ end
9
+
10
+ describe ".serialize_object(object_to_serialize, current_depth)" do
11
+ before(:each) do
12
+ LightMongo.stub!(:database => mock(:database, :connection => nil), :slow_serialization => true)
13
+ @test_class_collection = mock(:test_class_collection)
14
+ Mongo::Collection.stub!(:new => @test_class_collection)
15
+ end
16
+
17
+ context "when a non-primitive" do
18
+ context "non-LightMongo::Document object" do
19
+ before(:each) do
20
+ class Other
21
+ attr_accessor :name
22
+ def initialize
23
+ @name = self.class.name + object_id.to_s
24
+ end
25
+ end
26
+
27
+ @object = Other.new
28
+ end
29
+
30
+ it "hashifies the object" do
31
+ HashSerializer.send(:serialize_object, @object, @current_depth+3).
32
+ should == {'_class_name' => 'Other', 'name' => @object.name}
33
+ end
34
+ end
35
+
36
+ context "LightMongo::Document object" do
37
+ before(:each) do
38
+ class TestClass
39
+ include LightMongo::Document
40
+ end
41
+
42
+ @object = TestClass.new
43
+ @test_class_collection.stub(:save => (@id = mock(:id)))
44
+ end
45
+
46
+ context "when below the top level" do
47
+ before(:each) do
48
+ @current_depth = 1
49
+ end
50
+
51
+ it "exports the object" do
52
+ HashSerializer.send(:serialize_object, @object, @current_depth).
53
+ should == {'_class_name' => 'TestClass', '_id' => @id, '_embed' => true}
54
+ end
55
+ end
56
+
57
+ context "when at the top level" do
58
+ before(:each) do
59
+ @current_depth = 0
60
+ end
61
+
62
+ it "hashifies the object" do
63
+ HashSerializer.should_receive(:hashify).with(@object, @current_depth)
64
+ HashSerializer.send(:serialize_object, @object, @current_depth)
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ context "when a primitive object" do
71
+ before(:each) do
72
+ @object = "test string"
73
+ end
74
+
75
+ it "returns the raw object" do
76
+ HashSerializer.send(:serialize_object, @object, @current_depth).
77
+ should == @object
78
+ end
79
+ end
80
+ end
81
+
82
+ describe ".dump(object, current_depth)" do
83
+ context "when the object is a container" do
84
+ before(:each) do
85
+
86
+ @sub_object_1 = mock(:sub_object_1)
87
+ @sub_object_2 = mock(:sub_object_2)
88
+
89
+ LightMongo::Document::Serialization::Serializer.
90
+ stub!(:serialize).
91
+ with(@sub_object_1, anything).
92
+ and_return(@serial_1 = mock(:serial_1))
93
+ LightMongo::Document::Serialization::Serializer.
94
+ stub!(:serialize).
95
+ with(@sub_object_2, anything).
96
+ and_return(@serial_2 = mock(:serial_2))
97
+ end
98
+
99
+ context "and more specifically an Array" do
100
+ before(:each) do
101
+ @object = [@sub_object_1, @sub_object_2]
102
+ end
103
+
104
+ it "iterates over the array, serializing the contents." do
105
+ HashSerializer.dump(@object).should == [@serial_1, @serial_2]
106
+ end
107
+
108
+ it "notifies the generic Serializer of the new depth." do
109
+ LightMongo::Document::Serialization::Serializer.should_receive(:serialize).with(@sub_object_1, @current_depth + 1)
110
+ HashSerializer.dump(@object, @current_depth)
111
+ end
112
+
113
+ it "returns an array." do
114
+ HashSerializer.dump(@object).should be_an(Array)
115
+ end
116
+ end
117
+
118
+ context "and more specifically a Hash" do
119
+ before(:each) do
120
+ @object = {
121
+ :sub_object_1 => @sub_object_1,
122
+ :sub_object_2 => @sub_object_2
123
+ }
124
+ end
125
+
126
+ it "iterates over the hash, serializing the contents but retaining the keys." do
127
+ HashSerializer.dump(@object).should == {:sub_object_1 => @serial_1, :sub_object_2 => @serial_2}
128
+ end
129
+
130
+ it "notifies the generic Serializer of the new depth." do
131
+ LightMongo::Document::Serialization::Serializer.should_receive(:serialize).with(@sub_object_1, @current_depth + 1)
132
+ HashSerializer.dump(@object, @current_depth)
133
+ end
134
+
135
+ it "returns a Hash." do
136
+ HashSerializer.dump(@object).should be_an(Hash)
137
+ end
138
+ end
139
+ end
140
+
141
+ context "when the object is a custom object" do
142
+ before(:each) do
143
+ class TestClass; end
144
+ @object = TestClass.new
145
+ end
146
+
147
+ it "passes off to the object serializer" do
148
+ HashSerializer.should_receive(:serialize_object).with(@object, @current_depth)
149
+ HashSerializer.dump(@object, @current_depth)
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,237 @@
1
+ require File.dirname(__FILE__) + '/../../../lib/document/serialization'
2
+
3
+ Serializer = LightMongo::Document::Serialization::Serializer
4
+
5
+ describe Serializer do
6
+ before(:each) do
7
+ @object = mock(:object)
8
+ end
9
+
10
+ describe ".array_deserialize(object)" do
11
+ before(:each) do
12
+ @object = [
13
+ @ivar1 = mock(:ivar1),
14
+ @ivar2 = mock(:ivar2)
15
+ ]
16
+ end
17
+
18
+ it "map-deserializes each element in the array" do
19
+ Serializer.should_receive(:deserialize).with(@ivar1).
20
+ and_return(desel_ivar1 = mock(:desel_ivar1))
21
+
22
+ Serializer.should_receive(:deserialize).with(@ivar2).
23
+ and_return(desel_ivar2 = mock(:desel_ivar2))
24
+
25
+ Serializer.send(:array_deserialize, @object).
26
+ should == [desel_ivar1, desel_ivar2]
27
+ end
28
+ end
29
+
30
+ describe ".hash_deserialize(object)" do
31
+ before(:each) do
32
+ @object = {
33
+ :ivar1 => (@ivar1 = mock(:ivar1)),
34
+ :ivar2 => (@ivar2 = mock(:ivar2))
35
+ }
36
+ end
37
+
38
+ it "map-deserializes each element in the hash" do
39
+ Serializer.should_receive(:deserialize).with(@ivar1).
40
+ and_return(desel_ivar1 = mock(:desel_ivar1))
41
+
42
+ Serializer.should_receive(:deserialize).with(@ivar2).
43
+ and_return(desel_ivar2 = mock(:desel_ivar2))
44
+
45
+ Serializer.send(:hash_deserialize, @object).
46
+ should == {:ivar1 => desel_ivar1, :ivar2 => desel_ivar2}
47
+ end
48
+ end
49
+
50
+ describe ".deserialize(object)" do
51
+ context "when the object is an array" do
52
+ before(:each) do
53
+ @object = []
54
+ end
55
+
56
+ it "deserializes the array" do
57
+ Serializer.should_receive(:array_deserialize).with(@object).
58
+ and_return(desel_array = mock(:desel_array))
59
+
60
+ Serializer.deserialize(@object).should == desel_array
61
+ end
62
+ end
63
+
64
+ context "when the object is a hash" do
65
+ before(:each) do
66
+ @object = {}
67
+ end
68
+
69
+ context "and nothing more" do
70
+ it "deserializes the hash" do
71
+ Serializer.should_receive(:hash_deserialize).with(@object)
72
+ Serializer.deserialize(@object)
73
+ end
74
+ end
75
+
76
+ context "and a Marshal dump" do
77
+ before(:each) do
78
+ @marshal_dump = mock(:marshal_dump)
79
+ @object['_data'] = @marshal_dump
80
+ end
81
+
82
+ it "unmarshals the dump" do
83
+ Marshal.should_receive(:load).with(@marshal_dump).
84
+ and_return(unmarshalled_data = mock(:unmarshalled_data))
85
+
86
+ Serializer.deserialize(@object).should == unmarshalled_data
87
+ end
88
+ end
89
+
90
+ context "and a LightMongo::Document" do
91
+ before(:each) do
92
+ class TestClass
93
+ end
94
+
95
+ @id = mock(:id)
96
+ @object = {'_id' => @id, '_class_name' => 'TestClass', '_embed' => true}
97
+ end
98
+
99
+ it "recovers the linked document" do
100
+ TestClass.stub!(:find).with(@id).and_return([test_instance = mock(:test_instance)])
101
+
102
+ Serializer.deserialize(@object).should == test_instance
103
+ end
104
+ end
105
+ end
106
+
107
+ context "when the object is anything else" do
108
+ before(:each) do
109
+ @object = 3
110
+ end
111
+
112
+ it "returns the object unharmed" do
113
+ Serializer.deserialize(@object).should == @object
114
+ end
115
+ end
116
+ end
117
+
118
+ describe ".serialize(object, current_depth)" do
119
+ before(:each) do
120
+ @depth = 0
121
+ @serializer = Serializer.new(@object, @depth)
122
+ @serializer.stub!(:hash_serialize => (@hash_serialized_object = mock(:hash_serialized_object)))
123
+ Serializer.stub!(:new).with(@object, anything).and_return(@serializer)
124
+ end
125
+
126
+ def self.it_creates_a_new_serializer_instance
127
+ it "creates a new serializer instance" do
128
+ Serializer.should_receive(:new).with(@object, @depth).and_return(@serializer)
129
+ Serializer.serialize(@object, @depth)
130
+ end
131
+ end
132
+
133
+ def self.it_hash_serializes_the_object
134
+ it "hash-serializes the object." do
135
+ @serializer.should_receive(:hash_serialize)
136
+ Serializer.serialize(@object, @depth)
137
+ end
138
+ end
139
+
140
+ def self.it_returns_the_hash_serialized_object
141
+ it "returns the hash-serialized object." do
142
+ Serializer.serialize(@object, @depth).should == @hash_serialized_object
143
+ end
144
+ end
145
+
146
+ context "when above the marshalling depth threshold" do
147
+ before(:each) do
148
+ @depth = 1
149
+ end
150
+
151
+ it_creates_a_new_serializer_instance
152
+
153
+ it_hash_serializes_the_object
154
+
155
+ it "does not marshal the object." do
156
+ @serializer.should_not_receive(:marshal)
157
+ Serializer.serialize(@object, @depth)
158
+ end
159
+
160
+ it_returns_the_hash_serialized_object
161
+ end
162
+
163
+ context "when below the marshalling depth threshold" do
164
+ before(:each) do
165
+ @depth = 5
166
+ end
167
+
168
+ context "and marshalling is turned on" do
169
+ before(:each) do
170
+ LightMongo.slow_serialization = false
171
+ end
172
+
173
+ it_creates_a_new_serializer_instance
174
+
175
+ it "marshals the object." do
176
+ @serializer.should_receive(:marshal)
177
+ Serializer.serialize(@object, @depth)
178
+ end
179
+
180
+ it "does not hash-serialize the object." do
181
+ @serializer.should_not_receive(:hash_serialize)
182
+ Serializer.serialize(@object, @depth)
183
+ end
184
+
185
+ it "returns the marshalled object." do
186
+ @serializer.stub!(:marshal => (@marshalled_object = mock(:marshalled_object)))
187
+ Serializer.serialize(@object, @depth).should == @marshalled_object
188
+ end
189
+ end
190
+
191
+ context "but marshalling is turned off" do
192
+ before(:each) do
193
+ LightMongo.slow_serialization = true
194
+ end
195
+
196
+ it_creates_a_new_serializer_instance
197
+
198
+ it_hash_serializes_the_object
199
+
200
+ it_returns_the_hash_serialized_object
201
+ end
202
+ end
203
+ end
204
+
205
+ describe "#marshal(object)" do
206
+ it "marshals the object." do
207
+ Marshal.should_receive(:dump).with(@object)
208
+ Serializer.new(@object).marshal
209
+ end
210
+
211
+ it "returns the marshalled object." do
212
+ Marshal.stub!(:dump).
213
+ with(@object).
214
+ and_return(marshalled_object = mock(:marshalled_object))
215
+ Serializer.new(@object).marshal.should == {'_data' => marshalled_object}
216
+ end
217
+ end
218
+
219
+ describe "#hash_serialize(object, current_depth)" do
220
+ before(:each) do
221
+ @current_depth = mock(:current_depth)
222
+ end
223
+
224
+ it "serializes the object into a set of nested hashes." do
225
+ LightMongo::Document::Serialization::HashSerializer.
226
+ should_receive(:dump).with(@object, @current_depth)
227
+ Serializer.new(@object, @current_depth).hash_serialize
228
+ end
229
+
230
+ it "returns the marshalled object." do
231
+ LightMongo::Document::Serialization::HashSerializer.stub!(:dump).
232
+ with(@object, @current_depth).
233
+ and_return(hashed_object = mock(:hashed_object))
234
+ Serializer.new(@object, @current_depth).hash_serialize.should == hashed_object
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,75 @@
1
+ require File.dirname(__FILE__) + '/../../lib/light_mongo'
2
+
3
+ describe LightMongo::Document::Serialization do
4
+ before(:each) do
5
+ LightMongo.stub!(:database => mock(:database, :connection => nil), :slow_serialization => true)
6
+ @test_class_collection = mock(:test_class_collection)
7
+ Mongo::Collection.stub!(:new => @test_class_collection)
8
+
9
+ class TestClass
10
+ include LightMongo::Document
11
+ attr_accessor :test_attribute
12
+ end
13
+ end
14
+
15
+ describe "#export" do
16
+ before(:each) do
17
+ @test_object = TestClass.new
18
+
19
+ @id = mock(:id)
20
+ @test_class_collection.stub!(:save => @id)
21
+ end
22
+
23
+ context "if Persistence has been included" do
24
+ it "saves itself" do
25
+ @test_object.should_receive(:save)
26
+ @test_object.export
27
+ end
28
+
29
+ it "generates a hash of its class name, id, and embed status" do
30
+ @test_object.export.should == {'_class_name' => 'TestClass', '_id' => @id, '_embed' => true}
31
+ end
32
+ end
33
+
34
+ context "if Persistence hasn't been included" do
35
+ before(:each) do
36
+ class NoPersistence
37
+ include LightMongo::Document::Serialization
38
+ end
39
+ @no_persistence = NoPersistence.new
40
+ end
41
+
42
+ it "returns self" do
43
+ @no_persistence.export.should == @no_persistence
44
+ end
45
+ end
46
+ end
47
+
48
+ describe "#initialize(params)" do
49
+ before(:each) do
50
+ @test_value = mock(:test_value).to_s
51
+ end
52
+
53
+ context "when given a hash" do
54
+ before(:each) do
55
+ @params = {:test_attribute => @test_value}
56
+ end
57
+
58
+ it "converts the hash to attributes" do
59
+ @test_object = TestClass.new(@params)
60
+ @test_object.test_attribute.should == @test_value
61
+ end
62
+ end
63
+ end
64
+
65
+ describe "#from_hash(hash)" do
66
+ before(:each) do
67
+ @test_object = TestClass.new
68
+ end
69
+
70
+ it "parses a hash into instance attributes" do
71
+ @test_object.from_hash(:test_attribute => 'Test value')
72
+ @test_object.test_attribute.should == 'Test value'
73
+ end
74
+ end
75
+ end