jnunemaker-mongomapper 0.3.2 → 0.3.3

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/History CHANGED
@@ -1,3 +1,13 @@
1
+ 0.3.3 8/16/2009
2
+ * BACKWORDS COMPATIBILITY BREAK: _id is now once again a string rather than an object id and will stay that way.
3
+ * Custom id's can now be used because of the change to string id's
4
+ * Added dynamic finders to document. (dcu)
5
+ * Added destroy_all, delete_all and nullify for many document association
6
+ * Added :dependent option for many documents assocation (dcu)
7
+ * update_attributes now returns true or false instead of the document. (Durran Jordan and me)
8
+ * Keys no longer require a type
9
+ * Keys can now be added on the fly using []=
10
+
1
11
  0.3.2 8/6/2009
2
12
  * Added many polymorphic documents association
3
13
  * Implemented build and create for many and many polymorphic documents
data/Rakefile CHANGED
@@ -12,14 +12,16 @@ begin
12
12
  gem.rubyforge_project = "mongomapper"
13
13
 
14
14
  gem.add_dependency('activesupport')
15
- gem.add_dependency('mongodb-mongo', '0.10.1')
15
+ gem.add_dependency('mongodb-mongo', '0.11.1')
16
16
  gem.add_dependency('jnunemaker-validatable', '1.7.2')
17
17
 
18
18
  gem.add_development_dependency('mocha', '0.9.4')
19
19
  gem.add_development_dependency('jnunemaker-matchy', '0.4.0')
20
20
  end
21
-
22
- Jeweler::RubyforgeTasks.new
21
+
22
+ Jeweler::RubyforgeTasks.new do |rubyforge|
23
+ rubyforge.doc_task = "rdoc"
24
+ end
23
25
  rescue LoadError
24
26
  puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
25
27
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.2
1
+ 0.3.3
@@ -14,7 +14,9 @@ module MongoMapper
14
14
 
15
15
  protected
16
16
  def find_target
17
- proxy_class.find(proxy_id) if proxy_id && proxy_class
17
+ if proxy_id && proxy_class
18
+ proxy_class.find_by_id(proxy_id)
19
+ end
18
20
  end
19
21
 
20
22
  def proxy_id
@@ -6,7 +6,7 @@ module MongoMapper
6
6
  doc.save if doc.new?
7
7
  id = doc.id
8
8
  end
9
-
9
+
10
10
  @owner.send("#{@association.belongs_to_key_name}=", id)
11
11
  reset
12
12
  end
@@ -14,7 +14,7 @@ module MongoMapper
14
14
  protected
15
15
  def find_target
16
16
  if association_id = @owner.send(@association.belongs_to_key_name)
17
- @association.klass.find(association_id)
17
+ @association.klass.find_by_id(association_id)
18
18
  end
19
19
  end
20
20
  end
@@ -2,38 +2,38 @@ module MongoMapper
2
2
  module Associations
3
3
  class ManyDocumentsProxy < Proxy
4
4
  delegate :klass, :to => :@association
5
-
5
+
6
6
  def find(*args)
7
7
  options = args.extract_options!
8
8
  klass.find(*args << scoped_options(options))
9
9
  end
10
-
10
+
11
11
  def paginate(options)
12
12
  klass.paginate(scoped_options(options))
13
13
  end
14
-
14
+
15
15
  def all(options={})
16
16
  find(:all, scoped_options(options))
17
17
  end
18
-
18
+
19
19
  def first(options={})
20
20
  find(:first, scoped_options(options))
21
21
  end
22
-
22
+
23
23
  def last(options={})
24
24
  find(:last, scoped_options(options))
25
25
  end
26
-
26
+
27
27
  def count(conditions={})
28
28
  klass.count(conditions.deep_merge(scoped_conditions))
29
29
  end
30
-
30
+
31
31
  def replace(docs)
32
32
  @target.map(&:destroy) if load_target
33
33
  docs.each { |doc| apply_scope(doc).save }
34
34
  reset
35
35
  end
36
-
36
+
37
37
  def <<(*docs)
38
38
  ensure_owner_saved
39
39
  flatten_deeper(docs).each { |doc| apply_scope(doc).save }
@@ -41,36 +41,54 @@ module MongoMapper
41
41
  end
