mongodoc 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/README.textile +143 -0
  2. data/Rakefile +35 -3
  3. data/VERSION +1 -1
  4. data/examples/simple_document.rb +35 -0
  5. data/examples/simple_object.rb +32 -0
  6. data/features/finders.feature +72 -0
  7. data/features/mongodoc_base.feature +12 -2
  8. data/features/named_scopes.feature +66 -0
  9. data/features/new_record.feature +36 -0
  10. data/features/partial_updates.feature +105 -0
  11. data/features/step_definitions/criteria_steps.rb +4 -41
  12. data/features/step_definitions/document_steps.rb +56 -5
  13. data/features/step_definitions/documents.rb +14 -3
  14. data/features/step_definitions/finder_steps.rb +15 -0
  15. data/features/step_definitions/named_scope_steps.rb +18 -0
  16. data/features/step_definitions/partial_update_steps.rb +32 -0
  17. data/features/step_definitions/query_steps.rb +51 -0
  18. data/features/using_criteria.feature +5 -1
  19. data/lib/mongodoc/attributes.rb +76 -63
  20. data/lib/mongodoc/collection.rb +9 -9
  21. data/lib/mongodoc/criteria.rb +152 -161
  22. data/lib/mongodoc/cursor.rb +7 -5
  23. data/lib/mongodoc/document.rb +95 -31
  24. data/lib/mongodoc/finders.rb +29 -0
  25. data/lib/mongodoc/named_scope.rb +68 -0
  26. data/lib/mongodoc/parent_proxy.rb +15 -6
  27. data/lib/mongodoc/proxy.rb +22 -13
  28. data/lib/mongodoc.rb +3 -3
  29. data/mongodoc.gemspec +42 -14
  30. data/perf/mongodoc_runner.rb +90 -0
  31. data/perf/ruby_driver_runner.rb +64 -0
  32. data/spec/attributes_spec.rb +46 -12
  33. data/spec/collection_spec.rb +23 -23
  34. data/spec/criteria_spec.rb +124 -187
  35. data/spec/cursor_spec.rb +21 -17
  36. data/spec/document_ext.rb +2 -2
  37. data/spec/document_spec.rb +187 -218
  38. data/spec/embedded_save_spec.rb +104 -0
  39. data/spec/finders_spec.rb +81 -0
  40. data/spec/hash_matchers.rb +27 -0
  41. data/spec/named_scope_spec.rb +82 -0
  42. data/spec/new_record_spec.rb +216 -0
  43. data/spec/parent_proxy_spec.rb +8 -6
  44. data/spec/proxy_spec.rb +80 -0
  45. data/spec/spec_helper.rb +2 -0
  46. metadata +35 -7
  47. data/README.rdoc +0 -75
