djsun-mongo_mapper 0.5.0.1

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