42
42
  alias_method :push, :<<
43
43
  alias_method :concat, :<<
44
-
44
+
45
45
  def build(attrs={})
46
46
  doc = klass.new(attrs)
47
47
  apply_scope(doc)
48
48
  doc
49
49
  end
50
-
50
+
51
51
  def create(attrs={})
52
52
  doc = klass.new(attrs)
53
53
  apply_scope(doc).save
54
54
  doc
55
55
  end
56
+
57
+ def destroy_all(conditions={})
58
+ all(:conditions => conditions).map(&:destroy)
59
+ reset
60
+ end
61
+
62
+ def delete_all(conditions={})
63
+ klass.delete_all(conditions.deep_merge(scoped_conditions))
64
+ reset
65
+ end
56
66
 
67
+ def nullify
68
+ criteria = FinderOptions.to_mongo_criteria(scoped_conditions)
69
+ all(criteria).each do |doc|
70
+ doc.update_attributes self.foreign_key => nil
71
+ end
72
+ reset
73
+ end
74
+
57
75
  protected
58
76
  def scoped_conditions
59
77
  {self.foreign_key => @owner.id}
60
78
  end
61
-
79
+
62
80
  def scoped_options(options)
63
81
  options.deep_merge({:conditions => scoped_conditions})
64
82
  end
65
-
83
+
66
84
  def find_target
67
85
  find(:all)
68
86
  end
69
-
87
+
70
88
  def ensure_owner_saved
71
89
  @owner.save if @owner.new?
72
90
  end
73
-
91
+
74
92
  def apply_scope(doc)
75
93
  ensure_owner_saved
76
94
  doc.send("#{self.foreign_key}=", @owner.id)
@@ -1,12 +1,8 @@
1
1
  module MongoMapper
2
2
  module Associations
3
- class Proxy
3
+ class Proxy < BasicObject
4
4
  attr_reader :owner, :association
5
-
6
- instance_methods.each do |m|
7
- undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/
8
- end
9
-
5
+
10
6
  def initialize(owner, association)
11
7
  @owner = owner
12
8
  @association = association
@@ -2,12 +2,12 @@ module MongoMapper
2
2
  module Associations
3
3
  module ClassMethods
4
4
  def belongs_to(association_id, options = {})
5
- create_association(:belongs_to, association_id, options)
5
+ create_association(:belongs_to, association_id, options)
6
6
  self
7
7
  end
8
8
 
9
9
  def many(association_id, options = {})
10
- create_association(:many, association_id, options)
10
+ create_association(:many, association_id, options)
11
11
  self
12
12
  end
13
13
 
@@ -21,39 +21,74 @@ module MongoMapper
21
21
  associations[association.name] = association
22
22
  define_association_methods(association)
23
23
  define_association_keys(association)
24
+ define_dependent_callback(association)
24
25
  association
25
26
  end
26
-
27
+
27
28
  def define_association_methods(association)
28
29
  define_method(association.name) do
29
30
  get_proxy(association)
30
31
  end
31
-
32
+
32
33
  define_method("#{association.name}=") do |value|
33
34
  get_proxy(association).replace(value)
34
35
  value
35
36
  end
36
37
  end
37
-
38
+
38
39
  def define_association_keys(association)
39
40
  if association.belongs_to?
40
41
  key(association.belongs_to_key_name, String)
41
- key(association.type_key_name, String) if association.polymorphic?
42
+ key(association.type_key_name, String) if association.polymorphic?
42
43
  end
43
-
44
+
44
45
  if association.many? && association.polymorphic?
45
46
  association.klass.send(:key, association.type_key_name, String)
46
47
  end
47
48
  end
49
+
50
+ def define_dependent_callback(association)
51
+ if association.options[:dependent]
52
+ if association.many?
53
+ define_dependent_callback_for_many(association)
54
+ elsif association.belongs_to?
55
+ define_dependent_callback_for_belongs_to(association)
56
+ end
57
+ end
58
+ end
59
+
60
+ def define_dependent_callback_for_many(association)
61
+ return if association.embeddable?
62
+
63
+ after_destroy do |doc|
64
+ case association.options[:dependent]
65
+ when :destroy
66
+ doc.get_proxy(association).destroy_all
67
+ when :delete_all
68
+ doc.get_proxy(association).delete_all
69
+ when :nullify
70
+ doc.get_proxy(association).nullify
71
+ end
72
+ end
73
+ end
74
+
75
+ def define_dependent_callback_for_belongs_to(association)
76
+ after_destroy do |doc|
77
+ case association.options[:dependent]
78
+ when :destroy
79
+ doc.get_proxy(association).destroy
80
+ end
81
+ end
82
+ end
48
83
  end