@@ -0,0 +1,104 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Saving embedded documents" do
4
+ class NestedDocsRoot
5
+ include MongoDoc::Document
6
+
7
+ has_many :nested_children
8
+ end
9
+
10
+ class NestedChild
11
+ include MongoDoc::Document
12
+
13
+ has_one :leaf
14
+ end
15
+
16
+ class LeafDoc
17
+ include MongoDoc::Document
18
+
19
+ key :data
20
+ end
21
+
22
+ let(:leaf) { LeafDoc.new }
23
+ let(:data) { 'data' }
24
+
25
+ context "#save" do
26
+ let(:root) { NestedDocsRoot.new(:nested_children => [NestedChild.new(:leaf => leaf)]) }
27
+
28
+ it "calls the root document's save" do
29
+ root.should_receive(:save).with(true)
30
+ leaf.save
31
+ end
32
+
33
+ it "(with bang!) calls the root documents save!" do
34
+ root.should_receive(:save!)
35
+ leaf.save!
36
+ end
37
+ end
38
+
39
+ context "update_attributes naive" do
40
+ context "with no has_many, update_attributes" do
41
+ let(:root) { NestedChild.new(:leaf => leaf) }
42
+
43
+ it "calls the root document's _naive_update_attributes with a full attribute path and not safe" do
44
+ root.should_receive(:_naive_update_attributes).with({'leaf.data' => data}, false)
45
+ leaf.update_attributes(:data => data)
46
+ end
47
+
48
+ it "(with bang!) calls the root document's _naive_update_attributes with a full attribute path and safe" do
49
+ root.should_receive(:_naive_update_attributes).with({'leaf.data' => data}, true)
50
+ leaf.update_attributes!(:data => data)
51
+ end
52
+ end
53
+
54
+ context "with has_many, update_attributes" do
55
+ let(:root) { NestedDocsRoot.new(:nested_children => [NestedChild.new(:leaf => leaf)]) }
56
+
57
+ it "calls the root document's _naive_update_attributes with a full attribute path and not safe" do
58
+ root.should_receive(:_naive_update_attributes).with({'nested_children.0.leaf.data' => data}, false)
59
+ leaf.update_attributes(:data => data)
60
+ end
61
+
62
+ it "(with bang!) calls the root document's _naive_update_attributes with a full attribute path and safe" do
63
+ root.should_receive(:_naive_update_attributes).with({'nested_children.0.leaf.data' => data}, true)
64
+ leaf.update_attributes!(:data => data)
65
+ end
66
+ end
67
+ end
68
+
69
+ context "update_attributes strict" do
70
+ let(:leaf_id) { 'leaf_id' }
71
+
72
+ before do
73
+ leaf.stub(:_id).and_return(leaf_id)
74
+ end
75
+
76
+ context "with no has_many, update_attributes" do
77
+ let(:root) { NestedChild.new(:leaf => leaf) }
78
+
79
+ it "calls the root document's _strict_update_attributes with a full attribute path and not safe" do
80
+ root.should_receive(:_strict_update_attributes).with({'leaf.data' => data}, false, 'leaf._id' => leaf_id)
81
+ leaf.update_attributes(:data => data, :__strict__ => true)
82
+ end
83
+
84
+ it "(with bang!) calls the root document's _naive_update_attributes with a full attribute path and safe" do
85
+ root.should_receive(:_strict_update_attributes).with({'leaf.data' => data}, true, 'leaf._id' => leaf_id)
86
+ leaf.update_attributes!(:data => data, :__strict__ => true)
87
+ end
88
+ end
89
+
90
+ context "with has_many, update_attributes" do
91
+ let(:root) { NestedDocsRoot.new(:nested_children => [NestedChild.new(:leaf => leaf)]) }
92
+
93
+ it "calls the root document's _naive_update_attributes with a full attribute path and not safe" do
94
+ root.should_receive(:_strict_update_attributes).with({'nested_children.0.leaf.data' => data}, false, 'nested_children.leaf._id' => leaf_id)
95
+ leaf.update_attributes(:data => data, :__strict__ => true)
96
+ end
97
+
98
+ it "(with bang!) calls the root document's _naive_update_attributes with a full attribute path and safe" do
99
+ root.should_receive(:_strict_update_attributes).with({'nested_children.0.leaf.data' => data}, true, 'nested_children.leaf._id' => leaf_id)
100
+ leaf.update_attributes!(:data => data, :__strict__ => true)
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,81 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper.rb"))
2
+
3
+ describe MongoDoc::Finders do
4
+ class FindersTest
5
+ include MongoDoc::Document
6
+
7
+ key :data
8
+ end
9
+
10
+ context ".criteria" do
11
+ it "creates a new criteria for the document" do
12
+ FindersTest.criteria.should be_a_kind_of(MongoDoc::Criteria)
13
+ end
14
+
15
+ it "sets the criteria klass" do
16
+ FindersTest.criteria.klass.should == FindersTest
17
+ end
18
+ end
19
+
20
+ context ".find" do
21
+ before do
22
+ @criteria = stub('criteria').as_null_object
23
+ @conditions = {:where => 'this.a > 3'}
24
+ MongoDoc::Criteria.stub(:translate).and_return(@criteria)
25
+ end
26
+
27
+ it "creates a criteria" do
28
+ MongoDoc::Criteria.should_receive(:translate).with(FindersTest, @conditions).and_return(@criteria)
29
+ FindersTest.find(:first, @conditions)
30
+ end
31
+
32
+ [:all, :first, :last].each do |which|
33
+ it "calls #{which} on the criteria" do
34
+ @criteria.should_receive(which)
35
+ FindersTest.find(which, @conditions)
36
+ end
37
+ end
38
+ end
39
+
40
+ context ".find_one" do
41
+ context "with an id" do
42
+ it "calls translate with the id" do
43
+ id = 'an id'
44
+ MongoDoc::Criteria.should_receive(:translate).with(FindersTest, id)
45
+ FindersTest.find_one(id)
46
+ end
47
+ end
48
+
49
+ context "with conditions" do
50
+ before do
51
+ @criteria = stub('criteria').as_null_object
52
+ @conditions = {:where => 'this.a > 3'}
53
+ end
54
+
55
+ it "calls translate with the conditions" do
56
+ MongoDoc::Criteria.should_receive(:translate).with(FindersTest, @conditions).and_return(@criteria)
57
+ FindersTest.find_one(@conditions)
58
+ end
59
+
60
+ it "call :one on the result" do
61
+ MongoDoc::Criteria.stub(:translate).and_return(@criteria)
62
+ @criteria.should_receive(:one)
63
+ FindersTest.find_one(@conditions)
64
+ end
65
+ end
66
+ end
67
+
68
+ context "all other finders" do
69
+ before do
70
+ @criteria = stub('criteria').as_null_object
71
+ MongoDoc::Criteria.stub(:new).and_return(@criteria)
72
+ end
73
+
74
+ [:all, :count, :first, :last].each do |which|
75
+ it "calls #{which} on the new criteria" do
76
+ @criteria.should_receive(which)
77
+ FindersTest.send(which)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,27 @@
1
+ class HasEntry
2
+ def initialize(expected)
3
+ @expected = expected
4
+ end
5
+
6
+ def matches?(target)
7
+ @target = target
8
+ @expected.all? do |key, value|
9
+ @target[key] == value
10
+ end
11
+ end
12
+
13
+ def failure_message_for_should
14
+ "expected #{@target.inspect} to have entries #{@expected.inspect}"
15
+ end
16
+
17
+ def failure_message_for_should_not
18
+ "expected #{@target.inspect} not to have entries #{@expected.inspect}"
19
+ end
20
+ end
21
+
22
+ module HashMatchers
23
+ def has_entry(expected)
24
+ HasEntry.new(expected)
25
+ end
26
+ alias :has_entries :has_entry
27
+ end
@@ -0,0 +1,82 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper.rb"))
2
+
3
+ describe MongoDoc::NamedScope do
4
+
5
+ module Extension
6
+ def extension_module_method
7
+ "extension module method"
8
+ end
9
+ end
10
+
11
+ class NamedScopeTest
12
+ include MongoDoc::Document
13
+
14
+ key :active
15
+ key :count
16
+
17
+ named_scope :active, :where => {:active => true} do
18
+ def extension_method
19
+ "extension method"
20
+ end
21
+ end
22
+ named_scope :count_gt_one, :where => {:count => {'$gt' => 1}}, :extend => Extension
23
+ named_scope :at_least_count, lambda {|count| {:where => {:count => {'$gt' => count}}}}
24
+ end
25
+
26
+ context ".named_scope" do
27
+ it "adds the named scope to the hash of scopes" do
28
+ NamedScopeTest.scopes.should have_key(:active)
29
+ end
30
+
31
+ it "creates a class method for the named scope" do
32
+ NamedScopeTest.should respond_to(:active)
33
+ end
34
+ end
35
+
36
+ context "accessing a named scope" do
37
+ it "is a criteria proxy" do
38
+ MongoDoc::NamedScope::CriteriaProxy.should === NamedScopeTest.active
39
+ end
40
+
41
+ it "responds like a criteria" do
42
+ NamedScopeTest.active.should respond_to(:selector)
43
+ end
44
+
45
+ it "instantiates the criteria" do
46
+ criteria = MongoDoc::Criteria.new(NamedScopeTest)
47
+ MongoDoc::Criteria.should_receive(:new).and_return(criteria)
48
+ NamedScopeTest.active.selector
49
+ end
50
+
51
+ it "has set the conditions on the criteria" do
52
+ NamedScopeTest.active.selector.should has_entry(:active => true)
53
+ end
54
+
55
+ it "sets the association extension by block" do
56
+ NamedScopeTest.active.extension_method.should == "extension method"
57
+ end
58
+
59
+ it "sets the association extension by :extend" do
60
+ NamedScopeTest.count_gt_one.extension_module_method.should == "extension module method"
61
+ end
62
+
63
+ context "when using a lambda" do
64
+ it "accepts parameters to the criteria" do
65
+ NamedScopeTest.at_least_count(3).selector.should has_entry(:count => {'$gt' => 3})
66
+ end
67
+ end
68
+ end
69
+
70
+ context "chained scopes" do
71
+ it "instantiates the criteria" do
72
+ criteria = MongoDoc::Criteria.new(NamedScopeTest)
73
+ MongoDoc::Criteria.should_receive(:new).and_return(criteria)
74
+ NamedScopeTest.active.count_gt_one.selector
75
+ end
76
+
77
+ it "merges the criteria" do
78
+ NamedScopeTest.active.count_gt_one.selector.should has_entry(:count => {'$gt' => 1}, :active => true)
79
+ end
80
+ end
81
+ end
82
+
@@ -0,0 +1,216 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "MongoDoc::Document _id and #new_record?" do
4
+ class Child
5
+ include MongoDoc::Document
6
+ end
7
+
8
+ class Parent
9
+ include MongoDoc::Document
10
+
11
+ has_one :child
12
+ has_many :children
13
+
14
+ key :data
15
+
16
+ validates_presence_of :data
17
+ end
18
+
19
+ before do
20
+ @child = Child.new
21
+ end
22
+
23
+ context "as a has one@child" do
24
+ it "when added to the parent is a new record" do
25
+ Parent.new(:data => 'data', :child => @child)
26
+ @child.should be_new_record
27
+ end
28
+
29
+ context "creating" do
30
+ before do
31
+ @id = 'id'
32
+ @collection = stub('collection')
33
+ @collection.stub(:insert).and_return(@id)
34
+ Parent.stub(:collection).and_return(@collection)
35
+ end
36
+
37
+ context ".create" do
38
+ it "when created is not a new record" do
39
+ Parent.create(:data => 'data', :child => @child)
40
+ @child.should_not be_new_record
41
+ end
42
+
43
+ it "if parent is invalid, remains a new record" do
44
+ Parent.create(:child =>@child)
45
+ @child.should be_new_record
46
+ end
47
+ end
48
+
49
+ context ".create!" do
50
+ it "when created is not a new record" do
51
+ Parent.create!(:data => 'data', :child => @child)
52
+ @child.should_not be_new_record
53
+ end
54
+
55
+ it "if parent is invalid, remains a new record" do
56
+ Parent.create!(:child => @child) rescue nil
57
+ @child.should be_new_record
58
+ end
59
+
60
+ it "when db error is raised, remains a new record" do
61
+ @collection.stub(:insert).and_raise(Mongo::OperationFailure)
62
+ expect do
63
+ Parent.create!(:data => 'data', :child => @child)
64
+ end.should raise_error(Mongo::OperationFailure)
65
+ @child.should be_new_record
66
+ end
67
+ end
68
+ end
69
+
70
+ context "saving" do
71
+ before do
72
+ @id = 'id'
73
+ @collection = stub('collection')
74
+ @collection.stub(:save).and_return(@id)
75
+ Parent.stub(:collection).and_return(@collection)
76
+ end
77
+
78
+ context "#save" do
79
+ it "when saved is not a new record" do
80
+ parent = Parent.new(:data => 'data', :child => @child)
81
+ parent.save
82
+ @child.should_not be_new_record
83
+ end
84
+
85
+ it "if parent is invalid, remains a new record" do
86
+ parent = Parent.new(:child => @child)
87
+ parent.save
88
+ @child.should be_new_record
89
+ end
90
+ end
91
+
92
+ context "#save!" do
93
+ it "when saved is not a new record" do
94
+ parent = Parent.new(:data => 'data', :child => @child)
95
+ parent.save!
96
+ @child.should_not be_new_record
97
+ end
98
+
99
+ it "if parent is invalid, remains a new record" do
100
+ parent = Parent.new(:child => @child)
101
+ parent.save! rescue nil
102
+ @child.should be_new_record
103
+ end
104
+
105
+ it "when db error is raised, remains a new record" do
106
+ @collection.stub(:save).and_raise(Mongo::OperationFailure)
107
+ parent = Parent.new(:data => 'data', :child => @child)
108
+ expect do
109
+ parent.save!
110
+ end.should raise_error(Mongo::OperationFailure)
111
+ @child.should be_new_record
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ context "as a has many@child" do
118
+ it "when added to the parent is a new record" do
119
+ parent = Parent.new(:data => 'data')
120
+ parent.children << @child
121
+ @child.should be_new_record
122
+ end
123
+
124
+ context "creating" do
125
+ before do
126
+ @id = 'id'
127
+ @collection = stub('collection')
128
+ @collection.stub(:insert).and_return(@id)
129
+ Parent.stub(:collection).and_return(@collection)
130
+ end
131
+
132
+ context ".create" do
133
+ it "when created is not a new record" do
134
+ Parent.create(:data => 'data', :children => [@child])
135
+ @child.should_not be_new_record
136
+ end
137
+
138
+ it "if parent is invalid, remains a new record" do
139
+ Parent.create(:children => [@child])
140
+ @child.should be_new_record
141
+ end
142
+ end
143
+
144
+ context ".create!" do
145
+ it "when created is not a new record" do
146
+ Parent.create!(:data => 'data', :children => [@child])
147
+ @child.should_not be_new_record
148
+ end
149
+
150
+ it "if parent is invalid, remains a new record" do
151
+ Parent.create!(:children => [@child]) rescue nil
152
+ @child.should be_new_record
153
+ end
154
+
155
+ it "when db error is raised, remains a new record" do
156
+ @collection.stub(:insert).and_raise(Mongo::OperationFailure)
157
+ expect do
158
+ Parent.create!(:data => 'data', :children => [@child])
159
+ end.should raise_error(Mongo::OperationFailure)
160
+ @child.should be_new_record
161
+ end
162
+ end
163
+ end
164
+
165
+ context "saving" do
166
+ before do
167
+ @id = 'id'
168
+ @collection = stub('collection')
169
+ @collection.stub(:save).and_return(@id)
170
+ Parent.stub(:collection).and_return(@collection)
171
+ end
172
+
173
+ context "#save" do
174
+ it "when saved is not a new record" do
175
+ parent = Parent.new(:data => 'data')
176
+ parent.children << @child
177
+ parent.save
178
+ @child.should_not be_new_record
179
+ end
180
+
181
+ it "if parent is invalid, remains a new record" do
182
+ parent = Parent.new
183
+ parent.children << @child
184
+ parent.save
185
+ @child.should be_new_record
186
+ end
187
+ end
188
+
189
+ context "#save!" do
190
+ it "when saved is not a new record" do
191
+ parent = Parent.new(:data => 'data')
192
+ parent.children << @child
193
+ parent.save!
194
+ @child.should_not be_new_record
195
+ end
196
+
197
+ it "if parent is invalid, remains a new record" do
198
+ parent = Parent.new
199
+ parent.children << @child
200
+ parent.save! rescue nil
201
+ @child.should be_new_record
202
+ end
203
+
204
+ it "when db error is raised, remains a new record" do
205
+ @collection.stub(:save).and_raise(Mongo::OperationFailure)
206
+ parent = Parent.new(:data => 'data')
207
+ parent.children << @child
208
+ expect do
209
+ parent.save!
210
+ end.should raise_error(Mongo::OperationFailure)
211
+ @child.should be_new_record
212
+ end
213
+ end
214
+ end
215
+ end
216
+ end
@@ -2,9 +2,11 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
3
  describe "MongoDoc::ParentProxy" do
