mongodoc 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +42 -12
- data/Rakefile +4 -4
- data/TODO +26 -0
- data/VERSION +1 -1
- data/examples/simple_document.rb +1 -1
- data/examples/simple_object.rb +0 -2
- data/features/mongodb.yml +6 -5
- data/features/removing_documents.feature +68 -0
- data/features/step_definitions/collection_steps.rb +3 -3
- data/features/step_definitions/document_steps.rb +2 -2
- data/features/step_definitions/removing_documents_steps.rb +14 -0
- data/features/support/support.rb +2 -2
- data/lib/mongodoc.rb +4 -7
- data/lib/mongodoc/associations/collection_proxy.rb +103 -0
- data/lib/mongodoc/associations/document_proxy.rb +53 -0
- data/lib/mongodoc/associations/hash_proxy.rb +96 -0
- data/lib/mongodoc/associations/proxy_base.rb +51 -0
- data/lib/mongodoc/attributes.rb +49 -17
- data/lib/mongodoc/collection.rb +15 -5
- data/lib/mongodoc/connection.rb +83 -20
- data/lib/mongodoc/criteria.rb +9 -4
- data/lib/mongodoc/cursor.rb +9 -3
- data/lib/mongodoc/document.rb +37 -24
- data/lib/mongodoc/validations/macros.rb +11 -0
- data/lib/mongodoc/validations/validates_embedded.rb +13 -0
- data/mongodb.example.yml +13 -5
- data/mongodoc.gemspec +33 -23
- data/spec/associations/collection_proxy_spec.rb +200 -0
- data/spec/associations/document_proxy_spec.rb +42 -0
- data/spec/associations/hash_proxy_spec.rb +163 -0
- data/spec/attributes_spec.rb +113 -47
- data/spec/bson_spec.rb +24 -24
- data/spec/collection_spec.rb +67 -86
- data/spec/connection_spec.rb +98 -150
- data/spec/criteria_spec.rb +4 -3
- data/spec/cursor_spec.rb +33 -27
- data/spec/document_spec.rb +173 -156
- data/spec/embedded_save_spec.rb +8 -3
- data/spec/new_record_spec.rb +33 -121
- metadata +80 -39
- data/lib/mongodoc/parent_proxy.rb +0 -44
- data/lib/mongodoc/proxy.rb +0 -83
- data/spec/parent_proxy_spec.rb +0 -44
- data/spec/proxy_spec.rb +0 -80
data/README.textile
CHANGED
@@ -2,6 +2,8 @@ h1. MongoDoc
|
|
2
2
|
|
3
3
|
Version: 0.2.1 1/18/10
|
4
4
|
|
5
|
+
2010-01-23 Tracking MongoDoc with @git@? *READ THIS NOTE*[1]
|
6
|
+
|
5
7
|
h2. Introduction
|
6
8
|
|
7
9
|
MongoDoc is a simple and easy to use ActiveRecord-like object mapper for "mongoDB":http://www.mongodb.org in Ruby.
|
@@ -26,7 +28,7 @@ class Address
|
|
26
28
|
attr_accessor :street, :city, :state, :zip, :phone_number
|
27
29
|
end
|
28
30
|
|
29
|
-
p. With MongoDoc, instead of saving JSON[
|
31
|
+
p. With MongoDoc, instead of saving JSON[2], we can save an object directly:
|
30
32
|
|
31
33
|
bc.. contact = Contact.new
|
32
34
|
contact.name = 'Hashrocket'
|
@@ -100,21 +102,48 @@ bc. hashrocket.addresses.first.update_attributes(:street => '320 First Street No
|
|
100
102
|
|
101
103
|
h2. Installation
|
102
104
|
|
105
|
+
MongoDoc *requires* mongoDB v1.3.2 or later.
|
106
|
+
|
103
107
|
bc. sudo gem install mongodoc
|
104
108
|
|
105
|
-
h2.
|
109
|
+
h2. Connecting
|
110
|
+
|
111
|
+
By default, MongoDoc will read its configuration from @./mongodb.yml@. If that file does not exist, it will attempt to connect to a standard MongoDB local server setup and use a database name of @"mongodoc"@.
|
112
|
+
|
113
|
+
h3. With Rails
|
114
|
+
|
115
|
+
If you are using Rails, MongoDoc will look for its configuration in @config/mongodb.yml@. If that file does not exist, it will attempt to connect to a standard MongoDB local server setup and use a database name of @#{Rails.root.basename}_#{Rails.env}@.
|
116
|
+
|
117
|
+
h3. Database configuration file
|
118
|
+
|
119
|
+
The file is similar to the Rails database.yml file, with environment definitions containing the database configuration attributes. For example:
|
120
|
+
|
121
|
+
bc. development:
|
122
|
+
name: development
|
123
|
+
host: localhost
|
124
|
+
port: 27017
|
125
|
+
options:
|
126
|
+
auto_reconnect: true
|
127
|
+
test:
|
128
|
+
name: test
|
129
|
+
host: localhost
|
130
|
+
port: 27017
|
131
|
+
options:
|
132
|
+
auto_reconnect: true
|
133
|
+
|
134
|
+
If you are not using Rails, the default environment is @development@ and you can set the current environment in your code:
|
135
|
+
|
136
|
+
bc. MongoDoc::Connection.env = 'test'
|
137
|
+
|
138
|
+
You can also change the location of the configuration file:
|
106
139
|
|
107
|
-
|
140
|
+
bc. MongoDoc::Connection.config_path = './config/mongodb.yml'
|
108
141
|
|
109
|
-
|
110
|
-
host: localhost
|
111
|
-
port: 27017
|
112
|
-
options:
|
113
|
-
auto_reconnect: true
|
142
|
+
h3. Programmatically setting the database connection information
|
114
143
|
|
115
|
-
|
144
|
+
Finally, if you do not want to use the database configuration file, you can also set the database name, host, port, options, and strict values directly; for example, to set the database name to @stats@:
|
116
145
|
|
117
|
-
bc. MongoDoc.
|
146
|
+
bc. MongoDoc::Connection.name = 'stats'
|
118
147
|
|
119
148
|
h2. Credits
|
120
149
|
|
@@ -137,7 +166,8 @@ h2. Note on Patches/Pull Requests
|
|
137
166
|
|
138
167
|
h2. Copyright
|
139
168
|
|
140
|
-
Copyright (c) 2009 Les Hill. See LICENSE for details.
|
169
|
+
Copyright (c) 2009 - 2010 Les Hill. See LICENSE for details.
|
141
170
|
|
142
|
-
fn1.
|
171
|
+
fn1. Building from @HEAD@? MongoDoc *requires* mongoDB v1.3.2 or later. That means you must be using the 1.3.x nightly build as of 2010-01-22 .
|
143
172
|
|
173
|
+
fn2. The Ruby driver exposes an API that understands JSON.
|
data/Rakefile
CHANGED
@@ -10,12 +10,12 @@ begin
|
|
10
10
|
gem.email = "leshill@gmail.com"
|
11
11
|
gem.homepage = "http://github.com/leshill/mongodoc"
|
12
12
|
gem.authors = ["Les Hill"]
|
13
|
-
gem.add_dependency "mongo", "= 0.18.
|
14
|
-
gem.add_dependency "mongo_ext", "= 0.18.
|
13
|
+
gem.add_dependency "mongo", "= 0.18.3"
|
14
|
+
gem.add_dependency "mongo_ext", "= 0.18.3"
|
15
15
|
gem.add_dependency "durran-validatable", "= 2.0.1"
|
16
16
|
gem.add_dependency "leshill-will_paginate", "= 2.3.11"
|
17
|
-
gem.add_development_dependency "rspec", "= 1.
|
18
|
-
gem.add_development_dependency "cucumber", "= 0.
|
17
|
+
gem.add_development_dependency "rspec", "= 1.3.0"
|
18
|
+
gem.add_development_dependency "cucumber", "= 0.6.2"
|
19
19
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
20
20
|
end
|
21
21
|
Jeweler::GemcutterTasks.new
|
data/TODO
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
As of 2010-02-05
|
2
|
+
|
3
|
+
Associations
|
4
|
+
------------
|
5
|
+
|
6
|
+
Pull associations out of attributes and refactor
|
7
|
+
|
8
|
+
Documentation
|
9
|
+
-------------
|
10
|
+
|
11
|
+
How to get started with Rails
|
12
|
+
Using mongohq
|
13
|
+
|
14
|
+
Dynamic Attributes
|
15
|
+
------------------
|
16
|
+
|
17
|
+
Document#[] => reads attribute
|
18
|
+
Document#[]= => writes attribute
|
19
|
+
Document#dynamic_attributes => key, value for each dynamic attribute
|
20
|
+
Document#dynamic_attribute_names => list of dynamic attribute names
|
21
|
+
|
22
|
+
Validations
|
23
|
+
-----------
|
24
|
+
|
25
|
+
validates_hash_keys :has_hash_name, :in => [array of names]
|
26
|
+
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.2
|
data/examples/simple_document.rb
CHANGED
@@ -20,12 +20,12 @@ class Contact
|
|
20
20
|
named_scope :in_state, lambda {|state| {:where => {'addresses.state' => state}}}
|
21
21
|
end
|
22
22
|
|
23
|
-
MongoDoc.connect_to_database 'test'
|
24
23
|
Contact.collection.drop
|
25
24
|
|
26
25
|
contact = Contact.new(:name => 'Hashrocket', :interests => ['ruby', 'rails', 'agile'])
|
27
26
|
contact.addresses << Address.new(:street => '320 1st Street North, #712', :city => 'Jacksonville Beach', :state => 'FL', :zip_code => '32250', :phone_number => '877 885 8846')
|
28
27
|
contact.save
|
28
|
+
puts Contact.find_one(contact.to_param).addresses.first.street
|
29
29
|
|
30
30
|
hashrocket = Contact.in_state('FL').find {|contact| contact.name == 'Hashrocket'}
|
31
31
|
|
data/examples/simple_object.rb
CHANGED
data/features/mongodb.yml
CHANGED
@@ -0,0 +1,68 @@
|
|
1
|
+
Feature: Removing Documents
|
2
|
+
|
3
|
+
Background:
|
4
|
+
Given an empty Contact document collection
|
5
|
+
And a Contact document named 'hashrocket' :
|
6
|
+
| Name | Type | Note |
|
7
|
+
| Hashrocket | company | Premier Rails development shop! |
|
8
|
+
And 'hashrocket' has interests, an array of:
|
9
|
+
| Interest |
|
10
|
+
| ruby |
|
11
|
+
| rails |
|
12
|
+
| employment |
|
13
|
+
| contract work |
|
14
|
+
| restaurants |
|
15
|
+
| hotels |
|
16
|
+
| flights |
|
17
|
+
| car rentals |
|
18
|
+
And 'hashrocket' has many addresses :
|
19
|
+
| Street | City | State | Zip Code |
|
20
|
+
| 320 First Street North | Jacksonville Beach | FL | 32250 |
|
21
|
+
| 1 Lake Michigan Street | Chicago | IL | 60611 |
|
22
|
+
| 1 Main Street | Santiago | Chile | |
|
23
|
+
And I save the document 'hashrocket'
|
24
|
+
And a Contact document named 'rocketeer' :
|
25
|
+
| Name | Note |
|
26
|
+
| Rocketeer Mike | Fantastic developer |
|
27
|
+
And 'rocketeer' has interests, an array of:
|
28
|
+
| Interest |
|
29
|
+
| ruby |
|
30
|
+
| rails |
|
31
|
+
| restaurants |
|
32
|
+
| employment |
|
33
|
+
And 'rocketeer' has many addresses :
|
34
|
+
| Street | City | State | Zip Code |
|
35
|
+
| 1 Main Street | Atlantic Beach | FL | 32233 |
|
36
|
+
And I save the document 'rocketeer'
|
37
|
+
And a Contact document named 'contractor' :
|
38
|
+
| Name | Note |
|
39
|
+
| Contractor Joe | Knows MongoDB |
|
40
|
+
And 'contractor' has interests, an array of:
|
41
|
+
| Interest |
|
42
|
+
| ruby |
|
43
|
+
| rails |
|
44
|
+
| contract work |
|
45
|
+
| flights |
|
46
|
+
| car rentals |
|
47
|
+
| hotels |
|
48
|
+
| restaurants |
|
49
|
+
And 'contractor' has many addresses :
|
50
|
+
| Street | City | State | Zip Code |
|
51
|
+
| 1 Main St. | Jacksonville | FL | 32218 |
|
52
|
+
And I save the document 'contractor'
|
53
|
+
And a Place document named 'hashrocket_hq' :
|
54
|
+
| Name | Type |
|
55
|
+
| Hashrocket | company |
|
56
|
+
And 'hashrocket_hq' has one Address as address (identified by 'hq_address'):
|
57
|
+
| Street | City | State | Zip Code |
|
58
|
+
| 1 Main St. | Jacksonville | FL | 32218 |
|
59
|
+
And I save the document 'hashrocket_hq'
|
60
|
+
|
61
|
+
Scenario: Simple Remove
|
62
|
+
Given the document 'contractor' roundtrips
|
63
|
+
When I remove 'contractor'
|
64
|
+
Then the document 'contractor' is not found
|
65
|
+
|
66
|
+
Scenario: Embedded Remove
|
67
|
+
When the document 'hashrocket_hq' roundtrips
|
68
|
+
Then an exception is raised if I remove 'hq_address'
|
@@ -1,11 +1,11 @@
|
|
1
1
|
Given /a new collection named '(.*)'/ do |name|
|
2
|
-
MongoDoc.database.drop_collection(name)
|
2
|
+
MongoDoc::Connection.database.drop_collection(name)
|
3
3
|
@collection = MongoDoc::Collection.new(name)
|
4
4
|
end
|
5
5
|
|
6
6
|
Given /^an empty (\w+) collection$/ do |name|
|
7
|
-
MongoDoc.database.drop_collection(name)
|
8
|
-
MongoDoc.database.create_collection(name, :strict => true)
|
7
|
+
MongoDoc::Connection.database.drop_collection(name)
|
8
|
+
MongoDoc::Connection.database.create_collection(name, :strict => true)
|
9
9
|
end
|
10
10
|
|
11
11
|
Then /the collection should have (\d+) documents?/ do |count|
|
@@ -31,10 +31,10 @@ end
|
|
31
31
|
Given /^'(.*)' has (?:a|an|many) (.*) :$/ do |doc_name, assoc_name, table|
|
32
32
|
doc = instance_variable_get("@#{doc_name}")
|
33
33
|
table.hashes.each do |hash|
|
34
|
-
doc.send(assoc_name)
|
34
|
+
doc.send(assoc_name).build(hash.inject({}) do |attrs, (attr, value)|
|
35
35
|
attrs["#{attr.underscore.gsub(' ', '_')}"] = value
|
36
36
|
attrs
|
37
|
-
end
|
37
|
+
end)
|
38
38
|
end
|
39
39
|
@all = doc.send(assoc_name)
|
40
40
|
@last = @all.last
|
@@ -0,0 +1,14 @@
|
|
1
|
+
When /^I remove '(.+)'$/ do |doc_name|
|
2
|
+
doc = instance_variable_get("@#{doc_name}")
|
3
|
+
doc.remove
|
4
|
+
end
|
5
|
+
|
6
|
+
Then /^the document '(.+)' is not found$/ do |doc_name|
|
7
|
+
doc = instance_variable_get("@#{doc_name}")
|
8
|
+
doc.class.find_one(doc.id)
|
9
|
+
end
|
10
|
+
|
11
|
+
Then /^an exception is raised if I remove '(.+)'$/ do |doc_name|
|
12
|
+
doc = instance_variable_get("@#{doc_name}")
|
13
|
+
lambda { doc.remove }.should raise_error
|
14
|
+
end
|
data/features/support/support.rb
CHANGED
@@ -4,7 +4,7 @@ require 'spec/expectations'
|
|
4
4
|
require 'spec/bson_matchers'
|
5
5
|
require 'mongodoc'
|
6
6
|
|
7
|
-
MongoDoc.
|
8
|
-
MongoDoc.
|
7
|
+
MongoDoc::Connection.env = 'cucumber'
|
8
|
+
MongoDoc::Connection.config_path = './features/mongodb.yml'
|
9
9
|
|
10
10
|
World(BsonMatchers)
|
data/lib/mongodoc.rb
CHANGED
@@ -1,20 +1,17 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
|
3
|
-
gem 'mongo', '0.18.
|
4
|
-
gem 'mongo_ext', '0.18.
|
3
|
+
gem 'mongo', '0.18.3'
|
4
|
+
gem 'mongo_ext', '0.18.3'
|
5
5
|
gem 'durran-validatable', '2.0.1'
|
6
6
|
gem 'leshill-will_paginate', '2.3.11'
|
7
7
|
|
8
8
|
require 'mongo'
|
9
|
-
require '
|
9
|
+
require 'active_support'
|
10
10
|
require 'validatable'
|
11
11
|
require 'will_paginate/collection'
|
12
12
|
|
13
13
|
module MongoDoc
|
14
|
-
VERSION = '0.
|
15
|
-
|
16
|
-
class NoConnectionError < RuntimeError; end
|
17
|
-
class NoDatabaseError < RuntimeError; end
|
14
|
+
VERSION = '0.2.2'
|
18
15
|
end
|
19
16
|
|
20
17
|
require 'mongodoc/connection'
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# Thanks Sandro!
|
2
|
+
# http://github.com/sandro
|
3
|
+
module MongoDoc
|
4
|
+
module Associations
|
5
|
+
class CollectionProxy < ProxyBase
|
6
|
+
# List of array methods (that are not in +Object+) that need to be
|
7
|
+
# delegated to +collection+.
|
8
|
+
ARRAY_METHODS = (Array.instance_methods - Object.instance_methods).map { |n| n.to_s }
|
9
|
+
|
10
|
+
# List of additional methods that must be delegated to +collection+.
|
11
|
+
MUST_DEFINE = %w[to_a to_ary inspect to_bson ==]
|
12
|
+
|
13
|
+
DO_NOT_DEFINE = %w[concat insert replace]
|
14
|
+
|
15
|
+
(ARRAY_METHODS + MUST_DEFINE - DO_NOT_DEFINE).uniq.each do |method|
|
16
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
17
|
+
def #{method}(*args, &block) # def each(*args, &block)
|
18
|
+
collection.send(:#{method}, *args, &block) # collection.send(:each, *args, &block)
|
19
|
+
end # end
|
20
|
+
RUBY
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :collection
|
24
|
+
|
25
|
+
def _root=(root)
|
26
|
+
@_root = root
|
27
|
+
collection.each do |item|
|
28
|
+
item._root = root if is_document?(item)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(options)
|
33
|
+
super
|
34
|
+
@collection = []
|
35
|
+
end
|
36
|
+
|
37
|
+
alias _append <<
|
38
|
+
def <<(item)
|
39
|
+
attach(item)
|
40
|
+
_append item
|
41
|
+
self
|
42
|
+
end
|
43
|
+
alias push <<
|
44
|
+
|
45
|
+
alias add []=
|
46
|
+
def []=(index, item)
|
47
|
+
attach(item)
|
48
|
+
add(index, item)
|
49
|
+
end
|
50
|
+
alias insert []=
|
51
|
+
|
52
|
+
def build(attrs)
|
53
|
+
item = assoc_class.new(attrs)
|
54
|
+
push(item)
|
55
|
+
end
|
56
|
+
|
57
|
+
def concat(array)
|
58
|
+
array.each do |item|
|
59
|
+
push(item)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Lie about our class. Borrowed from Rake::FileList
|
64
|
+
# Note: Does not work for case equality (<tt>===</tt>)
|
65
|
+
def is_a?(klass)
|
66
|
+
klass == Array || super(klass)
|
67
|
+
end
|
68
|
+
alias kind_of? is_a?
|
69
|
+
|
70
|
+
def replace(other)
|
71
|
+
clear
|
72
|
+
concat(other)
|
73
|
+
end
|
74
|
+
|
75
|
+
alias _unshift unshift
|
76
|
+
def unshift(item)
|
77
|
+
attach(item)
|
78
|
+
_unshift(item)
|
79
|
+
end
|
80
|
+
|
81
|
+
def valid?
|
82
|
+
all? do |child|
|
83
|
+
if is_document?(child)
|
84
|
+
child.valid?
|
85
|
+
else
|
86
|
+
true
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
protected
|
92
|
+
|
93
|
+
def annotated_keys(src, attrs)
|
94
|
+
assoc_path = "#{assoc_name}.#{index(src)}"
|
95
|
+
annotated = {}
|
96
|
+
attrs.each do |(key, value)|
|
97
|
+
annotated["#{assoc_path}.#{key}"] = value
|
98
|
+
end
|
99
|
+
annotated
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module MongoDoc
|
2
|
+
module Associations
|
3
|
+
class DocumentProxy < ProxyBase
|
4
|
+
|
5
|
+
attr_reader :document
|
6
|
+
|
7
|
+
def _root=(root)
|
8
|
+
@_root = root
|
9
|
+
document._root = root if is_document?(document)
|
10
|
+
end
|
11
|
+
|
12
|
+
def ==(other)
|
13
|
+
if self.class === other
|
14
|
+
document == other.document
|
15
|
+
else
|
16
|
+
document == other
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def build(attrs)
|
21
|
+
item = assoc_class.new(attrs)
|
22
|
+
self.document = item
|
23
|
+
end
|
24
|
+
|
25
|
+
def document=(doc)
|
26
|
+
attach(doc)
|
27
|
+
@document = doc
|
28
|
+
end
|
29
|
+
|
30
|
+
def valid?
|
31
|
+
if is_document?(document)
|
32
|
+
document.valid?
|
33
|
+
else
|
34
|
+
true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def method_missing(method, *args)
|
41
|
+
unless document.respond_to?(method)
|
42
|
+
raise NoMethodError, "undefined method `#{method.to_s}' for proxied \"#{document}\":#{document.class.to_s}"
|
43
|
+
end
|
44
|
+
|
45
|
+
if block_given?
|
46
|
+
document.send(method, *args) { |*block_args| yield(*block_args) }
|
47
|
+
else
|
48
|
+
document.send(method, *args)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|