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.
Files changed (44) hide show
  1. data/README.textile +42 -12
  2. data/Rakefile +4 -4
  3. data/TODO +26 -0
  4. data/VERSION +1 -1
  5. data/examples/simple_document.rb +1 -1
  6. data/examples/simple_object.rb +0 -2
  7. data/features/mongodb.yml +6 -5
  8. data/features/removing_documents.feature +68 -0
  9. data/features/step_definitions/collection_steps.rb +3 -3
  10. data/features/step_definitions/document_steps.rb +2 -2
  11. data/features/step_definitions/removing_documents_steps.rb +14 -0
  12. data/features/support/support.rb +2 -2
  13. data/lib/mongodoc.rb +4 -7
  14. data/lib/mongodoc/associations/collection_proxy.rb +103 -0
  15. data/lib/mongodoc/associations/document_proxy.rb +53 -0
  16. data/lib/mongodoc/associations/hash_proxy.rb +96 -0
  17. data/lib/mongodoc/associations/proxy_base.rb +51 -0
  18. data/lib/mongodoc/attributes.rb +49 -17
  19. data/lib/mongodoc/collection.rb +15 -5
  20. data/lib/mongodoc/connection.rb +83 -20
  21. data/lib/mongodoc/criteria.rb +9 -4
  22. data/lib/mongodoc/cursor.rb +9 -3
  23. data/lib/mongodoc/document.rb +37 -24
  24. data/lib/mongodoc/validations/macros.rb +11 -0
  25. data/lib/mongodoc/validations/validates_embedded.rb +13 -0
  26. data/mongodb.example.yml +13 -5
  27. data/mongodoc.gemspec +33 -23
  28. data/spec/associations/collection_proxy_spec.rb +200 -0
  29. data/spec/associations/document_proxy_spec.rb +42 -0
  30. data/spec/associations/hash_proxy_spec.rb +163 -0
  31. data/spec/attributes_spec.rb +113 -47
  32. data/spec/bson_spec.rb +24 -24
  33. data/spec/collection_spec.rb +67 -86
  34. data/spec/connection_spec.rb +98 -150
  35. data/spec/criteria_spec.rb +4 -3
  36. data/spec/cursor_spec.rb +33 -27
  37. data/spec/document_spec.rb +173 -156
  38. data/spec/embedded_save_spec.rb +8 -3
  39. data/spec/new_record_spec.rb +33 -121
  40. metadata +80 -39
  41. data/lib/mongodoc/parent_proxy.rb +0 -44
  42. data/lib/mongodoc/proxy.rb +0 -83
  43. data/spec/parent_proxy_spec.rb +0 -44
  44. data/spec/proxy_spec.rb +0 -80
@@ -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[1], we can save an object directly:
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. Configuration
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
- Configure your database connection in ./mongodb.yml, you do not need one if you are running on localhost with the default port
140
+ bc. MongoDoc::Connection.config_path = './config/mongodb.yml'
108
141
 
109
- bc. name: test
110
- host: localhost
111
- port: 27017
112
- options:
113
- auto_reconnect: true
142
+ h3. Programmatically setting the database connection information
114
143
 
115
- You can change the location of the configuration file:
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.config_path = './config/mongodb.yml'
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. The Ruby driver exposes an API that understands JSON.
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.2"
14
- gem.add_dependency "mongo_ext", "= 0.18.2"
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.2.9"
18
- gem.add_development_dependency "cucumber", "= 0.4.4"
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
1
+ 0.2.2
@@ -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
 
@@ -8,8 +8,6 @@ class Address
8
8
  attr_accessor :street, :city, :state, :zip, :phone_number
9
9
  end
10
10
 
11
- MongoDoc.connect_to_database 'test'
12
-
13
11
  collection = MongoDoc::Collection.new('contacts')
14
12
  collection.drop
15
13
 
@@ -1,6 +1,7 @@
1
- name: test
2
- host: localhost
3
- port: 27017
4
- options:
5
- auto_reconnect: true
1
+ cucumber:
2
+ name: cucumber
3
+ host: localhost
4
+ port: 27017
5
+ options:
6
+ auto_reconnect: true
6
7
 
@@ -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) << hash.inject({}) do |attrs, (attr, value)|
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
@@ -4,7 +4,7 @@ require 'spec/expectations'
4
4
  require 'spec/bson_matchers'
5
5
  require 'mongodoc'
6
6
 
7
- MongoDoc.config_path = './features/mongodb.yml'
8
- MongoDoc.connect_to_database
7
+ MongoDoc::Connection.env = 'cucumber'
8
+ MongoDoc::Connection.config_path = './features/mongodb.yml'
9
9
 
10
10
  World(BsonMatchers)
@@ -1,20 +1,17 @@
1
1
  require 'rubygems'
2
2
 
3
- gem 'mongo', '0.18.2'
4
- gem 'mongo_ext', '0.18.2'
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 'activesupport'
9
+ require 'active_support'
10
10
  require 'validatable'
11
11
  require 'will_paginate/collection'
12
12
 
13
13
  module MongoDoc
14
- VERSION = '0.1.2'
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