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
@@ -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
|
|