mongodoc 0.2.1 → 0.2.2
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 +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
|