jnunemaker-mongomapper 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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