djsun-mongo_mapper 0.5.0.1

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 (71) hide show
  1. data/.gitignore +8 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +39 -0
  4. data/Rakefile +87 -0
  5. data/VERSION +1 -0
  6. data/bin/mmconsole +55 -0
  7. data/lib/mongo_mapper/associations/base.rb +83 -0
  8. data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +34 -0
  9. data/lib/mongo_mapper/associations/belongs_to_proxy.rb +22 -0
  10. data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +27 -0
  11. data/lib/mongo_mapper/associations/many_documents_proxy.rb +116 -0
  12. data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +33 -0
  13. data/lib/mongo_mapper/associations/many_embedded_proxy.rb +67 -0
  14. data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +11 -0
  15. data/lib/mongo_mapper/associations/many_proxy.rb +6 -0
  16. data/lib/mongo_mapper/associations/proxy.rb +74 -0
  17. data/lib/mongo_mapper/associations.rb +86 -0
  18. data/lib/mongo_mapper/callbacks.rb +106 -0
  19. data/lib/mongo_mapper/document.rb +308 -0
  20. data/lib/mongo_mapper/dynamic_finder.rb +35 -0
  21. data/lib/mongo_mapper/embedded_document.rb +354 -0
  22. data/lib/mongo_mapper/finder_options.rb +94 -0
  23. data/lib/mongo_mapper/key.rb +32 -0
  24. data/lib/mongo_mapper/observing.rb +50 -0
  25. data/lib/mongo_mapper/pagination.rb +51 -0
  26. data/lib/mongo_mapper/rails_compatibility/document.rb +15 -0
  27. data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +27 -0
  28. data/lib/mongo_mapper/save_with_validation.rb +19 -0
  29. data/lib/mongo_mapper/serialization.rb +55 -0
  30. data/lib/mongo_mapper/serializers/json_serializer.rb +92 -0
  31. data/lib/mongo_mapper/support.rb +171 -0
  32. data/lib/mongo_mapper/validations.rb +69 -0
  33. data/lib/mongo_mapper.rb +95 -0
  34. data/mongo_mapper.gemspec +156 -0
  35. data/specs.watchr +32 -0
  36. data/test/NOTE_ON_TESTING +1 -0
  37. data/test/custom_matchers.rb +48 -0
  38. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +55 -0
  39. data/test/functional/associations/test_belongs_to_proxy.rb +49 -0
  40. data/test/functional/associations/test_many_documents_as_proxy.rb +244 -0
  41. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +132 -0
  42. data/test/functional/associations/test_many_embedded_proxy.rb +174 -0
  43. data/test/functional/associations/test_many_polymorphic_proxy.rb +297 -0
  44. data/test/functional/associations/test_many_proxy.rb +331 -0
  45. data/test/functional/test_associations.rb +44 -0
  46. data/test/functional/test_binary.rb +18 -0
  47. data/test/functional/test_callbacks.rb +85 -0
  48. data/test/functional/test_document.rb +964 -0
  49. data/test/functional/test_embedded_document.rb +97 -0
  50. data/test/functional/test_logger.rb +20 -0
  51. data/test/functional/test_pagination.rb +87 -0
  52. data/test/functional/test_rails_compatibility.rb +30 -0
  53. data/test/functional/test_validations.rb +279 -0
  54. data/test/models.rb +169 -0
  55. data/test/test_helper.rb +30 -0
  56. data/test/unit/serializers/test_json_serializer.rb +193 -0
  57. data/test/unit/test_association_base.rb +144 -0
  58. data/test/unit/test_document.rb +165 -0
  59. data/test/unit/test_dynamic_finder.rb +125 -0
  60. data/test/unit/test_embedded_document.rb +643 -0
  61. data/test/unit/test_finder_options.rb +193 -0
  62. data/test/unit/test_key.rb +175 -0
  63. data/test/unit/test_mongomapper.rb +28 -0
  64. data/test/unit/test_observing.rb +101 -0
  65. data/test/unit/test_pagination.rb +109 -0
  66. data/test/unit/test_rails_compatibility.rb +39 -0
  67. data/test/unit/test_serializations.rb +52 -0
  68. data/test/unit/test_support.rb +272 -0
  69. data/test/unit/test_time_zones.rb +40 -0
  70. data/test/unit/test_validations.rb +503 -0
  71. metadata +207 -0
