jnunemaker-mongomapper 0.2.0 → 0.3.0
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/.gitignore +1 -0
- data/History +17 -0
- data/README.rdoc +6 -3
- data/Rakefile +3 -2
- data/VERSION +1 -1
- data/bin/mmconsole +56 -0
- data/lib/mongomapper.rb +48 -17
- data/lib/mongomapper/associations.rb +31 -39
- data/lib/mongomapper/associations/base.rb +40 -22
- data/lib/mongomapper/associations/belongs_to_polymorphic_proxy.rb +33 -0
- data/lib/mongomapper/associations/belongs_to_proxy.rb +10 -14
- data/lib/mongomapper/associations/many_embedded_polymorphic_proxy.rb +34 -0
- data/lib/mongomapper/associations/{has_many_embedded_proxy.rb → many_embedded_proxy.rb} +5 -5
- data/lib/mongomapper/associations/many_proxy.rb +55 -0
- data/lib/mongomapper/associations/proxy.rb +21 -14
- data/lib/mongomapper/callbacks.rb +1 -1
- data/lib/mongomapper/document.rb +82 -59
- data/lib/mongomapper/embedded_document.rb +121 -130
- data/lib/mongomapper/finder_options.rb +21 -6
- data/lib/mongomapper/key.rb +5 -7
- data/lib/mongomapper/observing.rb +1 -41
- data/lib/mongomapper/pagination.rb +52 -0
- data/lib/mongomapper/rails_compatibility/document.rb +15 -0
- data/lib/mongomapper/rails_compatibility/embedded_document.rb +25 -0
- data/lib/mongomapper/serialization.rb +1 -1
- data/mongomapper.gemspec +62 -36
- data/test/NOTE_ON_TESTING +1 -0
- data/test/functional/test_associations.rb +485 -0
- data/test/{test_callbacks.rb → functional/test_callbacks.rb} +2 -1
- data/test/functional/test_document.rb +636 -0
- data/test/functional/test_pagination.rb +82 -0
- data/test/functional/test_rails_compatibility.rb +31 -0
- data/test/functional/test_validations.rb +172 -0
- data/test/models.rb +92 -0
- data/test/test_helper.rb +5 -0
- data/test/{serializers → unit/serializers}/test_json_serializer.rb +0 -0
- data/test/unit/test_association_base.rb +131 -0
- data/test/unit/test_document.rb +115 -0
- data/test/{test_embedded_document.rb → unit/test_embedded_document.rb} +158 -66
- data/test/{test_finder_options.rb → unit/test_finder_options.rb} +66 -0
- data/test/{test_key.rb → unit/test_key.rb} +13 -1
- data/test/unit/test_mongo_id.rb +35 -0
- data/test/{test_mongomapper.rb → unit/test_mongomapper.rb} +0 -0
- data/test/{test_observing.rb → unit/test_observing.rb} +0 -0
- data/test/unit/test_pagination.rb +113 -0
- data/test/unit/test_rails_compatibility.rb +34 -0
- data/test/{test_serializations.rb → unit/test_serializations.rb} +0 -2
- data/test/{test_validations.rb → unit/test_validations.rb} +0 -134
- metadata +68 -36
- data/lib/mongomapper/associations/has_many_proxy.rb +0 -28
- data/lib/mongomapper/associations/polymorphic_belongs_to_proxy.rb +0 -31
- data/lib/mongomapper/rails_compatibility.rb +0 -23
- data/test/test_associations.rb +0 -149
- data/test/test_document.rb +0 -944
- data/test/test_rails_compatibility.rb +0 -29
@@ -0,0 +1,34 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Associations
|
3
|
+
class ManyEmbeddedPolymorphicProxy < ArrayProxy
|
4
|
+
def replace(v)
|
5
|
+
@_values = v.map do |doc_or_hash|
|
6
|
+
if doc_or_hash.kind_of?(EmbeddedDocument)
|
7
|
+
doc = doc_or_hash
|
8
|
+
{@association.type_key_name => doc.class.name}.merge(doc.attributes)
|
9
|
+
else
|
10
|
+
doc_or_hash
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
@target = nil
|
15
|
+
reload_target
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
def find_target
|
20
|
+
(@_values || []).map do |hash|
|
21
|
+
polymorphic_class(hash).new(hash)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def polymorphic_class(doc)
|
26
|
+
if class_name = doc[@association.type_key_name]
|
27
|
+
class_name.constantize
|
28
|
+
else
|
29
|
+
@association.klass
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module MongoMapper
|
2
2
|
module Associations
|
3
|
-
class
|
3
|
+
class ManyEmbeddedProxy < ArrayProxy
|
4
4
|
def replace(v)
|
5
5
|
@_values = v.map { |e| e.kind_of?(EmbeddedDocument) ? e.attributes : e }
|
6
6
|
@target = nil
|
@@ -9,11 +9,11 @@ module MongoMapper
|
|
9
9
|
end
|
10
10
|
|
11
11
|
protected
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
def find_target
|
13
|
+
(@_values || []).map do |e|
|
14
|
+
@association.klass.new(e)
|
15
|
+
end
|
15
16
|
end
|
16
|
-
end
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Associations
|
3
|
+
class ManyProxy < ArrayProxy
|
4
|
+
delegate :klass, :to => :@association
|
5
|
+
|
6
|
+
def find(*args)
|
7
|
+
options = args.extract_options!
|
8
|
+
klass.find(*args << scoped_options(options))
|
9
|
+
end
|
10
|
+
|
11
|
+
def paginate(options)
|
12
|
+
klass.paginate(scoped_options(options))
|
13
|
+
end
|
14
|
+
|
15
|
+
def all(options={})
|
16
|
+
find(:all, scoped_options(options))
|
17
|
+
end
|
18
|
+
|
19
|
+
def first(options={})
|
20
|
+
find(:first, scoped_options(options))
|
21
|
+
end
|
22
|
+
|
23
|
+
def last(options={})
|
24
|
+
find(:last, scoped_options(options))
|
25
|
+
end
|
26
|
+
|
27
|
+
def replace(docs)
|
28
|
+
if load_target
|
29
|
+
@target.map(&:destroy)
|
30
|
+
end
|
31
|
+
|
32
|
+
docs.each do |doc|
|
33
|
+
@owner.save if @owner.new?
|
34
|
+
doc.send(:write_attribute, self.foreign_key, @owner.id)
|
35
|
+
doc.save
|
36
|
+
end
|
37
|
+
|
38
|
+
reload_target
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
def scoped_options(options)
|
43
|
+
options.dup.deep_merge({:conditions => {self.foreign_key => @owner.id}})
|
44
|
+
end
|
45
|
+
|
46
|
+
def find_target
|
47
|
+
find(:all)
|
48
|
+
end
|
49
|
+
|
50
|
+
def foreign_key
|
51
|
+
@association.options[:foreign_key] || @owner.class.name.underscore.gsub("/", "_") + "_id"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -10,7 +10,6 @@ module MongoMapper
|
|
10
10
|
def initialize(owner, association)
|
11
11
|
@owner= owner
|
12
12
|
@association = association
|
13
|
-
|
14
13
|
reset
|
15
14
|
end
|
16
15
|
|
@@ -38,23 +37,31 @@ module MongoMapper
|
|
38
37
|
end
|
39
38
|
|
40
39
|
protected
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
40
|
+
def method_missing(method, *args)
|
41
|
+
if load_target
|
42
|
+
if block_given?
|
43
|
+
@target.send(method, *args) { |*block_args| yield(*block_args) }
|
44
|
+
else
|
45
|
+
@target.send(method, *args)
|
46
|
+
end
|
47
47
|
end
|
48
48
|
end
|
49
|
-
end
|
50
49
|
|
51
|
-
|
52
|
-
|
53
|
-
|
50
|
+
def load_target
|
51
|
+
@target ||= find_target
|
52
|
+
end
|
54
53
|
|
55
|
-
|
56
|
-
|
57
|
-
|
54
|
+
def find_target
|
55
|
+
raise NotImplementedError
|
56
|
+
end
|
57
|
+
|
58
|
+
# Array#flatten has problems with recursive arrays. Going one level
|
59
|
+
# deeper solves the majority of the problems.
|
60
|
+
def flatten_deeper(array)
|
61
|
+
array.collect do |element|
|
62
|
+
(element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element
|
63
|
+
end.flatten
|
64
|
+
end
|
58
65
|
end
|
59
66
|
end
|
60
67
|
end
|
@@ -90,7 +90,7 @@ module MongoMapper
|
|
90
90
|
def callback(method)
|
91
91
|
result = run_callbacks(method) { |result, object| false == result }
|
92
92
|
|
93
|
-
if result != false &&
|
93
|
+
if result != false && respond_to?(method)
|
94
94
|
result = send(method)
|
95
95
|
end
|
96
96
|
|
data/lib/mongomapper/document.rb
CHANGED
@@ -9,21 +9,21 @@ module MongoMapper
|
|
9
9
|
include Observing
|
10
10
|
include Callbacks
|
11
11
|
include SaveWithValidation
|
12
|
-
include RailsCompatibility
|
12
|
+
include RailsCompatibility::Document
|
13
13
|
extend ClassMethods
|
14
|
-
|
15
|
-
key :_id,
|
14
|
+
|
15
|
+
key :_id, MongoID
|
16
16
|
key :created_at, Time
|
17
17
|
key :updated_at, Time
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
descendants << model
|
21
21
|
end
|
22
22
|
|
23
23
|
def self.descendants
|
24
24
|
@descendants ||= Set.new
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
module ClassMethods
|
28
28
|
def find(*args)
|
29
29
|
options = args.extract_options!
|
@@ -32,10 +32,24 @@ module MongoMapper
|
|
32
32
|
when :first then find_first(options)
|
33
33
|
when :last then find_last(options)
|
34
34
|
when :all then find_every(options)
|
35
|
-
else find_from_ids(args)
|
35
|
+
else find_from_ids(args, options)
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
+
def paginate(options)
|
40
|
+
per_page = options.delete(:per_page)
|
41
|
+
page = options.delete(:page)
|
42
|
+
total_entries = count(options[:conditions] || {})
|
43
|
+
|
44
|
+
collection = Pagination::PaginationProxy.new(total_entries, page, per_page)
|
45
|
+
|
46
|
+
options[:limit] = collection.limit
|
47
|
+
options[:offset] = collection.offset
|
48
|
+
|
49
|
+
collection.subject = find_every(options)
|
50
|
+
collection
|
51
|
+
end
|
52
|
+
|
39
53
|
def first(options={})
|
40
54
|
find_first(options)
|
41
55
|
end
|
@@ -49,25 +63,26 @@ module MongoMapper
|
|
49
63
|
end
|
50
64
|
|
51
65
|
def find_by_id(id)
|
52
|
-
|
66
|
+
criteria = FinderOptions.to_mongo_criteria(:_id => id)
|
67
|
+
if doc = collection.find_first(criteria)
|
53
68
|
new(doc)
|
54
69
|
end
|
55
70
|
end
|
56
|
-
|
57
|
-
# TODO: remove the rescuing when ruby driver works correctly
|
71
|
+
|
58
72
|
def count(conditions={})
|
59
73
|
collection.count(FinderOptions.to_mongo_criteria(conditions))
|
60
74
|
end
|
61
|
-
|
75
|
+
|
62
76
|
def create(*docs)
|
63
77
|
instances = []
|
78
|
+
docs = [{}] if docs.blank?
|
64
79
|
docs.flatten.each do |attrs|
|
65
80
|
doc = new(attrs); doc.save
|
66
81
|
instances << doc
|
67
82
|
end
|
68
83
|
instances.size == 1 ? instances[0] : instances
|
69
84
|
end
|
70
|
-
|
85
|
+
|
71
86
|
# For updating single document
|
72
87
|
# Person.update(1, {:foo => 'bar'})
|
73
88
|
#
|
@@ -82,23 +97,25 @@ module MongoMapper
|
|
82
97
|
update_single(id, attributes)
|
83
98
|
end
|
84
99
|
end
|
85
|
-
|
100
|
+
|
86
101
|
def delete(*ids)
|
87
|
-
|
102
|
+
criteria = FinderOptions.to_mongo_criteria(:_id => ids.flatten)
|
103
|
+
collection.remove(criteria)
|
88
104
|
end
|
89
|
-
|
105
|
+
|
90
106
|
def delete_all(conditions={})
|
91
|
-
|
107
|
+
criteria = FinderOptions.to_mongo_criteria(conditions)
|
108
|
+
collection.remove(criteria)
|
92
109
|
end
|
93
|
-
|
110
|
+
|
94
111
|
def destroy(*ids)
|
95
112
|
find_some(ids.flatten).each(&:destroy)
|
96
113
|
end
|
97
|
-
|
114
|
+
|
98
115
|
def destroy_all(conditions={})
|
99
116
|
find(:all, :conditions => conditions).each(&:destroy)
|
100
117
|
end
|
101
|
-
|
118
|
+
|
102
119
|
def connection(mongo_connection=nil)
|
103
120
|
if mongo_connection.nil?
|
104
121
|
@connection ||= MongoMapper.connection
|
@@ -107,7 +124,7 @@ module MongoMapper
|
|
107
124
|
end
|
108
125
|
@connection
|
109
126
|
end
|
110
|
-
|
127
|
+
|
111
128
|
def database(name=nil)
|
112
129
|
if name.nil?
|
113
130
|
@database ||= MongoMapper.database
|
@@ -116,7 +133,7 @@ module MongoMapper
|
|
116
133
|
end
|
117
134
|
@database
|
118
135
|
end
|
119
|
-
|
136
|
+
|
120
137
|
def collection(name=nil)
|
121
138
|
if name.nil?
|
122
139
|
@collection ||= database.collection(self.to_s.demodulize.tableize)
|
@@ -125,7 +142,7 @@ module MongoMapper
|
|
125
142
|
end
|
126
143
|
@collection
|
127
144
|
end
|
128
|
-
|
145
|
+
|
129
146
|
def validates_uniqueness_of(*args)
|
130
147
|
add_validations(args, MongoMapper::Validations::ValidatesUniquenessOf)
|
131
148
|
end
|
@@ -133,74 +150,83 @@ module MongoMapper
|
|
133
150
|
def validates_exclusion_of(*args)
|
134
151
|
add_validations(args, MongoMapper::Validations::ValidatesExclusionOf)
|
135
152
|
end
|
136
|
-
|
153
|
+
|
137
154
|
def validates_inclusion_of(*args)
|
138
155
|
add_validations(args, MongoMapper::Validations::ValidatesInclusionOf)
|
139
156
|
end
|
140
|
-
|
157
|
+
|
141
158
|
private
|
142
159
|
def find_every(options)
|
143
|
-
criteria, options = FinderOptions.new(options).to_a
|
160
|
+
criteria, options = FinderOptions.new(options).to_a
|
144
161
|
collection.find(criteria, options).to_a.map { |doc| new(doc) }
|
145
162
|
end
|
146
|
-
|
163
|
+
|
147
164
|
def find_first(options)
|
148
|
-
find_every(options.merge(:limit => 1, :order => '
|
165
|
+
find_every(options.merge(:limit => 1, :order => '$natural asc'))[0]
|
149
166
|
end
|
150
|
-
|
167
|
+
|
151
168
|
def find_last(options)
|
152
|
-
find_every(options.merge(:limit => 1, :order => '
|
169
|
+
find_every(options.merge(:limit => 1, :order => '$natural desc'))[0]
|
153
170
|
end
|
154
|
-
|
155
|
-
def find_some(ids)
|
156
|
-
documents = find_every(:conditions => {'_id' => ids})
|
171
|
+
|
172
|
+
def find_some(ids, options={})
|
173
|
+
documents = find_every(options.deep_merge(:conditions => {'_id' => ids}))
|
157
174
|
if ids.size == documents.size
|
158
175
|
documents
|
159
176
|
else
|
160
177
|
raise DocumentNotFound, "Couldn't find all of the ids (#{ids.to_sentence}). Found #{documents.size}, but was expecting #{ids.size}"
|
161
178
|
end
|
162
179
|
end
|
163
|
-
|
164
|
-
def
|
180
|
+
|
181
|
+
def find_one(id, options={})
|
182
|
+
if doc = find_every(options.deep_merge(:conditions => {:_id => id})).first
|
183
|
+
doc
|
184
|
+
else
|
185
|
+
raise DocumentNotFound, "Document with id of #{id} does not exist in collection named #{collection.name}"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def find_from_ids(ids, options={})
|
165
190
|
ids = ids.flatten.compact.uniq
|
166
|
-
|
191
|
+
|
167
192
|
case ids.size
|
168
193
|
when 0
|
169
194
|
raise(DocumentNotFound, "Couldn't find without an ID")
|
170
195
|
when 1
|
171
|
-
|
196
|
+
find_one(ids[0], options)
|
172
197
|
else
|
173
|
-
find_some(ids)
|
198
|
+
find_some(ids, options)
|
174
199
|
end
|
175
200
|
end
|
176
|
-
|
201
|
+
|
177
202
|
def update_single(id, attrs)
|
178
203
|
if id.blank? || attrs.blank? || !attrs.is_a?(Hash)
|
179
204
|
raise ArgumentError, "Updating a single document requires an id and a hash of attributes"
|
180
205
|
end
|
181
|
-
|
206
|
+
|
182
207
|
find(id).update_attributes(attrs)
|
183
208
|
end
|
184
|
-
|
209
|
+
|
185
210
|
def update_multiple(docs)
|
186
211
|
unless docs.is_a?(Hash)
|
187
212
|
raise ArgumentError, "Updating multiple documents takes 1 argument and it must be hash"
|
188
213
|
end
|
214
|
+
|
189
215
|
instances = []
|
190
216
|
docs.each_pair { |id, attrs| instances << update(id, attrs) }
|
191
217
|
instances
|
192
218
|
end
|
193
219
|
end
|
194
|
-
|
220
|
+
|
195
221
|
module InstanceMethods
|
196
222
|
def collection
|
197
223
|
self.class.collection
|
198
224
|
end
|
199
|
-
|
225
|
+
|
200
226
|
def new?
|
201
|
-
read_attribute('_id').blank?
|
227
|
+
read_attribute('_id').blank?
|
202
228
|
end
|
203
|
-
|
229
|
+
|
204
230
|
def save
|
205
231
|
create_or_update
|
206
232
|
end
|
@@ -214,20 +240,21 @@ module MongoMapper
|
|
214
240
|
save
|
215
241
|
self
|
216
242
|
end
|
217
|
-
|
243
|
+
|
218
244
|
def destroy
|
219
|
-
|
245
|
+
criteria = FinderOptions.to_mongo_criteria(:_id => id)
|
246
|
+
collection.remove(criteria) unless new?
|
220
247
|
freeze
|
221
248
|
end
|
222
|
-
|
249
|
+
|
223
250
|
def ==(other)
|
224
251
|
other.is_a?(self.class) && id == other.id
|
225
252
|
end
|
226
|
-
|
253
|
+
|
227
254
|
def id
|
228
|
-
read_attribute('_id')
|
255
|
+
read_attribute('_id').to_s
|
229
256
|
end
|
230
|
-
|
257
|
+
|
231
258
|
private
|
232
259
|
def create_or_update
|
233
260
|
result = new? ? create : update
|
@@ -235,28 +262,24 @@ module MongoMapper
|
|
235
262
|
end
|
236
263
|
|
237
264
|
def create
|
238
|
-
write_attribute('_id', generate_id) if read_attribute('_id').blank?
|
239
265
|
update_timestamps
|
240
|
-
save_to_collection
|
266
|
+
write_attribute :_id, save_to_collection
|
241
267
|
end
|
242
|
-
|
268
|
+
|
243
269
|
def update
|
244
270
|
update_timestamps
|
245
271
|
save_to_collection
|
246
272
|
end
|
247
|
-
|
273
|
+
|
274
|
+
# collection.save returns mongoid
|
248
275
|
def save_to_collection
|
249
|
-
collection.save(attributes
|
276
|
+
collection.save(attributes)
|
250
277
|
end
|
251
|
-
|
278
|
+
|
252
279
|
def update_timestamps
|
253
280
|
write_attribute('created_at', Time.now.utc) if new?
|
254
281
|
write_attribute('updated_at', Time.now.utc)
|
255
282
|
end
|
256
|
-
|
257
|
-
def generate_id
|
258
|
-
XGen::Mongo::Driver::ObjectID.new
|
259
|
-
end
|
260
283
|
end
|
261
284
|
end # Document
|
262
285
|
end # MongoMapper
|