mongo_mapper 0.5.6 → 0.5.7
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -1
- data/README.rdoc +3 -0
- data/VERSION +1 -1
- data/lib/mongo_mapper.rb +14 -6
- data/lib/mongo_mapper/associations.rb +11 -5
- data/lib/mongo_mapper/associations/base.rb +17 -5
- data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +0 -2
- data/lib/mongo_mapper/associations/many_documents_proxy.rb +15 -15
- data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +2 -2
- data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +1 -1
- data/lib/mongo_mapper/associations/proxy.rb +1 -0
- data/lib/mongo_mapper/callbacks.rb +18 -0
- data/lib/mongo_mapper/document.rb +206 -89
- data/lib/mongo_mapper/dynamic_finder.rb +1 -1
- data/lib/mongo_mapper/embedded_document.rb +7 -3
- data/lib/mongo_mapper/finder_options.rb +87 -66
- data/lib/mongo_mapper/pagination.rb +2 -0
- data/lib/mongo_mapper/serialization.rb +2 -3
- data/lib/mongo_mapper/serializers/json_serializer.rb +1 -1
- data/lib/mongo_mapper/support.rb +9 -0
- data/lib/mongo_mapper/validations.rb +3 -1
- data/mongo_mapper.gemspec +4 -4
- data/test/functional/associations/test_many_documents_as_proxy.rb +2 -2
- data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +25 -1
- data/test/functional/associations/test_many_embedded_proxy.rb +25 -0
- data/test/functional/associations/test_many_polymorphic_proxy.rb +48 -6
- data/test/functional/associations/test_many_proxy.rb +27 -6
- data/test/functional/test_document.rb +49 -29
- data/test/functional/test_pagination.rb +17 -17
- data/test/functional/test_validations.rb +35 -14
- data/test/models.rb +85 -10
- data/test/support/{test_timing.rb → timing.rb} +1 -1
- data/test/test_helper.rb +8 -8
- data/test/unit/test_association_base.rb +17 -0
- data/test/unit/test_document.rb +12 -1
- data/test/unit/test_embedded_document.rb +13 -4
- data/test/unit/test_finder_options.rb +50 -48
- data/test/unit/test_pagination.rb +4 -0
- metadata +4 -4
data/.gitignore
CHANGED
data/README.rdoc
CHANGED
@@ -35,7 +35,10 @@ You can also just declare the source:
|
|
35
35
|
|
36
36
|
== Documentation
|
37
37
|
|
38
|
+
Documentation is lacking right now because if you can't look through the code right now and feel comfortable, this is probably too young for you to use. Wait til it stabilizes a bit more.
|
39
|
+
|
38
40
|
http://rdoc.info/projects/jnunemaker/mongomapper
|
41
|
+
http://groups.google.com/group/mongomapper
|
39
42
|
|
40
43
|
== More Info
|
41
44
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.5.
|
1
|
+
0.5.7
|
data/lib/mongo_mapper.rb
CHANGED
@@ -3,8 +3,17 @@ require 'mongo'
|
|
3
3
|
require 'validatable'
|
4
4
|
|
5
5
|
module MongoMapper
|
6
|
-
|
7
|
-
|
6
|
+
# generic MM error
|
7
|
+
class MongoMapperError < StandardError; end
|
8
|
+
|
9
|
+
# raised when key expected to exist but not found
|
10
|
+
class KeyNotFound < MongoMapperError; end
|
11
|
+
|
12
|
+
# raised when document expected but not found
|
13
|
+
class DocumentNotFound < MongoMapperError; end
|
14
|
+
|
15
|
+
# raised when document not valid and using !
|
16
|
+
class DocumentNotValid < MongoMapperError
|
8
17
|
def initialize(document)
|
9
18
|
@document = document
|
10
19
|
super("Validation failed: #{@document.errors.full_messages.join(", ")}")
|
@@ -54,13 +63,12 @@ module MongoMapper
|
|
54
63
|
module Finders
|
55
64
|
def dynamic_find(finder, args)
|
56
65
|
attributes = {}
|
57
|
-
find_options = args.extract_options!.deep_merge(:conditions => attributes)
|
58
|
-
|
59
66
|
finder.attributes.each_with_index do |attr, index|
|
60
67
|
attributes[attr] = args[index]
|
61
68
|
end
|
62
|
-
|
63
|
-
|
69
|
+
|
70
|
+
options = args.extract_options!.merge(attributes)
|
71
|
+
result = find(finder.finder, options)
|
64
72
|
|
65
73
|
if result.nil?
|
66
74
|
if finder.bang
|
@@ -1,13 +1,13 @@
|
|
1
1
|
module MongoMapper
|
2
2
|
module Associations
|
3
3
|
module ClassMethods
|
4
|
-
def belongs_to(association_id, options
|
4
|
+
def belongs_to(association_id, options={})
|
5
5
|
create_association(:belongs_to, association_id, options)
|
6
6
|
self
|
7
7
|
end
|
8
8
|
|
9
|
-
def many(association_id, options = {})
|
10
|
-
create_association(:many, association_id, options)
|
9
|
+
def many(association_id, options = {}, &block)
|
10
|
+
create_association(:many, association_id, options, &block)
|
11
11
|
self
|
12
12
|
end
|
13
13
|
|
@@ -18,13 +18,20 @@ module MongoMapper
|
|
18
18
|
end
|
19
19
|
|
20
20
|
private
|
21
|
-
def create_association(type, name, options)
|
21
|
+
def create_association(type, name, options, &extension)
|
22
|
+
options[:extend] = modulized_extensions(extension, options[:extend])
|
22
23
|
association = Associations::Base.new(type, name, options)
|
23
24
|
associations[association.name] = association
|
24
25
|
define_association_methods(association)
|
25
26
|
define_dependent_callback(association)
|
26
27
|
association
|
27
28
|
end
|
29
|
+
|
30
|
+
def modulized_extensions(*extensions)
|
31
|
+
extensions.flatten.compact.map do |extension|
|
32
|
+
Proc === extension ? Module.new(&extension) : extension
|
33
|
+
end
|
34
|
+
end
|
28
35
|
|
29
36
|
def define_association_methods(association)
|
30
37
|
define_method(association.name) do
|
@@ -59,7 +66,6 @@ module MongoMapper
|
|
59
66
|
end
|
60
67
|
end
|
61
68
|
end
|
62
|
-
|
63
69
|
end
|
64
70
|
|
65
71
|
module InstanceMethods
|
@@ -1,10 +1,22 @@
|
|
1
1
|
module MongoMapper
|
2
2
|
module Associations
|
3
3
|
class Base
|
4
|
-
attr_reader :type, :name, :options
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
attr_reader :type, :name, :options, :finder_options
|
5
|
+
|
6
|
+
# Options that should not be considered MongoDB query options/criteria
|
7
|
+
AssociationOptions = [:as, :class_name, :dependent, :extend, :foreign_key, :polymorphic]
|
8
|
+
|
9
|
+
def initialize(type, name, options={})
|
10
|
+
@type, @name = type, name
|
11
|
+
@options, @finder_options = {}, {}
|
12
|
+
|
13
|
+
options.each_pair do |key, value|
|
14
|
+
if AssociationOptions.include?(key)
|
15
|
+
@options[key] = value
|
16
|
+
else
|
17
|
+
@finder_options[key] = value
|
18
|
+
end
|
19
|
+
end
|
8
20
|
end
|
9
21
|
|
10
22
|
def class_name
|
@@ -26,7 +38,7 @@ module MongoMapper
|
|
26
38
|
def many?
|
27
39
|
@many_type ||= @type == :many
|
28
40
|
end
|
29
|
-
|
41
|
+
|
30
42
|
def belongs_to?
|
31
43
|
@belongs_to_type ||= @type == :belongs_to
|
32
44
|
end
|
@@ -4,7 +4,7 @@ module MongoMapper
|
|
4
4
|
delegate :klass, :to => :@association
|
5
5
|
delegate :collection, :to => :klass
|
6
6
|
|
7
|
-
include MongoMapper::Finders
|
7
|
+
include ::MongoMapper::Finders
|
8
8
|
|
9
9
|
def find(*args)
|
10
10
|
options = args.extract_options!
|
@@ -16,19 +16,19 @@ module MongoMapper
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def all(options={})
|
19
|
-
|
19
|
+
klass.all(scoped_options(options))
|
20
20
|
end
|
21
21
|
|
22
22
|
def first(options={})
|
23
|
-
|
23
|
+
klass.first(scoped_options(options))
|
24
24
|
end
|
25
25
|
|
26
26
|
def last(options={})
|
27
|
-
|
27
|
+
klass.last(scoped_options(options))
|
28
28
|
end
|
29
29
|
|
30
|
-
def count(
|
31
|
-
klass.count(
|
30
|
+
def count(options={})
|
31
|
+
klass.count(scoped_options(options))
|
32
32
|
end
|
33
33
|
|
34
34
|
def replace(docs)
|
@@ -57,20 +57,20 @@ module MongoMapper
|
|
57
57
|
doc
|
58
58
|
end
|
59
59
|
|
60
|
-
def destroy_all(
|
61
|
-
all(
|
60
|
+
def destroy_all(options={})
|
61
|
+
all(options).map(&:destroy)
|
62
62
|
reset
|
63
63
|
end
|
64
64
|
|
65
|
-
def delete_all(
|
66
|
-
klass.delete_all(
|
65
|
+
def delete_all(options={})
|
66
|
+
klass.delete_all(options.merge(scoped_conditions))
|
67
67
|
reset
|
68
68
|
end
|
69
69
|
|
70
70
|
def nullify
|
71
|
-
criteria = FinderOptions.
|
71
|
+
criteria = FinderOptions.new(klass, scoped_conditions).criteria
|
72
72
|
all(criteria).each do |doc|
|
73
|
-
doc.update_attributes
|
73
|
+
doc.update_attributes(self.foreign_key => nil)
|
74
74
|
end
|
75
75
|
reset
|
76
76
|
end
|
@@ -89,13 +89,13 @@ module MongoMapper
|
|
89
89
|
def scoped_conditions
|
90
90
|
{self.foreign_key => @owner.id}
|
91
91
|
end
|
92
|
-
|
92
|
+
|
93
93
|
def scoped_options(options)
|
94
|
-
options.
|
94
|
+
@association.finder_options.merge(options).merge(scoped_conditions)
|
95
95
|
end
|
96
96
|
|
97
97
|
def find_target
|
98
|
-
|
98
|
+
all
|
99
99
|
end
|
100
100
|
|
101
101
|
def ensure_owner_saved
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module MongoMapper
|
2
2
|
module Associations
|
3
|
-
class ManyEmbeddedPolymorphicProxy < Proxy
|
3
|
+
class ManyEmbeddedPolymorphicProxy < Proxy
|
4
4
|
def replace(v)
|
5
5
|
@_values = v.map do |doc_or_hash|
|
6
6
|
if doc_or_hash.kind_of?(EmbeddedDocument)
|
@@ -30,4 +30,4 @@ module MongoMapper
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
33
|
-
end
|
33
|
+
end
|
@@ -1,4 +1,17 @@
|
|
1
1
|
module MongoMapper
|
2
|
+
# This module is mixed into the Document module to provide call-backs before
|
3
|
+
# and after the following events:
|
4
|
+
#
|
5
|
+
# * save
|
6
|
+
# * create
|
7
|
+
# * update
|
8
|
+
# * validation
|
9
|
+
# ** every validation
|
10
|
+
# ** validation when created
|
11
|
+
# ** validation when updated
|
12
|
+
# * destruction
|
13
|
+
#
|
14
|
+
# @see ActiveSupport::Callbacks
|
2
15
|
module Callbacks
|
3
16
|
def self.included(model) #:nodoc:
|
4
17
|
model.class_eval do
|
@@ -42,6 +55,11 @@ module MongoMapper
|
|
42
55
|
return result
|
43
56
|
end
|
44
57
|
|
58
|
+
# Here we override the +destroy+ method to allow for the +before_destroy+
|
59
|
+
# and +after_destroy+ call-backs. Note that the +destroy+ call is aborted
|
60
|
+
# if the +before_destroy+ call-back returns +false+.
|
61
|
+
#
|
62
|
+
# @return the result of calling +destroy+ on the document
|
45
63
|
def destroy #:nodoc:
|
46
64
|
return false if callback(:before_destroy) == false
|
47
65
|
result = super
|
@@ -13,12 +13,12 @@ module MongoMapper
|
|
13
13
|
extend Validations::Macros
|
14
14
|
extend ClassMethods
|
15
15
|
extend Finders
|
16
|
-
|
16
|
+
|
17
17
|
def self.per_page
|
18
18
|
25
|
19
19
|
end unless respond_to?(:per_page)
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
descendants << model
|
23
23
|
end
|
24
24
|
|
@@ -32,17 +32,29 @@ module MongoMapper
|
|
32
32
|
create_indexes_for(key)
|
33
33
|
key
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
def ensure_index(name_or_array, options={})
|
37
37
|
keys_to_index = if name_or_array.is_a?(Array)
|
38
38
|
name_or_array.map { |pair| [pair[0], pair[1]] }
|
39
39
|
else
|
40
40
|
name_or_array
|
41
41
|
end
|
42
|
-
|
42
|
+
|
43
43
|
MongoMapper.ensure_index(self, keys_to_index, options)
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
|
+
# @overload find(:first, options)
|
47
|
+
# @see Document.first
|
48
|
+
#
|
49
|
+
# @overload find(:last, options)
|
50
|
+
# @see Document.last
|
51
|
+
#
|
52
|
+
# @overload find(:all, options)
|
53
|
+
# @see Document.all
|
54
|
+
#
|
55
|
+
# @overload find(ids, options)
|
56
|
+
#
|
57
|
+
# @raise DocumentNotFound raised when no ID or arguments are provided
|
46
58
|
def find(*args)
|
47
59
|
options = args.extract_options!
|
48
60
|
case args.first
|
@@ -55,7 +67,7 @@ module MongoMapper
|
|
55
67
|
when 0
|
56
68
|
raise DocumentNotFound, "Couldn't find without an ID"
|
57
69
|
when 1
|
58
|
-
find_one(args[0]
|
70
|
+
find_one!(options.merge({:_id => args[0]}))
|
59
71
|
else
|
60
72
|
find_some(args, options)
|
61
73
|
end
|
@@ -63,68 +75,113 @@ module MongoMapper
|
|
63
75
|
end
|
64
76
|
|
65
77
|
def paginate(options)
|
66
|
-
per_page = options.delete(:per_page) ||
|
78
|
+
per_page = options.delete(:per_page) || self.per_page
|
67
79
|
page = options.delete(:page)
|
68
|
-
total_entries = count(options
|
69
|
-
|
80
|
+
total_entries = count(options)
|
81
|
+
pagination = Pagination::PaginationProxy.new(total_entries, page, per_page)
|
70
82
|
|
71
|
-
options
|
72
|
-
|
73
|
-
|
74
|
-
collection.subject = find_every(options)
|
75
|
-
collection
|
83
|
+
options.merge!(:limit => pagination.limit, :skip => pagination.skip)
|
84
|
+
pagination.subject = find_every(options)
|
85
|
+
pagination
|
76
86
|
end
|
77
87
|
|
88
|
+
# @param [Hash] options any conditions understood by
|
89
|
+
# FinderOptions.to_mongo_criteria
|
90
|
+
#
|
91
|
+
# @return the first document in the ordered collection as described by
|
92
|
+
# +options+
|
93
|
+
#
|
94
|
+
# @see FinderOptions
|
78
95
|
def first(options={})
|
79
|
-
options
|
80
|
-
find_every(options)[0]
|
96
|
+
find_one(options)
|
81
97
|
end
|
82
98
|
|
99
|
+
# @param [Hash] options any conditions understood by
|
100
|
+
# FinderOptions.to_mongo_criteria
|
101
|
+
# @option [String] :order this *mandatory* option describes how to
|
102
|
+
# identify the ordering of the documents in your collection. Note that
|
103
|
+
# the *last* document in this collection will be selected.
|
104
|
+
#
|
105
|
+
# @return the last document in the ordered collection as described by
|
106
|
+
# +options+
|
107
|
+
#
|
108
|
+
# @raise Exception when no <tt>:order</tt> option has been defined
|
83
109
|
def last(options={})
|
84
|
-
if options[:order].blank?
|
85
|
-
|
86
|
-
end
|
87
|
-
|
88
|
-
options.merge!(:limit => 1)
|
89
|
-
options[:order] = invert_order_clause(options[:order])
|
90
|
-
find_every(options)[0]
|
110
|
+
raise ':order option must be provided when using last' if options[:order].blank?
|
111
|
+
find_one(options.merge(:order => invert_order_clause(options[:order])))
|
91
112
|
end
|
92
113
|
|
114
|
+
# @param [Hash] options any conditions understood by
|
115
|
+
# FinderOptions.to_mongo_criteria
|
116
|
+
#
|
117
|
+
# @return [Array] all documents in your collection that match the
|
118
|
+
# provided conditions
|
119
|
+
#
|
120
|
+
# @see FinderOptions
|
93
121
|
def all(options={})
|
94
122
|
find_every(options)
|
95
123
|
end
|
96
124
|
|
97
125
|
def find_by_id(id)
|
98
|
-
|
99
|
-
if doc = collection.find_one(criteria)
|
100
|
-
new(doc)
|
101
|
-
end
|
126
|
+
find_one(:_id => id)
|
102
127
|
end
|
103
128
|
|
104
|
-
def count(
|
105
|
-
collection.find(
|
129
|
+
def count(options={})
|
130
|
+
collection.find(to_criteria(options)).count
|
106
131
|
end
|
107
132
|
|
108
|
-
def exists?(
|
109
|
-
!count(
|
133
|
+
def exists?(options={})
|
134
|
+
!count(options).zero?
|
110
135
|
end
|
111
136
|
|
137
|
+
# @overload create(doc_attributes)
|
138
|
+
# Create a single new document
|
139
|
+
# @param [Hash] doc_attributes key/value pairs to create a new
|
140
|
+
# document
|
141
|
+
#
|
142
|
+
# @overload create(docs_attributes)
|
143
|
+
# Create many new documents
|
144
|
+
# @param [Array<Hash>] provide many Hashes of key/value pairs to create
|
145
|
+
# multiple documents
|
146
|
+
#
|
147
|
+
# @example Creating a single document
|
148
|
+
# MyModel.create({ :foo => "bar" })
|
149
|
+
#
|
150
|
+
# @example Creating multiple documents
|
151
|
+
# MyModel.create([{ :foo => "bar" }, { :foo => "baz" })
|
152
|
+
#
|
153
|
+
# @return [Boolean] when a document is successfully created, +true+ will
|
154
|
+
# be returned. If a document fails to create, +false+ will be returned.
|
112
155
|
def create(*docs)
|
113
156
|
initialize_each(*docs) { |doc| doc.save }
|
114
157
|
end
|
115
158
|
|
159
|
+
# @see Document.create
|
160
|
+
#
|
161
|
+
# @raise [DocumentNotValid] raised if a document fails to create
|
116
162
|
def create!(*docs)
|
117
163
|
initialize_each(*docs) { |doc| doc.save! }
|
118
164
|
end
|
119
165
|
|
120
|
-
#
|
166
|
+
# @overload update(id, attributes)
|
167
|
+
# Update a single document
|
168
|
+
# @param id the ID of the document you wish to update
|
169
|
+
# @param [Hash] attributes the key to update on the document with a new
|
170
|
+
# value
|
171
|
+
#
|
172
|
+
# @overload update(ids_and_attributes)
|
173
|
+
# Update multiple documents
|
174
|
+
# @param [Hash] ids_and_attributes each key is the ID of some document
|
175
|
+
# you wish to update. The value each key points toward are those
|
176
|
+
# applied to the target document
|
177
|
+
#
|
178
|
+
# @example Updating single document
|
121
179
|
# Person.update(1, {:foo => 'bar'})
|
122
180
|
#
|
123
|
-
#
|
181
|
+
# @example Updating multiple documents at once:
|
124
182
|
# Person.update({'1' => {:foo => 'bar'}, '2' => {:baz => 'wick'}})
|
125
183
|
def update(*args)
|
126
|
-
|
127
|
-
if updating_multiple
|
184
|
+
if args.length == 1
|
128
185
|
update_multiple(args[0])
|
129
186
|
else
|
130
187
|
id, attributes = args
|
@@ -132,24 +189,55 @@ module MongoMapper
|
|
132
189
|
end
|
133
190
|
end
|
134
191
|
|
192
|
+
# Removes ("deletes") one or many documents from the collection. Note
|
193
|
+
# that this will bypass any +destroy+ hooks defined by your class.
|
194
|
+
#
|
195
|
+
# @param [Array] ids the ID or IDs of the records you wish to delete
|
135
196
|
def delete(*ids)
|
136
|
-
|
137
|
-
collection.remove(criteria)
|
197
|
+
collection.remove(to_criteria(:_id => ids.flatten))
|
138
198
|
end
|
139
199
|
|
140
|
-
def delete_all(
|
141
|
-
|
142
|
-
collection.remove(criteria)
|
200
|
+
def delete_all(options={})
|
201
|
+
collection.remove(to_criteria(options))
|
143
202
|
end
|
144
203
|
|
204
|
+
# Iterates over each document found by the provided IDs and calls their
|
205
|
+
# +destroy+ method. This has the advantage of processing your document's
|
206
|
+
# +destroy+ call-backs.
|
207
|
+
#
|
208
|
+
# @overload destroy(id)
|
209
|
+
# Destroy a single document by ID
|
210
|
+
# @param id the ID of the document to destroy
|
211
|
+
#
|
212
|
+
# @overload destroy(ids)
|
213
|
+
# Destroy many documents by their IDs
|
214
|
+
# @param [Array] the IDs of each document you wish to destroy
|
215
|
+
#
|
216
|
+
# @example Destroying a single document
|
217
|
+
# Person.destroy("34")
|
218
|
+
#
|
219
|
+
# @example Destroying multiple documents
|
220
|
+
# Person.destroy("34", "45", ..., "54")
|
221
|
+
#
|
222
|
+
# # OR...
|
223
|
+
#
|
224
|
+
# Person.destroy(["34", "45", ..., "54"])
|
145
225
|
def destroy(*ids)
|
146
226
|
find_some(ids.flatten).each(&:destroy)
|
147
227
|
end
|
148
228
|
|
149
|
-
def destroy_all(
|
150
|
-
all(
|
229
|
+
def destroy_all(options={})
|
230
|
+
all(options).each(&:destroy)
|
151
231
|
end
|
152
232
|
|
233
|
+
# @overload connection()
|
234
|
+
# @return [Mongo::Connection] the connection used by your document class
|
235
|
+
#
|
236
|
+
# @overload connection(mongo_connection)
|
237
|
+
# @param [Mongo::Connection] mongo_connection a new connection for your
|
238
|
+
# document class to use
|
239
|
+
# @return [Mongo::Connection] a new Mongo::Connection for yoru document
|
240
|
+
# class
|
153
241
|
def connection(mongo_connection=nil)
|
154
242
|
if mongo_connection.nil?
|
155
243
|
@connection ||= MongoMapper.connection
|
@@ -159,6 +247,12 @@ module MongoMapper
|
|
159
247
|
@connection
|
160
248
|
end
|
161
249
|
|
250
|
+
# @overload database()
|
251
|
+
# @return [Mongo] the database object used by your document class
|
252
|
+
#
|
253
|
+
# @overload database(name)
|
254
|
+
# @param [String] name the name of an existing, or new, Mongo database
|
255
|
+
# @return [Mongo] a Mongo database object for the specified database
|
162
256
|
def database(name=nil)
|
163
257
|
if name.nil?
|
164
258
|
@database ||= MongoMapper.database
|
@@ -167,42 +261,49 @@ module MongoMapper
|
|
167
261
|
end
|
168
262
|
@database
|
169
263
|
end
|
170
|
-
|
264
|
+
|
171
265
|
# Changes the collection name from the default to whatever you want
|
266
|
+
#
|
267
|
+
# @param [#to_s] name the new collection name to use. Defaults to +nil+
|
172
268
|
def set_collection_name(name=nil)
|
173
269
|
@collection = nil
|
174
270
|
@collection_name = name
|
175
271
|
end
|
176
|
-
|
272
|
+
|
177
273
|
# Returns the collection name, if not set, defaults to class name tableized
|
274
|
+
#
|
275
|
+
# @return [String] the collection name, if not set, defaults to class
|
276
|
+
# name tableized
|
178
277
|
def collection_name
|
179
|
-
@collection_name ||= self.to_s.
|
278
|
+
@collection_name ||= self.to_s.tableize.gsub(/\//, '.')
|
180
279
|
end
|
181
280
|
|
182
|
-
#
|
281
|
+
# @return the Mongo Ruby driver +collection+ object
|
183
282
|
def collection
|
184
283
|
@collection ||= database.collection(collection_name)
|
185
284
|
end
|
186
285
|
|
286
|
+
# Defines a +created_at+ and +updated_at+ attribute (with a +Time+
|
287
|
+
# value) on your document. These attributes are updated by an
|
288
|
+
# injected +update_timestamps+ +before_save+ hook.
|
187
289
|
def timestamps!
|
188
290
|
key :created_at, Time
|
189
291
|
key :updated_at, Time
|
190
|
-
|
191
292
|
class_eval { before_save :update_timestamps }
|
192
293
|
end
|
193
|
-
|
294
|
+
|
194
295
|
def single_collection_inherited?
|
195
296
|
keys.has_key?('_type') && single_collection_inherited_superclass?
|
196
297
|
end
|
197
|
-
|
298
|
+
|
198
299
|
def single_collection_inherited_superclass?
|
199
300
|
superclass.respond_to?(:keys) && superclass.keys.has_key?('_type')
|
200
301
|
end
|
201
|
-
|
302
|
+
|
202
303
|
protected
|
203
304
|
def method_missing(method, *args)
|
204
305
|
finder = DynamicFinder.new(method)
|
205
|
-
|
306
|
+
|
206
307
|
if finder.found?
|
207
308
|
meta_def(finder.method) { |*args| dynamic_find(finder, args) }
|
208
309
|
send(finder.method, *args)
|
@@ -212,50 +313,41 @@ module MongoMapper
|
|
212
313
|
end
|
213
314
|
|
214
315
|
private
|
215
|
-
|
316
|
+
def create_indexes_for(key)
|
317
|
+
ensure_index key.name if key.options[:index]
|
318
|
+
end
|
319
|
+
|
216
320
|
def initialize_each(*docs)
|
217
321
|
instances = []
|
218
322
|
docs = [{}] if docs.blank?
|
219
323
|
docs.flatten.each do |attrs|
|
220
|
-
doc =
|
324
|
+
doc = initialize_doc(attrs)
|
221
325
|
yield(doc)
|
222
326
|
instances << doc
|
223
327
|
end
|
224
328
|
instances.size == 1 ? instances[0] : instances
|
225
329
|
end
|
226
330
|
|
227
|
-
def
|
228
|
-
|
331
|
+
def initialize_doc(doc)
|
332
|
+
begin
|
333
|
+
klass = doc['_type'].present? ? doc['_type'].constantize : self
|
334
|
+
klass.new(doc)
|
335
|
+
rescue NameError
|
336
|
+
new(doc)
|
337
|
+
end
|
229
338
|
end
|
230
|
-
|
339
|
+
|
231
340
|
def find_every(options)
|
232
|
-
criteria, options =
|
341
|
+
criteria, options = to_finder_options(options)
|
233
342
|
collection.find(criteria, options).to_a.map do |doc|
|
234
|
-
|
235
|
-
klass = doc['_type'].present? ? doc['_type'].constantize : self
|
236
|
-
klass.new(doc)
|
237
|
-
rescue NameError
|
238
|
-
new(doc)
|
239
|
-
end
|
343
|
+
initialize_doc(doc)
|
240
344
|
end
|
241
345
|
end
|
242
346
|
|
243
|
-
def invert_order_clause(order)
|
244
|
-
order.split(',').map do |order_segment|
|
245
|
-
if order_segment =~ /\sasc/i
|
246
|
-
order_segment.sub /\sasc/i, ' desc'
|
247
|
-
elsif order_segment =~ /\sdesc/i
|
248
|
-
order_segment.sub /\sdesc/i, ' asc'
|
249
|
-
else
|
250
|
-
"#{order_segment.strip} desc"
|
251
|
-
end
|
252
|
-
end.join(',')
|
253
|
-
end
|
254
|
-
|
255
347
|
def find_some(ids, options={})
|
256
|
-
ids
|
257
|
-
documents = find_every(options.
|
258
|
-
|
348
|
+
ids = ids.flatten.compact.uniq
|
349
|
+
documents = find_every(options.merge(:_id => ids))
|
350
|
+
|
259
351
|
if ids.size == documents.size
|
260
352
|
documents
|
261
353
|
else
|
@@ -263,14 +355,29 @@ module MongoMapper
|
|
263
355
|
end
|
264
356
|
end
|
265
357
|
|
266
|
-
def find_one(
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
raise DocumentNotFound, "Document with id of #{id} does not exist in collection named #{collection.name}"
|
358
|
+
def find_one(options={})
|
359
|
+
criteria, options = to_finder_options(options)
|
360
|
+
if doc = collection.find_one(criteria, options)
|
361
|
+
initialize_doc(doc)
|
271
362
|
end
|
272
363
|
end
|
273
364
|
|
365
|
+
def find_one!(options={})
|
366
|
+
find_one(options) || raise(DocumentNotFound, "Document match #{options.inspect} does not exist in #{collection.name} collection")
|
367
|
+
end
|
368
|
+
|
369
|
+
def invert_order_clause(order)
|
370
|
+
order.split(',').map do |order_segment|
|
371
|
+
if order_segment =~ /\sasc/i
|
372
|
+
order_segment.sub /\sasc/i, ' desc'
|
373
|
+
elsif order_segment =~ /\sdesc/i
|
374
|
+
order_segment.sub /\sdesc/i, ' asc'
|
375
|
+
else
|
376
|
+
"#{order_segment.strip} desc"
|
377
|
+
end
|
378
|
+
end.join(',')
|
379
|
+
end
|
380
|
+
|
274
381
|
def update_single(id, attrs)
|
275
382
|
if id.blank? || attrs.blank? || !attrs.is_a?(Hash)
|
276
383
|
raise ArgumentError, "Updating a single document requires an id and a hash of attributes"
|
@@ -290,9 +397,17 @@ module MongoMapper
|
|
290
397
|
docs.each_pair { |id, attrs| instances << update(id, attrs) }
|
291
398
|
instances
|
292
399
|
end
|
400
|
+
|
401
|
+
def to_criteria(options={})
|
402
|
+
FinderOptions.new(self, options).criteria
|
403
|
+
end
|
404
|
+
|
405
|
+
def to_finder_options(options={})
|
406
|
+
FinderOptions.new(self, options).to_a
|
407
|
+
end
|
293
408
|
end
|
294
409
|
|
295
|
-
module InstanceMethods
|
410
|
+
module InstanceMethods
|
296
411
|
def collection
|
297
412
|
self.class.collection
|
298
413
|
end
|
@@ -311,12 +426,14 @@ module MongoMapper
|
|
311
426
|
|
312
427
|
def destroy
|
313
428
|
return false if frozen?
|
314
|
-
|
315
|
-
criteria = FinderOptions.to_mongo_criteria(self.class, :_id => id)
|
316
|
-
collection.remove(criteria) unless new?
|
429
|
+
self.class.delete(id) unless new?
|
317
430
|
freeze
|
318
431
|
end
|
319
432
|
|
433
|
+
def reload
|
434
|
+
self.class.find(id)
|
435
|
+
end
|
436
|
+
|
320
437
|
private
|
321
438
|
def create_or_update
|
322
439
|
result = new? ? create : update
|
@@ -327,7 +444,7 @@ module MongoMapper
|
|
327
444
|
assign_id
|
328
445
|
save_to_collection
|
329
446
|
end
|
330
|
-
|
447
|
+
|
331
448
|
def assign_id
|
332
449
|
if read_attribute(:_id).blank?
|
333
450
|
write_attribute(:_id, Mongo::ObjectID.new.to_s)
|
@@ -348,7 +465,7 @@ module MongoMapper
|
|
348
465
|
write_attribute('created_at', now) if new?
|
349
466
|
write_attribute('updated_at', now)
|
350
467
|
end
|
351
|
-
|
468
|
+
|
352
469
|
def clear_custom_id_flag
|
353
470
|
@using_custom_id = nil
|
354
471
|
end
|