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
@@ -45,13 +45,26 @@ Given /^I set the id on the document '(.*)' to (.*)$/ do |doc_name, value|
45
45
  doc._id = Mongo::ObjectID.new([value.to_i])
46
46
  end
47
47
 
48
+ Given /^'(.+)' has one (.+?) as (.+?) \(identified by '(.+)'\):$/ do |doc_name, class_name, assoc_name, var_name, table|
49
+ doc = instance_variable_get("@#{doc_name}")
50
+ obj = class_name.constantize.new
51
+ table.hashes.each do |hash|
52
+ hash.each do |key, value|
53
+ obj.send("#{key.underscore.gsub(' ', '_')}=", value)
54
+ end
55
+ end
56
+ instance_variable_set("@#{var_name}", obj)
57
+ doc.send("#{assoc_name.underscore.gsub(' ', '_')}=", obj)
58
+ @last = obj
59
+ end
60
+
48
61
  When /^I save the document '(.*)'$/ do |name|
49
62
  object = instance_variable_get("@#{name}")
50
- @last_return = object.save
63
+ @last_return = object.save
51
64
  end
52
65
 
53
66
  When /^I save the last document$/ do
54
- @last_return = @last.save
67
+ @last_return = @last.save
55
68
  end
56
69
 
57
70
  When /^I create an (.*) '(.*)' from the hash '(.*)'$/ do |doc, name, hash|
@@ -63,7 +76,18 @@ end
63
76
  When /^I update the document '(.*)' with the hash named '(.*)'$/ do |doc_name, hash_name|
64
77
  doc = instance_variable_get("@#{doc_name}")
65
78
  attrs = instance_variable_get("@#{hash_name}")
66
- @last_return = doc.update_attributes(attrs)
79
+ @last_return = doc.update_attributes(attrs)
80
+ end
81
+
82
+ When /^I query (.*) with find (.*)$/ do |doc, criteria_text|
83
+ klass = doc.singularize.camelize.constantize
84
+ criteria = eval(criteria_text)
85
+ @last_return = klass.find(:all, criteria)
86
+ end
87
+
88
+ When /^'(.+)' is the first (.+?) of '(.+)'$/ do |var_name, single_assoc, doc_name|
89
+ doc = instance_variable_get("@#{doc_name}")
90
+ instance_variable_set("@#{var_name}", doc.send(single_assoc.pluralize).first)
67
91
  end
68
92
 
69
93
  Then /^'(.*)' is not a new record$/ do |name|
@@ -82,6 +106,33 @@ Then /^the document '(.*)' roundtrips$/ do |name|
82
106
  instance_variable_set("@#{name}", from_db)
83
107
  end
84
108
 
85
- Then /^the last return value is false$/ do
86
- @last_return.should be_false
109
+ Then /^the document '(.+)' does not roundtrip$/ do |name|
110
+ object = instance_variable_get("@#{name}")
111
+ from_db = object.class.find_one(object._id)
112
+ from_db.should_not == object
113
+ end
114
+
115
+ Then /^the last return value is (.+)$/ do |bool_val|
116
+ @last_return.should send("be_#{bool_val}")
117
+ end
118
+
119
+ Then /^the first (.*) of '(.*)' is not a new record$/ do |assoc, name|
120
+ object = instance_variable_get("@#{name}")
121
+ plural = assoc.pluralize
122
+ object.send(plural).first.should_not be_new_record
123
+ end
124
+
125
+ Then /^the (\w*) of '(.*)' is not a new record$/ do |assoc, name|
126
+ object = instance_variable_get("@#{name}")
127
+ object.send(assoc).should_not be_new_record
128
+ end
129
+
130
+ Then /^the (\w*) of '(.*)' roundtrips$/ do |assoc, name|
131
+ object = instance_variable_get("@#{name}")
132
+ from_db = object.class.find_one(object._id)
133
+ object.send(assoc).id.should == from_db.send(assoc).id
134
+ end
135
+
136
+ Then /^the size of the last return value is (.*)$/ do |count|
137
+ @last_return.size.should == count.to_i
87
138
  end
@@ -1,19 +1,30 @@
1
- class Address < MongoDoc::Document
1
+ class Address
2
+ include MongoDoc::Document
3
+
2
4
  key :street
3
5
  key :city
4
6
  key :state
5
7
  key :zip_code
6
8
  end
7
9
 
8
- class Place < MongoDoc::Document
10
+ class Place
11
+ include MongoDoc::Document
12
+
9
13
  key :name
10
14
  key :type
11
15
  has_one :address
12
16
  end