49
84
 
50
85
  module InstanceMethods
51
86
  def get_proxy(association)
52
87
  unless proxy = self.instance_variable_get(association.ivar)
53
88
  proxy = association.proxy_class.new(self, association)
54
- self.instance_variable_set(association.ivar, proxy)
89
+ self.instance_variable_set(association.ivar, proxy) if !frozen?
55
90
  end
56
-
91
+
57
92
  proxy
58
93
  end
59
94
  end
@@ -11,18 +11,18 @@ module MongoMapper
11
11
  include SaveWithValidation
12
12
  include RailsCompatibility::Document
13
13
  extend ClassMethods
14
-
14
+
15
15
  key :created_at, Time
16
16
  key :updated_at, Time
17
17
  end
18
-
18
+
19
19
  descendants << model
20
20
  end
21
21
 
22
22
  def self.descendants
23
23
  @descendants ||= Set.new
24
24
  end
25
-
25
+
26
26
  module ClassMethods
27
27
  def find(*args)
28
28
  options = args.extract_options!
@@ -35,16 +35,15 @@ module MongoMapper
35
35
  end
36
36
  end
37
37
 
38
- def paginate(options)
38
+ def paginate(options)
39
39
  per_page = options.delete(:per_page)
40
40
  page = options.delete(:page)
41
41
  total_entries = count(options[:conditions] || {})
42
-
43
- collection = Pagination::PaginationProxy.new(total_entries, page, per_page)
44
-
45
- options[:limit] = collection.limit
42
+ collection = Pagination::PaginationProxy.new(total_entries, page, per_page)
43
+
44
+ options[:limit] = collection.limit
46
45
  options[:offset] = collection.offset
47
-
46
+
48
47
  collection.subject = find_every(options)
49
48
  collection
50
49
  end
@@ -67,11 +66,11 @@ module MongoMapper
67
66
  new(doc)
68
67
  end
69
68
  end
70
-
69
+
71
70
  def count(conditions={})
72
71
  collection.count(FinderOptions.to_mongo_criteria(conditions))
73
72
  end
74
-
73
+
75
74
  def create(*docs)
76
75
  instances = []
77
76
  docs = [{}] if docs.blank?
@@ -81,7 +80,7 @@ module MongoMapper
81
80
  end
82
81
  instances.size == 1 ? instances[0] : instances
83
82
  end
84
-
83
+
85
84
  # For updating single document
86
85
  # Person.update(1, {:foo => 'bar'})
87
86
  #
@@ -96,25 +95,25 @@ module MongoMapper
96
95
  update_single(id, attributes)
97
96
  end
98
97
  end
99
-
98
+
100
99
  def delete(*ids)
101
100
  criteria = FinderOptions.to_mongo_criteria(:_id => ids.flatten)
102
101
  collection.remove(criteria)
103
102
  end
104
-
103
+
105
104
  def delete_all(conditions={})
106
105
  criteria = FinderOptions.to_mongo_criteria(conditions)
107
106
  collection.remove(criteria)
108
107
  end
109
-
108
+
110
109
  def destroy(*ids)
111
110
  find_some(ids.flatten).each(&:destroy)
112
111
  end
113
-
112
+
114
113
  def destroy_all(conditions={})
115
114
  find(:all, :conditions => conditions).each(&:destroy)
116
115
  end
117
-
116
+
118
117
  def connection(mongo_connection=nil)
119
118
  if mongo_connection.nil?
120
119
  @connection ||= MongoMapper.connection
@@ -123,7 +122,7 @@ module MongoMapper
123
122
  end
124
123
  @connection
125
124
  end
126
-
125
+
127
126
  def database(name=nil)
128
127
  if name.nil?
129
128
  @database ||= MongoMapper.database