@@ -0,0 +1,86 @@
1
+ module MongoMapper
2
+ module Associations
3
+ module ClassMethods
4
+ def belongs_to(association_id, options = {})
5
+ create_association(:belongs_to, association_id, options)
6
+ self
7
+ end
8
+
9
+ def many(association_id, options = {})
10
+ create_association(:many, association_id, options)
11
+ self
12
+ end
13
+
14
+ def associations
15
+ @associations ||= self.superclass.respond_to?(:associations) ?
16
+ self.superclass.associations :
17
+ HashWithIndifferentAccess.new
18
+ end
19
+
20
+ private
21
+ def create_association(type, name, options)
22
+ association = Associations::Base.new(type, name, options)
23
+ associations[association.name] = association
24
+ define_association_methods(association)
25
+ define_dependent_callback(association)
26
+ association
27
+ end
28
+
29
+ def define_association_methods(association)
30
+ define_method(association.name) do
31
+ get_proxy(association)
32
+ end
33
+
34
+ define_method("#{association.name}=") do |value|
35
+ get_proxy(association).replace(value)
36
+ value
37
+ end
38
+ end
39
+
40
+ def define_dependent_callback(association)
41
+ if association.options[:dependent]
42
+ if association.many?
43
+ define_dependent_callback_for_many(association)
44
+ elsif association.belongs_to?
45
+ define_dependent_callback_for_belongs_to(association)
46
+ end
47
+ end
48
+ end
49
+
50
+ def define_dependent_callback_for_many(association)
51
+ return if association.embeddable?
52
+
53
+ after_destroy do |doc|
54
+ case association.options[:dependent]
55
+ when :destroy
56
+ doc.get_proxy(association).destroy_all
57
+ when :delete_all
58
+ doc.get_proxy(association).delete_all
59
+ when :nullify
60
+ doc.get_proxy(association).nullify
61
+ end
62
+ end
63
+ end
64
+
65
+ def define_dependent_callback_for_belongs_to(association)
66
+ after_destroy do |doc|
67
+ case association.options[:dependent]
68
+ when :destroy
69
+ doc.get_proxy(association).destroy
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ module InstanceMethods
76
+ def get_proxy(association)
77
+ unless proxy = self.instance_variable_get(association.ivar)
78
+ proxy = association.proxy_class.new(self, association)
79
+ self.instance_variable_set(association.ivar, proxy) if !frozen?
80
+ end
81
+
82
+ proxy
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,106 @@
1
+ module MongoMapper
2
+ module Callbacks
3
+ def self.included(model) #:nodoc:
4
+ model.class_eval do
5
+ extend Observable
6
+ include ActiveSupport::Callbacks
7
+
8
+ define_callbacks *%w(
9
+ before_save after_save before_create after_create before_update after_update before_validation
10
+ after_validation before_validation_on_create after_validation_on_create before_validation_on_update
11
+ after_validation_on_update before_destroy after_destroy
12
+ )
13
+
14
+ [:create_or_update, :valid?, :create, :update, :destroy].each do |method|
15
+ alias_method_chain method, :callbacks
16
+ end
17
+ end
18
+ end
19
+
20
+ def before_save() end
21
+
22
+ def after_save() end
23
+ def create_or_update_with_callbacks #:nodoc:
24
+ return false if callback(:before_save) == false
25
+ if result = create_or_update_without_callbacks
26
+ callback(:after_save)
27
+ end
28
+ result
29
+ end
30
+ private :create_or_update_with_callbacks
31
+
32
+ def before_create() end
33
+
34
+ def after_create() end
35
+ def create_with_callbacks #:nodoc:
36
+ return false if callback(:before_create) == false
37
+ result = create_without_callbacks
38
+ callback(:after_create)
39
+ result
40
+ end
41
+ private :create_with_callbacks
42
+
43
+ def before_update() end
44
+
45
+ def after_update() end
46
+
47
+ def update_with_callbacks(*args) #:nodoc:
48
+ return false if callback(:before_update) == false
49
+ result = update_without_callbacks(*args)
50
+ callback(:after_update)
51
+ result
52
+ end
53
+ private :update_with_callbacks
54
+
55
+ def before_validation() end
56
+
57
+ def after_validation() end
58
+
59
+ def before_validation_on_create() end
60
+
61
+ def after_validation_on_create() end
62
+
63
+ def before_validation_on_update() end
64
+
65
+ def after_validation_on_update() end
66
+
67
+ def valid_with_callbacks? #:nodoc:
68
+ return false if callback(:before_validation) == false
69
+ result = new? ? callback(:before_validation_on_create) : callback(:before_validation_on_update)
70
+ return false if false == result
71
+
72
+ result = valid_without_callbacks?
73
+ callback(:after_validation)
74
+
75
+ new? ? callback(:after_validation_on_create) : callback(:after_validation_on_update)
76
+ return result
77
+ end
78
+
79
+ def before_destroy() end
80
+
81
+ def after_destroy() end
82
+ def destroy_with_callbacks #:nodoc:
83
+ return false if callback(:before_destroy) == false
84
+ result = destroy_without_callbacks
85
+ callback(:after_destroy)
86
+ result
87
+ end
88
+
89
+ private
90
+ def callback(method)
91
+ result = run_callbacks(method) { |result, object| false == result }
92
+
93
+ if result != false && respond_to?(method)
94
+ result = send(method)
95
+ end
96
+
97
+ notify(method)
98
+ return result
99
+ end
100
+
101
+ def notify(method) #:nodoc:
102
+ self.class.changed
103
+ self.class.notify_observers(method, self)
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,308 @@
1
+ require 'set'
2
+
3
+ module MongoMapper
4
+ module Document
5
+ def self.included(model)
6
+ model.class_eval do
7
+ include EmbeddedDocument
8
+ include InstanceMethods
9
+ include Observing
10
+ include Callbacks
11
+ include SaveWithValidation
12
+ include RailsCompatibility::Document
13
+ extend Validations::Macros
14
+ extend ClassMethods
15
+ extend Finders
16
+
17
+ def self.per_page
18
+ 25
19
+ end unless respond_to?(:per_page)
20
+ end
21
+
22
+ descendants << model
23
+ end
24
+
25
+ def self.descendants
26
+ @descendants ||= Set.new
27
+ end
28
+
29
+ module ClassMethods
30
+ def find(*args)
31
+ options = args.extract_options!
32
+ case args.first
33
+ when :first then first(options)
34
+ when :last then last(options)
35
+ when :all then find_every(options)
36
+ when Array then find_some(args, options)
37
+ else
38
+ case args.size
39
+ when 0
40
+ raise DocumentNotFound, "Couldn't find without an ID"
41
+ when 1
42
+ find_one(args[0], options)
43
+ else
44
+ find_some(args, options)
45
+ end
46
+ end
47
+ end
48
+
49
+ def paginate(options)
50
+ per_page = options.delete(:per_page) || self.per_page
51
+ page = options.delete(:page)
52
+ total_entries = count(options[:conditions] || {})
53
+ collection = Pagination::PaginationProxy.new(total_entries, page, per_page)
54
+
55
+ options[:limit] = collection.limit
56
+ options[:skip] = collection.skip
57
+
58
+ collection.subject = find_every(options)
59
+ collection
60
+ end
61
+
62
+ def first(options={})
63
+ options.merge!(:limit => 1)
64
+ find_every(options)[0]
65
+ end
66
+
67
+ def last(options={})
68
+ if options[:order].blank?
69
+ raise ':order option must be provided when using last'
70
+ end
71
+
72
+ options.merge!(:limit => 1)
73
+ options[:order] = invert_order_clause(options[:order])
74
+ find_every(options)[0]
75
+ end
76
+
77
+ def all(options={})
78
+ find_every(options)
79
+ end
80
+
81
+ def find_by_id(id)
82
+ criteria = FinderOptions.to_mongo_criteria(:_id => id)
83
+ if doc = collection.find_one(criteria)
84
+ new(doc)
85
+ end
86
+ end
87
+
88
+ def count(conditions={})
89
+ collection.find(FinderOptions.to_mongo_criteria(conditions)).count
90
+ end
91
+
92
+ def create(*docs)
93
+ instances = []
94
+ docs = [{}] if docs.blank?
95
+ docs.flatten.each do |attrs|
96
+ doc = new(attrs); doc.save
97
+ instances << doc
98
+ end
99
+ instances.size == 1 ? instances[0] : instances
100
+ end
101
+
102
+ # For updating single document
103
+ # Person.update(1, {:foo => 'bar'})
104
+ #
105
+ # For updating multiple documents at once:
106
+ # Person.update({'1' => {:foo => 'bar'}, '2' => {:baz => 'wick'}})
107
+ def update(*args)
108
+ updating_multiple = args.length == 1
109
+ if updating_multiple
110
+ update_multiple(args[0])
111
+ else
112
+ id, attributes = args
113
+ update_single(id, attributes)
114
+ end
115
+ end
116
+
117
+ def delete(*ids)
118
+ criteria = FinderOptions.to_mongo_criteria(:_id => ids.flatten)
119
+ collection.remove(criteria)
120
+ end
121
+
122
+ def delete_all(conditions={})
123
+ criteria = FinderOptions.to_mongo_criteria(conditions)
124
+ collection.remove(criteria)
125
+ end
126
+
127
+ def destroy(*ids)
128
+ find_some(ids.flatten).each(&:destroy)
129
+ end
130
+
131
+ def destroy_all(conditions={})
132
+ find(:all, :conditions => conditions).each(&:destroy)
133
+ end
134
+
135
+ def connection(mongo_connection=nil)
136
+ if mongo_connection.nil?
137
+ @connection ||= MongoMapper.connection
138
+ else
139
+ @connection = mongo_connection
140
+ end
141
+ @connection
142
+ end
143
+
144
+ def database(name=nil)
145
+ if name.nil?
146
+ @database ||= MongoMapper.database
147
+ else
148
+ @database = connection.db(name)
149
+ end
150
+ @database
151
+ end
152
+
153
+ # Changes the collection name from the default to whatever you want
154
+ def set_collection_name(name=nil)
155
+ @collection = nil
156
+ @collection_name = name
157
+ end
158
+
159
+ # Returns the collection name, if not set, defaults to class name tableized
160
+ def collection_name
161
+ @collection_name ||= self.to_s.demodulize.tableize
162
+ end
163
+
164
+ # Returns the mongo ruby driver collection object
165
+ def collection
166
+ @collection ||= database.collection(collection_name)
167
+ end
168
+
169
+ def timestamps!
170
+ key :created_at, Time
171
+ key :updated_at, Time
172
+
173
+ class_eval { before_save :update_timestamps }
174
+ end
175
+
176
+ protected
177
+ def method_missing(method, *args)
178
+ finder = DynamicFinder.new(method)
179
+
180
+ if finder.found?
181
+ meta_def(finder.method) { |*args| dynamic_find(finder, args) }
182
+ send(finder.method, *args)
183
+ else
184
+ super
185
+ end
186
+ end
187
+
188
+ private
189
+ def find_every(options)
190
+ criteria, options = FinderOptions.new(options).to_a
191
+ collection.find(criteria, options).to_a.map { |doc| new(doc) }
192
+ end
193
+
194
+ def invert_order_clause(order)
195
+ order.split(',').map do |order_segment|
196
+ if order_segment =~ /\sasc/i
197
+ order_segment.sub /\sasc/i, ' desc'
198
+ elsif order_segment =~ /\sdesc/i
199
+ order_segment.sub /\sdesc/i, ' asc'
200
+ else
201
+ "#{order_segment.strip} desc"
202
+ end
203
+ end.join(',')
204
+ end
205
+
206
+ def find_some(ids, options={})
207
+ ids = ids.flatten.compact.uniq
208
+ documents = find_every(options.deep_merge(:conditions => {'_id' => ids}))
209
+
210
+ if ids.size == documents.size
211
+ documents
212
+ else
213
+ raise DocumentNotFound, "Couldn't find all of the ids (#{ids.to_sentence}). Found #{documents.size}, but was expecting #{ids.size}"
214
+ end
215
+ end
216
+
217
+ def find_one(id, options={})
218
+ if doc = find_every(options.deep_merge(:conditions => {:_id => id})).first
219
+ doc
220
+ else
221
+ raise DocumentNotFound, "Document with id of #{id} does not exist in collection named #{collection.name}"
222
+ end
223
+ end
224
+
225
+ def update_single(id, attrs)
226
+ if id.blank? || attrs.blank? || !attrs.is_a?(Hash)
227
+ raise ArgumentError, "Updating a single document requires an id and a hash of attributes"
228
+ end
229
+
230
+ doc = find(id)
231
+ doc.update_attributes(attrs)
232
+ doc
233
+ end
234
+
235
+ def update_multiple(docs)
236
+ unless docs.is_a?(Hash)
237
+ raise ArgumentError, "Updating multiple documents takes 1 argument and it must be hash"
238
+ end
239
+
240
+ instances = []
241
+ docs.each_pair { |id, attrs| instances << update(id, attrs) }
242
+ instances
243
+ end
244
+ end
245
+
246
+ module InstanceMethods
247
+ def collection
248
+ self.class.collection
249
+ end
250
+
251
+ def new?
252
+ read_attribute('_id').blank? || using_custom_id?
253
+ end
254
+
255
+ def save
256
+ create_or_update
257
+ end
258
+
259
+ def save!
260
+ create_or_update || raise(DocumentNotValid.new(self))
261
+ end
262
+
263
+ def destroy
264
+ return false if frozen?
265
+
266
+ criteria = FinderOptions.to_mongo_criteria(:_id => id)
267
+ collection.remove(criteria) unless new?
268
+ freeze
269
+ end
270
+
271
+ private
272
+ def create_or_update
273
+ result = new? ? create : update
274
+ result != false
275
+ end
276
+
277
+ def create
278
+ assign_id
279
+ save_to_collection
280
+ end
281
+
282
+ def assign_id
283
+ if read_attribute(:_id).blank?
284
+ write_attribute(:_id, Mongo::ObjectID.new.to_s)
285
+ end
286
+ end
287
+
288
+ def update
289
+ save_to_collection
290
+ end
291
+
292
+ def save_to_collection
293
+ clear_custom_id_flag
294
+ collection.save(to_mongo)
295
+ end
296
+
297
+ def update_timestamps
298
+ now = Time.now.utc
299
+ write_attribute('created_at', now) if new?
300
+ write_attribute('updated_at', now)
301
+ end
302
+
303
+ def clear_custom_id_flag
304
+ @using_custom_id = nil
305
+ end
306
+ end
307
+ end # Document
308
+ end # MongoMapper
@@ -0,0 +1,35 @@
1
+ module MongoMapper
2
+ class DynamicFinder
3
+ attr_reader :method, :attributes, :finder, :bang, :instantiator
4
+
5
+ def initialize(method)
6
+ @method = method
7
+ @finder = :first
8
+ @bang = false
9
+ match()
10
+ end
11
+
12
+ def found?
13
+ @finder.present?
14
+ end
15
+
16
+ protected
17
+ def match
18
+ case method.to_s
19
+ when /^find_(all_by|by)_([_a-zA-Z]\w*)$/
20
+ @finder = :all if $1 == 'all_by'
21
+ names = $2
22
+ when /^find_by_([_a-zA-Z]\w*)\!$/
23
+ @bang = true
24
+ names = $1
25
+ when /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/
26
+ @instantiator = $1 == 'initialize' ? :new : :create
27
+ names = $2
28
+ else
29
+ @finder = nil
30
+ end
31
+
32
+ @attributes = names && names.split('_and_')
33
+ end
34
+ end
35
+ end