13
17
 
14
- class Contact < MongoDoc::Document
18
+ class Contact
19
+ include MongoDoc::Document
20
+
15
21
  key :name
16
22
  key :type
23
+ key :note
17
24
  key :interests
18
25
  has_many :addresses
26
+
27
+ named_scope :rubyists, :in => {:interests => ['ruby']}
28
+ named_scope :contract_work, :in => {:interests => ['contract work']}
29
+ named_scope :in_state, lambda {|state| { :where => {'addresses.state' => state}}}
19
30
  end
@@ -0,0 +1,15 @@
1
+ def finder_query=(finder)
2
+ @query = finder
3
+ end
4
+
5
+ When /^I query (.+) with (\w+)$/ do |doc, finder|
6
+ self.finder_query = klass(doc).send(finder)
7
+ end
8
+
9
+ When /^I query (.+) to find_one with the id of the '(.+)' document$/ do |collection, doc_name|
10
+ self.finder_query = klass(collection).find_one(instance_variable_get("@#{doc_name}").id)
11
+ end
12
+
13
+ Then /^the query result was (\d+) documents$/ do |count|
14
+ query.should == count.to_i
15
+ end
@@ -0,0 +1,18 @@
1
+ def scope_query=(scope)
2
+ @query = scope
3
+ end
4
+
5
+ When /^I query (.*) with scope '(.*)'$/ do |doc, scope|
6
+ self.scope_query = klass(doc).send(scope)
7
+ end
8
+
9
+ When /^I query (.*) with scopes '(.*)'$/ do |doc, scopes|
10
+ self.scope_query = scopes.split(',').inject(klass(doc)) do |result, scope|
11
+ result.send(scope.strip)
12
+ end
13
+ end
14
+
15
+ When /^I query (.*) with lambda scope '(.*)' with parameters '(.*)'$/ do |doc, scope, params_text|
16
+ params = params_text.split(',').map(&:strip)
17
+ self.scope_query = klass(doc).send(scope, *params)
18
+ end
@@ -0,0 +1,32 @@
1
+ When /^I(\sstrict\s|\s)update the '(.+)' for '(.+)' to '(.+)'$/ do |strict, attr, doc_name, value|
2
+ doc = instance_variable_get("@#{doc_name}")
3
+ attrs = {attr => value}
4
+ attrs.merge!(:__strict__ => true) unless strict.blank?
5
+ @last_return = doc.update_attributes(attrs)
6
+ end
7
+
8
+ When /^someone else changes the (.+?) '(.+)' of '(.+)' to$/ do |assoc_klass, assoc_name, name, table|
9
+ orig = instance_variable_get("@#{name}")
10
+ doc = orig.class.find_one(orig._id)
11
+ obj = assoc_klass.constantize.new
12
+ table.hashes.each do |hash|
13
+ hash.each do |key, value|
14
+ obj.send("#{key.underscore.gsub(' ', '_')}=", value)
15
+ end
16
+ end
17
+ doc.send("#{assoc_name.underscore.gsub(' ', '_')}=", obj)
18
+ doc.save
19
+ end
20
+
21
+ When /^someone else changes the (.+) of '(.+)':$/ do |assoc_name, name, table|
22
+ orig = instance_variable_get("@#{name}")
23
+ doc = orig.class.find_one(orig._id)
24
+ doc.send(assoc_name).clear
25
+ table.hashes.each do |hash|
26
+ doc.send(assoc_name) << hash.inject({}) do |attrs, (attr, value)|
27
+ attrs["#{attr.underscore.gsub(' ', '_')}"] = value
28
+ attrs
29
+ end
30
+ end
31
+ doc.save
32
+ end
@@ -0,0 +1,51 @@
1
+ def klass(klass_name = nil)
2
+ @klass ||= klass_name.singularize.camelize.constantize
3
+ end
4
+
5
+ def query(klass_name = nil)
6
+ @query ||= klass(klass_name).criteria
7
+ end
8
+
9
+ Then /^the (first|last) query result is equal to the document '(.*)'$/ do |position, name|
10
+ object = instance_variable_get("@#{name}")
11
+ query.send(position).should == object
12
+ end
13
+
14
+ Then /^one of the query results is the document '(.*)'$/ do |name|
15
+ object = instance_variable_get("@#{name}")
16
+ query.any? {|doc| doc == object}
17
+ end
18
+
19
+ Then /^the aggregate query result with "(.*)" == "(.*)" has a count of (.*)$/ do |key, value, count|
20
+ result = query.aggregate
21
+ result.find {|r| r.has_key?(key) and r[key] == value }['count'].should == count.to_i
22
+ end
23
+
24
+ Then /^the query result has (.*) documents*$/ do |count|
25
+ if query.respond_to?(:size)
26
+ query.size.should == count.to_i
27
+ else
28
+ query.count.should == count.to_i
29
+ end
30
+ end
31
+
32
+ Then /^the size of the query result is (.*)$/ do |count|
33
+ query.to_a.size.should == count.to_i
34
+ end
35
+
36
+ Then /^the group query result with "([^\"]*)" == "([^\"]*)" has the document '(.*)'$/ do |key, value, name|
37
+ object = instance_variable_get("@#{name}")
38
+ result = query.group
39
+ result.find {|r| r.has_key?(key) and r[key] == value }['group'].should include(object)
40
+ end
41
+
42
+ Then /^the query result is the document '(.*)'$/ do |name|
43
+ object = instance_variable_get("@#{name}")
44
+ if query.kind_of?(Array)
45
+ query.size.should == 1
46
+ query.first.should == object
47
+ else
48
+ query.should == object
49
+ end
50
+ end
51
+
@@ -116,7 +116,7 @@ Feature: MongoDoc::Base
116
116
  Then the query result has 3 documents