@@ -132,7 +131,7 @@ module MongoMapper
132
131
  end
133
132
  @database
134
133
  end
135
-
134
+
136
135
  def collection(name=nil)
137
136
  if name.nil?
138
137
  @collection ||= database.collection(self.to_s.demodulize.tableize)
@@ -141,96 +140,137 @@ module MongoMapper
141
140
  end
142
141
  @collection
143
142
  end
144
-
143
+
145
144
  def validates_uniqueness_of(*args)
146
145
  add_validations(args, MongoMapper::Validations::ValidatesUniquenessOf)
147
146
  end
148
-
147
+
149
148
  def validates_exclusion_of(*args)
150
149
  add_validations(args, MongoMapper::Validations::ValidatesExclusionOf)
151
150
  end
152
-
151
+
153
152
  def validates_inclusion_of(*args)
154
153
  add_validations(args, MongoMapper::Validations::ValidatesInclusionOf)
155
154
  end
156
155
 
157
- private
158
- def find_every(options)
159
- criteria, options = FinderOptions.new(options).to_a
160
- collection.find(criteria, options).to_a.map { |doc| new(doc) }
161
- end
162
-
163
- def find_first(options)
164
- options.merge!(:limit => 1)
165
- find_every({:order => '$natural asc'}.merge(options))[0]
166
- end
167
-
168
- def find_last(options)
169
- find_every(options.merge(:limit => 1, :order => '$natural desc'))[0]
170
- end
171
-
172
- def find_some(ids, options={})
173
- documents = find_every(options.deep_merge(:conditions => {'_id' => ids}))
174
- if ids.size == documents.size
175
- documents
176
- else
177
- raise DocumentNotFound, "Couldn't find all of the ids (#{ids.to_sentence}). Found #{documents.size}, but was expecting #{ids.size}"
156
+ protected
157
+ def method_missing(method, *args)
158
+ finder = DynamicFinder.new(self, method)
159
+
160
+ if finder.valid?
161
+ meta_def(finder.options[:method]) do |*args|
162
+ find_with_args(args, finder.options)
163
+ end
164
+
165
+ send(finder.options[:method], *args)
166
+ else
167
+ super
168
+ end
178
169
  end
179
- end
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}"
170
+
171
+ private
172
+ def find_every(options)
173
+ criteria, options = FinderOptions.new(options).to_a
174
+ collection.find(criteria, options).to_a.map { |doc| new(doc) }
186
175
  end
187
- end
188
-
189
- def find_from_ids(ids, options={})
190
- ids = ids.flatten.compact.uniq
191
-
192
- case ids.size
193
- when 0
194
- raise(DocumentNotFound, "Couldn't find without an ID")
195
- when 1
196
- find_one(ids[0], options)
176
+
177
+ def find_first(options)
178
+ options.merge!(:limit => 1)
179
+ find_every({:order => '$natural asc'}.merge(options))[0]
180
+ end
181
+
182
+ def find_last(options)
183
+ options.merge!(:limit => 1)
184
+ find_every({:order => '$natural desc'}.merge(options))[0]
185
+ end
186
+
187
+ def find_some(ids, options={})
188
+ documents = find_every(options.deep_merge(:conditions => {'_id' => ids}))
189
+ if ids.size == documents.size
190
+ documents
197
191
  else
198
- find_some(ids, options)
192
+ raise DocumentNotFound, "Couldn't find all of the ids (#{ids.to_sentence}). Found #{documents.size}, but was expecting #{ids.size}"
193
+ end
199
194
  end
200
- end
201
-
202
- def update_single(id, attrs)
203
- if id.blank? || attrs.blank? || !attrs.is_a?(Hash)
204
- raise ArgumentError, "Updating a single document requires an id and a hash of attributes"
195
+
196
+ def find_one(id, options={})
197
+ if doc = find_every(options.deep_merge(:conditions => {:_id => id})).first
198
+ doc
199
+ else
200
+ raise DocumentNotFound, "Document with id of #{id} does not exist in collection named #{collection.name}"
201
+ end
205
202
  end