4
4
  class Parent
5
- def path_to_root(attrs)
6
- attrs
7
- end
5
+ include MongoDoc::Document
6
+ end
7
+
8
+ class Child
9
+ include MongoDoc::Document
8
10
  end
9
11
 
10
12
  before do
@@ -36,7 +38,7 @@ describe "MongoDoc::ParentProxy" do
36
38
  end.should raise_error
37
39
  end
38
40
 
39
- it "inserts the association name the path_to_root" do
40
- subject.path_to_root({:name1 => 'value1', :name2 => 'value2'}).should == {"#{@assoc_name}.name1" => 'value1', "#{@assoc_name}.name2" => "value2"}
41
+ it "inserts the association name the _path_to_root" do
42
+ subject._path_to_root(Child.new, :name1 => 'value1', :name2 => 'value2').should == {"#{@assoc_name}.name1" => 'value1', "#{@assoc_name}.name2" => "value2"}
41
43
  end
42
- end
44
+ end
@@ -0,0 +1,80 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe MongoDoc::Proxy do
4
+ class ProxyTest
5
+ include MongoDoc::Document
6
+
7
+ key :name
8
+ end
9
+
10
+ let(:root) { stub('root', :register_save_observer => nil) }
11
+ let(:proxy) { MongoDoc::Proxy.new(:assoc_name => 'has_many_name', :collection_class => ProxyTest, :root => root, :parent => root) }
12
+
13
+ context "#<<" do
14
+ let(:item) { ProxyTest.new }
15
+
16
+ it "appends the item to the collection" do
17
+ (proxy << item).should include(item)
18
+ end
19
+
20
+ context "when the item is a Hash" do
21
+ let(:hash) {{:name => 'hash'}}
22
+
23
+ it "calls build when the item is a hash" do
24
+ proxy.should_receive(:build).with(hash).and_return(item)
25
+ proxy << hash
26
+ end
27
+
28
+ it "registers a save observer" do
29
+ proxy.stub(:build).and_return(item)
30
+ root.should_receive(:register_save_observer)
31
+ proxy << hash
32
+ end
33
+
34
+ it "sets the root" do
35
+ proxy.stub(:build).and_return(item)
36
+ proxy << hash
37
+ item._root.should == root
38
+ end
39
+ end
40
+
41
+ context "when the item is not a MongoDoc::Document" do
42
+ it "does not register a save observer" do
43
+ root.should_not_receive(:register_save_observer)
44
+ proxy << 'not_doc'
45
+ end
46
+
47
+ it "does not set the root" do
48
+ item.should_not_receive(:_root=)
49
+ proxy << 'not_doc'
50
+ end
51
+ end
52
+
53
+ context "when the item is a MongoDoc::Document" do
54
+ it "registers a save observer" do
55
+ root.should_receive(:register_save_observer)
56
+ proxy << item
57
+ end
58
+
59
+ it "sets the root" do
60
+ proxy << item
61
+ item._root.should == root
62
+ end
63
+ end
64
+
65
+ context "when the item is an array" do
66
+ it "adds the array" do
67
+ array = ['something else']
68
+ proxy << array
69
+ proxy.should include(array)
70
+ end
71
+ end
72
+ end
73
+
74
+ context "#build" do
75
+ it "builds an object of the collection class from the hash attrs" do
76
+ name = 'built'
77
+ proxy.build({:name => name}).name.should == name
78
+ end
79
+ end
80
+ end
data/spec/spec_helper.rb CHANGED
@@ -4,8 +4,10 @@ require 'mongodoc'
4
4
  require 'spec'
5
5
  require 'spec/autorun'
6
6
  require 'bson_matchers'
7
+ require 'hash_matchers'
7
8
  require 'document_ext'
8
9
 
9
10
  Spec::Runner.configure do |config|
10
11
  config.include(BsonMatchers)
12
+ config.include(HashMatchers)
11
13
  end