mongodoc 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
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