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
@@ -0,0 +1,96 @@
|
|
1
|
+
module MongoDoc
|
2
|
+
class InvalidEmbeddedHashKey < RuntimeError; end
|
3
|
+
|
4
|
+
module Associations
|
5
|
+
class HashProxy < ProxyBase
|
6
|
+
HASH_METHODS = (Hash.instance_methods - Object.instance_methods).map { |n| n.to_s }
|
7
|
+
|
8
|
+
MUST_DEFINE = %w[to_a inspect to_bson ==]
|
9
|
+
|
10
|
+
DO_NOT_DEFINE = %w[merge! replace store update]
|
11
|
+
|
12
|
+
(HASH_METHODS + MUST_DEFINE - DO_NOT_DEFINE).uniq.each do |method|
|
13
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
14
|
+
def #{method}(*args, &block) # def each(*args, &block)
|
15
|
+
hash.send(:#{method}, *args, &block) # hash.send(:each, *args, &block)
|
16
|
+
end # end
|
17
|
+
RUBY
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :hash
|
21
|
+
|
22
|
+
def _root=(root)
|
23
|
+
@_root = root
|
24
|
+
hash.each do |key, value|
|
25
|
+
value._root = root if is_document?(value)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(options)
|
30
|
+
super
|
31
|
+
@hash = {}
|
32
|
+
end
|
33
|
+
|
34
|
+
alias put []=
|
35
|
+
def []=(key, value)
|
36
|
+
raise InvalidEmbeddedHashKey.new("Key name [#{key}] must be a valid element name, see http://www.mongodb.org/display/DOCS/BSON#BSON-noteonelementname") unless valid_key?(key)
|
37
|
+
attach(value)
|
38
|
+
put(key, value)
|
39
|
+
end
|
40
|
+
alias store []=
|
41
|
+
|
42
|
+
def build(key, attrs)
|
43
|
+
item = assoc_class.new(attrs)
|
44
|
+
store(key, item)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Lie about our class. Borrowed from Rake::FileList
|
48
|
+
# Note: Does not work for case equality (<tt>===</tt>)
|
49
|
+
def is_a?(klass)
|
50
|
+
klass == Hash || super(klass)
|
51
|
+
end
|
52
|
+
alias kind_of? is_a?
|
53
|
+
|
54
|
+
def merge!(other)
|
55
|
+
other.each_pair do |key, value|
|
56
|
+
self[key] = if block_given?
|
57
|
+
yield key, [key], value
|
58
|
+
else
|
59
|
+
value
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
alias update merge!
|
64
|
+
|
65
|
+
def replace(other)
|
66
|
+
clear
|
67
|
+
merge!(other)
|
68
|
+
end
|
69
|
+
|
70
|
+
def valid?
|
71
|
+
values.all? do |child|
|
72
|
+
if is_document?(child)
|
73
|
+
child.valid?
|
74
|
+
else
|
75
|
+
true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
protected
|
81
|
+
|
82
|
+
def annotated_keys(src, attrs)
|
83
|
+
assoc_path = "#{assoc_name}.#{index(src)}"
|
84
|
+
annotated = {}
|
85
|
+
attrs.each do |(key, value)|
|
86
|
+
annotated["#{assoc_path}.#{key}"] = value
|
87
|
+
end
|
88
|
+
annotated
|
89
|
+
end
|
90
|
+
|
91
|
+
def valid_key?(key)
|
92
|
+
(String === key or Symbol === key) and key.to_s !~ /(_id|query|\$.*|.*\..*)/
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module MongoDoc
|
2
|
+
module Associations
|
3
|
+
class ProxyBase
|
4
|
+
undef_method :id, :to_bson
|
5
|
+
|
6
|
+
attr_reader :assoc_name, :assoc_class, :_parent, :_root
|
7
|
+
|
8
|
+
def _parent=(parent)
|
9
|
+
@_parent = parent
|
10
|
+
end
|
11
|
+
|
12
|
+
def _path_to_root(src, attrs)
|
13
|
+
_parent._path_to_root(src, annotated_keys(src, attrs))
|
14
|
+
end
|
15
|
+
|
16
|
+
def _root=(root)
|
17
|
+
@_root = root
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(options)
|
21
|
+
@assoc_name = options[:assoc_name]
|
22
|
+
@assoc_class = options[:assoc_class]
|
23
|
+
@_root = options[:root]
|
24
|
+
@_parent = options[:parent]
|
25
|
+
end
|
26
|
+
|
27
|
+
def attach(item)
|
28
|
+
if is_document?(item)
|
29
|
+
item._parent = self
|
30
|
+
item._root = _root
|
31
|
+
_root.send(:register_save_observer, item)
|
32
|
+
end
|
33
|
+
item
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def annotated_keys(src, hash)
|
39
|
+
annotated = {}
|
40
|
+
hash.each do |(key, value)|
|
41
|
+
annotated["#{assoc_name}.#{key}"] = value
|
42
|
+
end
|
43
|
+
annotated
|
44
|
+
end
|
45
|
+
|
46
|
+
def is_document?(object)
|
47
|
+
object.respond_to?(:_parent)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/mongodoc/attributes.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
require 'mongodoc/
|
2
|
-
require 'mongodoc/
|
1
|
+
require 'mongodoc/associations/proxy_base'
|
2
|
+
require 'mongodoc/associations/collection_proxy'
|
3
|
+
require 'mongodoc/associations/document_proxy'
|
4
|
+
require 'mongodoc/associations/hash_proxy'
|
3
5
|
|
4
6
|
module MongoDoc
|
5
7
|
module Attributes
|
@@ -34,11 +36,6 @@ module MongoDoc
|
|
34
36
|
_parent._path_to_root(self, attrs)
|
35
37
|
end
|
36
38
|
|
37
|
-
def _selector_path_to_root(selector)
|
38
|
-
return selector unless _parent
|
39
|
-
_parent._selector_path_to_root(selector)
|
40
|
-
end
|
41
|
-
|
42
39
|
module ClassMethods
|
43
40
|
def _attributes
|
44
41
|
_keys + _associations
|
@@ -52,27 +49,32 @@ module MongoDoc
|
|
52
49
|
end
|
53
50
|
|
54
51
|
def has_one(*args)
|
52
|
+
options = args.extract_options!
|
53
|
+
assoc_class = if class_name = options.delete(:class_name)
|
54
|
+
type_name_with_module(class_name).constantize
|
55
|
+
end
|
56
|
+
|
55
57
|
args.each do |name|
|
56
58
|
_associations << name unless _associations.include?(name)
|
59
|
+
|
57
60
|
attr_reader name
|
58
61
|
|
59
62
|
define_method("#{name}=") do |value|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
value._root.register_save_observer(value)
|
63
|
+
association = instance_variable_get("@#{name}")
|
64
|
+
unless association
|
65
|
+
association = Associations::DocumentProxy.new(:root => _root || self, :parent => self, :assoc_name => name, :assoc_class => assoc_class || self.class.class_from_name(name))
|
66
|
+
instance_variable_set("@#{name}", association)
|
65
67
|
end
|
66
|
-
|
68
|
+
association.document = value
|
67
69
|
end
|
68
70
|
|
69
|
-
|
71
|
+
validates_embedded name, :if => Proc.new { !send(name).nil? }
|
70
72
|
end
|
71
73
|
end
|
72
74
|
|
73
75
|
def has_many(*args)
|
74
76
|
options = args.extract_options!
|
75
|
-
|
77
|
+
assoc_class = if class_name = options.delete(:class_name)
|
76
78
|
type_name_with_module(class_name).constantize
|
77
79
|
end
|
78
80
|
|
@@ -82,13 +84,13 @@ module MongoDoc
|
|
82
84
|
define_method("#{name}") do
|
83
85
|
association = instance_variable_get("@#{name}")
|
84
86
|
unless association
|
85
|
-
association =
|
87
|
+
association = Associations::CollectionProxy.new(:root => _root || self, :parent => self, :assoc_name => name, :assoc_class => assoc_class || self.class.type_name_with_module(name.to_s.classify).constantize)
|
86
88
|
instance_variable_set("@#{name}", association)
|
87
89
|
end
|
88
90
|
association
|
89
91
|
end
|
90
92
|
|
91
|
-
|
93
|
+
validates_embedded name
|
92
94
|
|
93
95
|
define_method("#{name}=") do |arrayish|
|
94
96
|
proxy = send("#{name}")
|
@@ -100,6 +102,36 @@ module MongoDoc
|
|
100
102
|
end
|
101
103
|
end
|
102
104
|
|
105
|
+
def has_hash(*args)
|
106
|
+
options = args.extract_options!
|
107
|
+
assoc_class = if class_name = options.delete(:class_name)
|
108
|
+
type_name_with_module(class_name).constantize
|
109
|
+
end
|
110
|
+
|
111
|
+
args.each do |name|
|
112
|
+
_associations << name unless _associations.include?(name)
|
113
|
+
|
114
|
+
define_method("#{name}") do
|
115
|
+
association = instance_variable_get("@#{name}")
|
116
|
+
unless association
|
117
|
+
association = Associations::HashProxy.new(:root => _root || self, :parent => self, :assoc_name => name, :assoc_class => assoc_class || self.class.type_name_with_module(name.to_s.classify).constantize)
|
118
|
+
instance_variable_set("@#{name}", association)
|
119
|
+
end
|
120
|
+
association
|
121
|
+
end
|
122
|
+
|
123
|
+
validates_embedded name
|
124
|
+
|
125
|
+
define_method("#{name}=") do |hash|
|
126
|
+
send("#{name}").replace(hash)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def class_from_name(name)
|
132
|
+
type_name_with_module(name.to_s.classify).constantize rescue nil
|
133
|
+
end
|
134
|
+
|
103
135
|
def type_name_with_module(type_name)
|
104
136
|
(/^::/ =~ type_name) ? type_name : "#{parents}::#{type_name}"
|
105
137
|
end
|
data/lib/mongodoc/collection.rb
CHANGED
@@ -3,14 +3,15 @@ require 'mongodoc/cursor'
|
|
3
3
|
module MongoDoc
|
4
4
|
class Collection
|
5
5
|
attr_accessor :_collection
|
6
|
-
|
6
|
+
|
7
|
+
delegate :[], :clear, :count, :create_index, :db, :distinct, :drop, :drop_index, :drop_indexes, :group, :hint, :index_information, :map_reduce, :mapreduce, :name, :options, :pk_factory, :remove, :rename, :size, :to => :_collection
|
7
8
|
|
8
9
|
def initialize(name)
|
9
10
|
self._collection = self.class.mongo_collection(name)
|
10
11
|
end
|
11
12
|
|
12
13
|
def find(query = {}, options = {})
|
13
|
-
cursor =
|
14
|
+
cursor = wrapped_cursor(query, options)
|
14
15
|
if block_given?
|
15
16
|
yield cursor
|
16
17
|
cursor.close
|
@@ -34,12 +35,21 @@ module MongoDoc
|
|
34
35
|
|
35
36
|
def update(spec, doc, options = {})
|
36
37
|
_collection.update(spec, doc.to_bson, options)
|
37
|
-
|
38
|
-
|
38
|
+
(last_error || {})['updatedExisting'] || false
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def last_error
|
44
|
+
MongoDoc::Connection.database.command({'getlasterror' => 1})
|
45
|
+
end
|
46
|
+
|
47
|
+
def wrapped_cursor(query = {}, options = {})
|
48
|
+
MongoDoc::Cursor.new(self, _collection.find(query, options))
|
39
49
|
end
|
40
50
|
|
41
51
|
def self.mongo_collection(name)
|
42
|
-
MongoDoc.database.collection(name)
|
52
|
+
MongoDoc::Connection.database.collection(name)
|
43
53
|
end
|
44
54
|
end
|
45
55
|
end
|
data/lib/mongodoc/connection.rb
CHANGED
@@ -1,25 +1,88 @@
|
|
1
1
|
module MongoDoc
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
def self.connect_to_database(name = nil, host = nil, port = nil, options = nil, force_connect = false)
|
6
|
-
name ||= configuration['name']
|
7
|
-
self.database = ((!force_connect && connection)|| connect(host, port, options)).db(name)
|
8
|
-
raise NoDatabaseError unless database
|
9
|
-
database
|
10
|
-
end
|
2
|
+
class NoConnectionError < RuntimeError; end
|
3
|
+
class UnsupportedServerVersionError < RuntimeError; end
|
11
4
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
5
|
+
module Connection
|
6
|
+
|
7
|
+
extend self
|
8
|
+
|
9
|
+
attr_writer :config_path, :env, :host, :name, :options, :port, :strict
|
10
|
+
|
11
|
+
def config_path
|
12
|
+
@config_path || default_path
|
13
|
+
end
|
14
|
+
|
15
|
+
def configuration
|
16
|
+
@configuration ||= File.exists?(config_path) ? YAML.load_file(config_path)[env] : {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def connection
|
20
|
+
@connection ||= connect
|
21
|
+
end
|
22
|
+
|
23
|
+
def database
|
24
|
+
@database ||= connection.db(name, :strict => strict)
|
25
|
+
end
|
26
|
+
|
27
|
+
def env
|
28
|
+
if rails?
|
29
|
+
Rails.env
|
30
|
+
else
|
31
|
+
@env ||= 'development'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def host
|
36
|
+
@host ||= configuration['host']
|
37
|
+
end
|
38
|
+
|
39
|
+
def name
|
40
|
+
@name ||= configuration['name'] || default_name
|
41
|
+
end
|
42
|
+
|
43
|
+
def options
|
44
|
+
@options ||= configuration['options'] || {}
|
45
|
+
end
|
46
|
+
|
47
|
+
def port
|
48
|
+
@port ||= configuration['port']
|
49
|
+
end
|
50
|
+
|
51
|
+
def strict
|
52
|
+
@strict ||= configuration['strict'] || false
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def connect
|
58
|
+
connection = Mongo::Connection.new(host, port, options)
|
59
|
+
raise NoConnectionError unless connection
|
60
|
+
verify_server_version(connection)
|
61
|
+
connection
|
62
|
+
end
|
63
|
+
|
64
|
+
def default_name
|
65
|
+
if rails?
|
66
|
+
"#{Rails.root.basename}_#{Rails.env}"
|
67
|
+
else
|
68
|
+
"mongodoc"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def default_path
|
73
|
+
if rails?
|
74
|
+
Rails.root + 'config/mongodb.yml'
|
75
|
+
else
|
76
|
+
'./mongodb.yml'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def rails?
|
81
|
+
Object.const_defined?("Rails")
|
82
|
+
end
|
21
83
|
|
22
|
-
|
23
|
-
|
84
|
+
def verify_server_version(connection)
|
85
|
+
raise UnsupportedServerVersionError.new('MongoDoc requires at least mongoDB version 1.3.2') unless connection.server_version >= "1.3.2"
|
86
|
+
end
|
24
87
|
end
|
25
88
|
end
|
data/lib/mongodoc/criteria.rb
CHANGED
@@ -101,11 +101,12 @@ module MongoDoc #:nodoc:
|
|
101
101
|
def each(&block)
|
102
102
|
@collection ||= execute
|
103
103
|
if block_given?
|
104
|
-
|
104
|
+
container = []
|
105
|
+
collection.each do |item|
|
105
106
|
container << item
|
106
107
|
yield item
|
107
|
-
container
|
108
108
|
end
|
109
|
+
@collection = container
|
109
110
|
end
|
110
111
|
self
|
111
112
|
end
|
@@ -166,9 +167,10 @@ module MongoDoc #:nodoc:
|
|
166
167
|
#
|
167
168
|
# Returns <tt>self</tt>
|
168
169
|
def criteria(criteria_conditions = {})
|
169
|
-
criteria_conditions.
|
170
|
-
|
170
|
+
criteria_conditions.each do |(key, value)|
|
171
|
+
send(key, value)
|
171
172
|
end
|
173
|
+
self
|
172
174
|
end
|
173
175
|
|
174
176
|
# Adds a criterion to the +Criteria+ that specifies values that must all
|
@@ -242,6 +244,9 @@ module MongoDoc #:nodoc:
|
|
242
244
|
#
|
243
245
|
# Returns: <tt>self</tt>
|
244
246
|
def id(id_or_object_id)
|
247
|
+
if id_or_object_id.kind_of?(String)
|
248
|
+
id_or_object_id = Mongo::ObjectID.from_string(id_or_object_id)
|
249
|
+
end
|
245
250
|
selector[:_id] = id_or_object_id; self
|
246
251
|
end
|
247
252
|
|