206
-
207
- find(id).update_attributes(attrs)
208
- end
209
-
210
- def update_multiple(docs)
211
- unless docs.is_a?(Hash)
212
- raise ArgumentError, "Updating multiple documents takes 1 argument and it must be hash"
203
+
204
+ def find_from_ids(ids, options={})
205
+ ids = ids.flatten.compact.uniq
206
+
207
+ case ids.size
208
+ when 0
209
+ raise(DocumentNotFound, "Couldn't find without an ID")
210
+ when 1
211
+ find_one(ids[0], options)
212
+ else
213
+ find_some(ids, options)
214
+ end
213
215
  end
214
216
 
215
- instances = []
216
- docs.each_pair { |id, attrs| instances << update(id, attrs) }
217
- instances
218
- end
217
+ def find_with_args(args, options)
218
+ attributes, = {}
219
+ find_options = args.extract_options!.deep_merge(:conditions => attributes)
220
+
221
+ options[:attribute_names].each_with_index do |attr, index|
222
+ attributes[attr] = args[index]
223
+ end
224
+
225
+ result = find(options[:finder], find_options)
226
+
227
+ if result.nil?
228
+ if options[:bang]
229
+ raise DocumentNotFound, "Couldn't find Document with #{attributes.inspect} in collection named #{collection.name}"
230
+ end
231
+
232
+ if options[:instantiator]
233
+ self.send(options[:instantiator], attributes)
234
+ end
235
+ else
236
+ result
237
+ end
238
+ end
239
+
240
+ def update_single(id, attrs)
241
+ if id.blank? || attrs.blank? || !attrs.is_a?(Hash)
242
+ raise ArgumentError, "Updating a single document requires an id and a hash of attributes"
243
+ end
244
+
245
+ doc = find(id)
246
+ doc.update_attributes(attrs)
247
+ doc
248
+ end
249
+
250
+ def update_multiple(docs)
251
+ unless docs.is_a?(Hash)
252
+ raise ArgumentError, "Updating multiple documents takes 1 argument and it must be hash"
253
+ end
254
+
255
+ instances = []
256
+ docs.each_pair { |id, attrs| instances << update(id, attrs) }
257
+ instances
258
+ end
219
259
  end
220
-
260
+
221
261
  module InstanceMethods
222
262
  def collection
223
263
  self.class.collection
224
264
  end
225
-
265
+
226
266
  def new?
227
- read_attribute('_id').blank?
267
+ read_attribute('_id').blank? || using_custom_id?
228
268
  end
229
-
269
+
230
270
  def save
231
271
  create_or_update
232
272
  end
233
-
273
+
234
274
  def save!
235
275
  create_or_update || raise(DocumentNotValid.new(self))
236
276
  end
@@ -238,41 +278,54 @@ module MongoMapper
238
278
  def update_attributes(attrs={})
239
279
  self.attributes = attrs
240
280
  save
241
- self
242
281
  end
243
-
282
+
244
283
  def destroy
284
+ return false if frozen?
285
+
245
286
  criteria = FinderOptions.to_mongo_criteria(:_id => id)
246
287
  collection.remove(criteria) unless new?
247
288
  freeze
248
289
  end
249
-
290
+
250
291
  private
251
292
  def create_or_update
252
293
  result = new? ? create : update
253
294
  result != false
254
295
  end
255
-
296
+
256
297
  def create
257
298
  update_timestamps
258
- write_attribute :_id, save_to_collection
299
+ assign_id
300
+ save_to_collection
259
301
  end
260
302
 
303
+ def assign_id
304
+ if read_attribute(:_id).blank?
305
+ write_attribute(:_id, XGen::Mongo::Driver::ObjectID.new.to_s)
306
+ end
307
+ end
308
+
261
309
  def update
262
310
  update_timestamps
263
311
  save_to_collection
264
312
  end
265
-
313
+
266
314
  # collection.save returns mongoid
267
315
  def save_to_collection
316
+ clear_custom_id_flag
268
317
  collection.save(attributes)
269
318
  end
270
-
319
+
271
320
  def update_timestamps
272
321
  now = Time.now.utc
273
322
  write_attribute('created_at', now) if new?
274
323
  write_attribute('updated_at', now)
275
324
  end
325
+
326
+ def clear_custom_id_flag
327
+ @using_custom_id = nil
328
+ end
276
329
  end
277
330
  end # Document
278
331
  end # MongoMapper