117
117
 
118
118
  Scenario: Selecting a contact with the id operator
119
- When I query contacts with 'hashrocket' id
119
+ When I query contacts with the 'hashrocket' id
120
120
  Then the query result has 1 documents
121
121
  And the first query result is equal to the document 'hashrocket'
122
122
 
@@ -143,3 +143,7 @@ Feature: MongoDoc::Base
143
143
  When I query contacts with every "{'interests' => ['ruby', 'rails']}"
144
144
  And I set the query on contacts to skip 1
145
145
  Then the size of the query result is 2
146
+
147
+ Scenario: All
148
+ When I query contacts with 'all'
149
+ Then the size of the query result is 3
@@ -3,93 +3,106 @@ require 'mongodoc/parent_proxy'
3
3
 
4
4
  module MongoDoc
5
5
  module Attributes
6
- def self.extended(klass)
7
- klass.class_inheritable_array :_keys
8
- klass._keys = []
9
- klass.class_inheritable_array :_associations
10
- klass._associations = []
6
+ def self.included(klass)
7
+ klass.class_eval do
8
+ class_inheritable_array :_keys
9
+ self._keys = []
10
+ class_inheritable_array :_associations
11
+ self._associations = []
11
12
 
12
- klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
13
13
  attr_accessor :_parent
14
+ attr_accessor :_id
14
15
 
15
- def _root
16
- @_root
17
- end
16
+ extend ClassMethods
17
+ end
18
+ end
18
19
 
19
- def _root=(root)
20
- @_root = root
21
- _associations.each do|a|
22
- association = send(a)
23
- association._root = root if association
24
- end
25
- end
20
+ def _root
21
+ @_root
22
+ end
26
23
 
27
- def path_to_root(prev)
28
- return prev unless _parent
29
- _parent.path_to_root(prev)
30
- end
31
- RUBY
24
+ def _root=(root)
25
+ @_root = root
26
+ _associations.each do|a|
27
+ association = send(a)
28
+ association._root = root if association
29
+ end
32
30
  end
33
31
 
34
- def _attributes
35
- _keys + _associations
32
+ def _path_to_root(src, attrs)
33
+ return attrs unless _parent
34
+ _parent._path_to_root(self, attrs)
36
35
  end
37
36
 
38
- def key(*args)
39
- args.each do |name|
40
- _keys << name unless _keys.include?(name)
41
- attr_accessor name
42
- end
37
+ def _selector_path_to_root(selector)
38
+ return selector unless _parent
39
+ _parent._selector_path_to_root(selector)
43
40
  end
44
41
 
45
- def has_one(*args)
46
- args.each do |name|
47
- _associations << name unless _associations.include?(name)
48
- attr_reader name
42
+ module ClassMethods
43
+ def _attributes
44
+ _keys + _associations
45
+ end
49
46
 
50
- define_method("#{name}=") do |value|
51
- if value
52
- raise NotADocumentError unless Document === value
53
- value._parent = ParentProxy.new(self, name)
54
- value._root = _root || self
55
- end
56
- instance_variable_set("@#{name}", value)
47
+ def key(*args)
48
+ args.each do |name|
49
+ _keys << name unless _keys.include?(name)
50
+ attr_accessor name
57
51
  end
58
-
59
- validates_associated name
60
52
  end
61
- end
62
53
 
