mongodoc 0.1.2 → 0.2.0

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