jnunemaker-mongomapper 0.3.2 → 0.3.3

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