63
- def has_many(*args)
64
- options = args.extract_options!
65
- collection_class = if class_name = options.delete(:class_name)
66
- type_name_with_module(class_name).constantize
54
+ def has_one(*args)
55
+ args.each do |name|
56
+ _associations << name unless _associations.include?(name)
57
+ attr_reader name
58
+
59
+ define_method("#{name}=") do |value|
60
+ if value
61
+ raise NotADocumentError unless Document === value
62
+ value._parent = ParentProxy.new(self, name)
63
+ value._root = _root || self
64
+ value._root.register_save_observer(value)
65
+ end
66
+ instance_variable_set("@#{name}", value)
67
+ end
68
+
69
+ validates_associated name
70
+ end
67
71
  end
68
72
 
69
- args.each do |name|
70
- _associations << name unless _associations.include?(name)
73
+ def has_many(*args)
74
+ options = args.extract_options!
75
+ collection_class = if class_name = options.delete(:class_name)
76
+ type_name_with_module(class_name).constantize
77
+ end
71
78
 
72
- define_method("#{name}") do
73
- association = instance_variable_get("@#{name}")
74
- unless association
75
- association = Proxy.new(:root => _root || self, :parent => self, :assoc_name => name, :collection_class => collection_class || self.class.type_name_with_module(name.to_s.classify).constantize)
76
- instance_variable_set("@#{name}", association)
79
+ args.each do |name|
80
+ _associations << name unless _associations.include?(name)
81
+
82
+ define_method("#{name}") do
83
+ association = instance_variable_get("@#{name}")
84
+ unless association
85
+ association = Proxy.new(:root => _root || self, :parent => self, :assoc_name => name, :collection_class => collection_class || self.class.type_name_with_module(name.to_s.classify).constantize)
86
+ instance_variable_set("@#{name}", association)
87
+ end
88
+ association
77
89
  end
78
- association
79
- end
80
90
 
81
- validates_associated name
91
+ validates_associated name
82
92
 
83
- define_method("#{name}=") do |array|
84
- proxy = send("#{name}")
85
- proxy.clear
86
- proxy << array
93
+ define_method("#{name}=") do |arrayish|
94
+ proxy = send("#{name}")
95
+ proxy.clear
96
+ Array.wrap(arrayish).each do|item|
97
+ proxy << item
98
+ end
99
+ end
87
100
  end
88
101
  end
89
- end
90
102
 
91
- def type_name_with_module(type_name)
92
- (/^::/ =~ type_name) ? type_name : "#{parents}::#{type_name}"
103
+ def type_name_with_module(type_name)
104
+ (/^::/ =~ type_name) ? type_name : "#{parents}::#{type_name}"
105
+ end
93
106
  end
94
107
  end
95
108
  end
@@ -4,11 +4,11 @@ module MongoDoc
4
4
  class Collection
5
5
  attr_accessor :_collection
6
6
  delegate :[], :clear, :count, :create_index, :db, :drop, :drop_index, :drop_indexes, :group, :hint, :index_information, :name, :options, :remove, :rename, :size, :to => :_collection
7
-
7
+
8
8
  def initialize(name)
9
9
  self._collection = self.class.mongo_collection(name)
10
10
  end
11
-
11
+
12
12
  def find(query = {}, options = {})
13
13
  cursor = MongoDoc::Cursor.new(_collection.find(query, options))
14
14
  if block_given?
@@ -18,28 +18,28 @@ module MongoDoc
18
18
  cursor
19
19
  end
20
20
  end
21
-
21
+
22
22
  def find_one(spec_or_object_id = nil, options = {})
23
23
  MongoDoc::BSON.decode(_collection.find_one(spec_or_object_id, options))
24
24
  end
25
-
25
+
26
26
  def insert(doc_or_docs, options = {})
27
27
  _collection.insert(doc_or_docs.to_bson, options)
28
28
  end
29
29
  alias :<< :insert
30
-
30
+
31
31
  def save(doc, options = {})
32
32
  _collection.save(doc.to_bson, options)
33
33
  end
34
-
34
+
35
35
  def update(spec, doc, options = {})
36
36
  _collection.update(spec, doc.to_bson, options)
37
- result = MongoDoc.database.db_command({'getlasterror' => 1})
37
+ result = MongoDoc.database.command({'getlasterror' => 1})
38
38
  (result and result.has_key?('updatedExisting')) ? result['updatedExisting'] : false
39
39
  end
40
-
40
+
41
41
  def self.mongo_collection(name)
42
42
  MongoDoc.database.collection(name)
43
43
  end
44
44
  end
45
- end
45
+ end