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.
- data/README.textile +143 -0
- data/Rakefile +35 -3
- data/VERSION +1 -1
- data/examples/simple_document.rb +35 -0
- data/examples/simple_object.rb +32 -0
- data/features/finders.feature +72 -0
- data/features/mongodoc_base.feature +12 -2
- data/features/named_scopes.feature +66 -0
- data/features/new_record.feature +36 -0
- data/features/partial_updates.feature +105 -0
- data/features/step_definitions/criteria_steps.rb +4 -41
- data/features/step_definitions/document_steps.rb +56 -5
- data/features/step_definitions/documents.rb +14 -3
- data/features/step_definitions/finder_steps.rb +15 -0
- data/features/step_definitions/named_scope_steps.rb +18 -0
- data/features/step_definitions/partial_update_steps.rb +32 -0
- data/features/step_definitions/query_steps.rb +51 -0
- data/features/using_criteria.feature +5 -1
- data/lib/mongodoc/attributes.rb +76 -63
- data/lib/mongodoc/collection.rb +9 -9
- data/lib/mongodoc/criteria.rb +152 -161
- data/lib/mongodoc/cursor.rb +7 -5
- data/lib/mongodoc/document.rb +95 -31
- data/lib/mongodoc/finders.rb +29 -0
- data/lib/mongodoc/named_scope.rb +68 -0
- data/lib/mongodoc/parent_proxy.rb +15 -6
- data/lib/mongodoc/proxy.rb +22 -13
- data/lib/mongodoc.rb +3 -3
- data/mongodoc.gemspec +42 -14
- data/perf/mongodoc_runner.rb +90 -0
- data/perf/ruby_driver_runner.rb +64 -0
- data/spec/attributes_spec.rb +46 -12
- data/spec/collection_spec.rb +23 -23
- data/spec/criteria_spec.rb +124 -187
- data/spec/cursor_spec.rb +21 -17
- data/spec/document_ext.rb +2 -2
- data/spec/document_spec.rb +187 -218
- data/spec/embedded_save_spec.rb +104 -0
- data/spec/finders_spec.rb +81 -0
- data/spec/hash_matchers.rb +27 -0
- data/spec/named_scope_spec.rb +82 -0
- data/spec/new_record_spec.rb +216 -0
- data/spec/parent_proxy_spec.rb +8 -6
- data/spec/proxy_spec.rb +80 -0
- data/spec/spec_helper.rb +2 -0
- metadata +35 -7
- 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
|
-
|
63
|
+
@last_return = object.save
|
51
64
|
end
|
52
65
|
|
53
66
|
When /^I save the last document$/ do
|
54
|
-
|
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
|
-
|
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
|
86
|
-
|
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
|
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
|
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
|
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
|
data/lib/mongodoc/attributes.rb
CHANGED
@@ -3,93 +3,106 @@ require 'mongodoc/parent_proxy'
|
|
3
3
|
|
4
4
|
module MongoDoc
|
5
5
|
module Attributes
|
6
|
-
def self.
|
7
|
-
klass.
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
16
|
+
extend ClassMethods
|
17
|
+
end
|
18
|
+
end
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
association = send(a)
|
23
|
-
association._root = root if association
|
24
|
-
end
|
25
|
-
end
|
20
|
+
def _root
|
21
|
+
@_root
|
22
|
+
end
|
26
23
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
35
|
-
|
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
|
39
|
-
|
40
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
42
|
+
module ClassMethods
|
43
|
+
def _attributes
|
44
|
+
_keys + _associations
|
45
|
+
end
|
49
46
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
70
|
-
|
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
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
91
|
+
validates_associated name
|
82
92
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
92
|
-
|
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
|
data/lib/mongodoc/collection.rb
CHANGED
@@ -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.
|
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
|