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