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.
Files changed (55) hide show
  1. data/.gitignore +1 -0
  2. data/History +17 -0
  3. data/README.rdoc +6 -3
  4. data/Rakefile +3 -2
  5. data/VERSION +1 -1
  6. data/bin/mmconsole +56 -0
  7. data/lib/mongomapper.rb +48 -17
  8. data/lib/mongomapper/associations.rb +31 -39
  9. data/lib/mongomapper/associations/base.rb +40 -22
  10. data/lib/mongomapper/associations/belongs_to_polymorphic_proxy.rb +33 -0
  11. data/lib/mongomapper/associations/belongs_to_proxy.rb +10 -14
  12. data/lib/mongomapper/associations/many_embedded_polymorphic_proxy.rb +34 -0
  13. data/lib/mongomapper/associations/{has_many_embedded_proxy.rb → many_embedded_proxy.rb} +5 -5
  14. data/lib/mongomapper/associations/many_proxy.rb +55 -0
  15. data/lib/mongomapper/associations/proxy.rb +21 -14
  16. data/lib/mongomapper/callbacks.rb +1 -1
  17. data/lib/mongomapper/document.rb +82 -59
  18. data/lib/mongomapper/embedded_document.rb +121 -130
  19. data/lib/mongomapper/finder_options.rb +21 -6
  20. data/lib/mongomapper/key.rb +5 -7
  21. data/lib/mongomapper/observing.rb +1 -41
  22. data/lib/mongomapper/pagination.rb +52 -0
  23. data/lib/mongomapper/rails_compatibility/document.rb +15 -0
  24. data/lib/mongomapper/rails_compatibility/embedded_document.rb +25 -0
  25. data/lib/mongomapper/serialization.rb +1 -1
  26. data/mongomapper.gemspec +62 -36
  27. data/test/NOTE_ON_TESTING +1 -0
  28. data/test/functional/test_associations.rb +485 -0
  29. data/test/{test_callbacks.rb → functional/test_callbacks.rb} +2 -1
  30. data/test/functional/test_document.rb +636 -0
  31. data/test/functional/test_pagination.rb +82 -0
  32. data/test/functional/test_rails_compatibility.rb +31 -0
  33. data/test/functional/test_validations.rb +172 -0
  34. data/test/models.rb +92 -0
  35. data/test/test_helper.rb +5 -0
  36. data/test/{serializers → unit/serializers}/test_json_serializer.rb +0 -0
  37. data/test/unit/test_association_base.rb +131 -0
  38. data/test/unit/test_document.rb +115 -0
  39. data/test/{test_embedded_document.rb → unit/test_embedded_document.rb} +158 -66
  40. data/test/{test_finder_options.rb → unit/test_finder_options.rb} +66 -0
  41. data/test/{test_key.rb → unit/test_key.rb} +13 -1
  42. data/test/unit/test_mongo_id.rb +35 -0
  43. data/test/{test_mongomapper.rb → unit/test_mongomapper.rb} +0 -0
  44. data/test/{test_observing.rb → unit/test_observing.rb} +0 -0
  45. data/test/unit/test_pagination.rb +113 -0
  46. data/test/unit/test_rails_compatibility.rb +34 -0
  47. data/test/{test_serializations.rb → unit/test_serializations.rb} +0 -2
  48. data/test/{test_validations.rb → unit/test_validations.rb} +0 -134
  49. metadata +68 -36
  50. data/lib/mongomapper/associations/has_many_proxy.rb +0 -28
  51. data/lib/mongomapper/associations/polymorphic_belongs_to_proxy.rb +0 -31
  52. data/lib/mongomapper/rails_compatibility.rb +0 -23
  53. data/test/test_associations.rb +0 -149
  54. data/test/test_document.rb +0 -944
  55. 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 HasManyEmbeddedProxy < ArrayProxy
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
- def find_target
13
- (@_values || []).map do |e|
14
- @association.klass.new(e)
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
- def method_missing(method, *args)
42
- if load_target
43
- if block_given?
44
- @target.send(method, *args) { |*block_args| yield(*block_args) }
45
- else
46
- @target.send(method, *args)
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
- def load_target
52
- @target ||= find_target
53
- end
50
+ def load_target
51
+ @target ||= find_target
52
+ end
54
53
 
55
- def find_target
56
- raise NotImplementedError
57
- end
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 && respond_to_without_attributes?(method)
93
+ if result != false && respond_to?(method)
94
94
  result = send(method)
95
95
  end
96
96
 
@@ -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, String
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
- if doc = collection.find_first({:_id => id})
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
- collection.remove(:_id => {'$in' => ids.flatten})
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
- collection.remove(FinderOptions.to_mongo_criteria(conditions))
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 => 'created_at')).first
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 => 'created_at desc')).first
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 find_from_ids(*ids)
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
- find_by_id(ids[0]) || raise(DocumentNotFound, "Document with id of #{ids[0]} does not exist in collection named #{collection.name}")
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? || self.class.find_by_id(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
- collection.remove(:_id => id) unless new?
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.merge!(embedded_association_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