fcoury-mongomapper 0.2.0

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 (42) hide show
  1. data/.gitignore +7 -0
  2. data/History +30 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +39 -0
  5. data/Rakefile +71 -0
  6. data/VERSION +1 -0
  7. data/lib/mongomapper.rb +70 -0
  8. data/lib/mongomapper/associations.rb +69 -0
  9. data/lib/mongomapper/associations/array_proxy.rb +6 -0
  10. data/lib/mongomapper/associations/base.rb +54 -0
  11. data/lib/mongomapper/associations/belongs_to_proxy.rb +26 -0
  12. data/lib/mongomapper/associations/has_many_embedded_proxy.rb +19 -0
  13. data/lib/mongomapper/associations/has_many_proxy.rb +29 -0
  14. data/lib/mongomapper/associations/polymorphic_belongs_to_proxy.rb +31 -0
  15. data/lib/mongomapper/associations/proxy.rb +66 -0
  16. data/lib/mongomapper/callbacks.rb +106 -0
  17. data/lib/mongomapper/document.rb +276 -0
  18. data/lib/mongomapper/document_rails_compatibility.rb +13 -0
  19. data/lib/mongomapper/embedded_document.rb +248 -0
  20. data/lib/mongomapper/embedded_document_rails_compatibility.rb +22 -0
  21. data/lib/mongomapper/finder_options.rb +81 -0
  22. data/lib/mongomapper/key.rb +82 -0
  23. data/lib/mongomapper/observing.rb +50 -0
  24. data/lib/mongomapper/save_with_validation.rb +19 -0
  25. data/lib/mongomapper/serialization.rb +55 -0
  26. data/lib/mongomapper/serializers/json_serializer.rb +77 -0
  27. data/lib/mongomapper/validations.rb +47 -0
  28. data/mongomapper.gemspec +105 -0
  29. data/test/serializers/test_json_serializer.rb +104 -0
  30. data/test/test_associations.rb +444 -0
  31. data/test/test_callbacks.rb +84 -0
  32. data/test/test_document.rb +1002 -0
  33. data/test/test_embedded_document.rb +253 -0
  34. data/test/test_finder_options.rb +148 -0
  35. data/test/test_helper.rb +62 -0
  36. data/test/test_key.rb +200 -0
  37. data/test/test_mongomapper.rb +28 -0
  38. data/test/test_observing.rb +101 -0
  39. data/test/test_rails_compatibility.rb +73 -0
  40. data/test/test_serializations.rb +54 -0
  41. data/test/test_validations.rb +409 -0
  42. metadata +155 -0
@@ -0,0 +1,31 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class PolymorphicBelongsToProxy < Proxy
4
+ def replace(v)
5
+ ref_id = "#{@association.name}_id"
6
+ ref_type = "#{@association.name}_type"
7
+
8
+ if v
9
+ v.save if v.new?
10
+ @owner.__send__(:write_attribute, ref_id, v.id)
11
+ @owner.__send__(:write_attribute, ref_type, v.class.name)
12
+ else
13
+ @owner.__send__(:write_attribute, ref_id, nil)
14
+ @owner.__send__(:write_attribute, ref_type, nil)
15
+ end
16
+ @owner.save
17
+
18
+ reload_target
19
+ end
20
+
21
+ protected
22
+ def find_target
23
+ ref_id = @owner.__send__(:read_attribute, "#{@association.name}_id")
24
+ ref_type = @owner.__send__(:read_attribute, "#{@association.name}_type")
25
+ if ref_id && ref_type
26
+ ref_type.constantize.find(ref_id)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,66 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class Proxy
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
+
10
+ def initialize(owner, association)
11
+ @owner= owner
12
+ @association = association
13
+
14
+ reset
15
+ end
16
+
17
+ def respond_to?(*methods)
18
+ (load_target && @target.respond_to?(*methods))
19
+ end
20
+
21
+ def reset
22
+ @target = nil
23
+ end
24
+
25
+ def reload_target
26
+ reset
27
+ load_target
28
+ self
29
+ end
30
+
31
+ def send(method, *args)
32
+ load_target
33
+ @target.send(method, *args)
34
+ end
35
+
36
+ def replace(v)
37
+ raise NotImplementedError
38
+ end
39
+
40
+ 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)
47
+ end
48
+ end
49
+ end
50
+
51
+ def load_target
52
+ @target ||= find_target
53
+ end
54
+
55
+ def find_target
56
+ raise NotImplementedError
57
+ end
58
+
59
+ # Array#flatten has problems with recursive arrays. Going one level
60
+ # deeper solves the majority of the problems.
61
+ def flatten_deeper(array)
62
+ array.collect { |element| (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element }.flatten
63
+ end
64
+ end
65
+ end
66
+ 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_without_attributes?(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,276 @@
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 DocumentRailsCompatibility
13
+ extend ClassMethods
14
+
15
+ key :_id, String
16
+ key :created_at, Time
17
+ key :updated_at, Time
18
+ end
19
+
20
+ descendants << model
21
+ end
22
+
23
+ def self.descendants
24
+ @descendants ||= Set.new
25
+ end
26
+
27
+ module ClassMethods
28
+ def find(*args)
29
+ options = args.extract_options!
30
+
31
+ case args.first
32
+ when :first then find_first(options)
33
+ when :last then find_last(options)
34
+ when :all then find_every(options)
35
+ else find_from_ids(args)
36
+ end
37
+ end
38
+
39
+ def paginate(options)
40
+ per_page = options.delete(:per_page)
41
+ page = options.delete(:page)
42
+ total_entries = count(options)
43
+
44
+ collection = Pagination::PaginationProxy.new(total_entries, page, per_page)
45
+
46
+ options[:limit] = collection.limit
47
+ options[:skip] = collection.skip
48
+
49
+ collection.subject = find_every(options)
50
+ collection
51
+ end
52
+
53
+ def first(options={})
54
+ find_first(options)
55
+ end
56
+
57
+ def last(options={})
58
+ find_last(options)
59
+ end
60
+
61
+ def all(options={})
62
+ find_every(options)
63
+ end
64
+
65
+ def find_by_id(id)
66
+ if doc = collection.find_first({:_id => id})
67
+ new(doc)
68
+ end
69
+ end
70
+
71
+ # TODO: remove the rescuing when ruby driver works correctly
72
+ def count(conditions={})
73
+ collection.count(FinderOptions.to_mongo_criteria(conditions))
74
+ end
75
+
76
+ def create(*docs)
77
+ instances = []
78
+ docs.flatten.each do |attrs|
79
+ doc = new(attrs); doc.save
80
+ instances << doc
81
+ end
82
+ instances.size == 1 ? instances[0] : instances
83
+ end
84
+
85
+ # For updating single document
86
+ # Person.update(1, {:foo => 'bar'})
87
+ #
88
+ # For updating multiple documents at once:
89
+ # Person.update({'1' => {:foo => 'bar'}, '2' => {:baz => 'wick'}})
90
+ def update(*args)
91
+ updating_multiple = args.length == 1
92
+ if updating_multiple
93
+ update_multiple(args[0])
94
+ else
95
+ id, attributes = args
96
+ update_single(id, attributes)
97
+ end
98
+ end
99
+
100
+ def delete(*ids)
101
+ collection.remove(:_id => {'$in' => ids.flatten})
102
+ end
103
+
104
+ def delete_all(conditions={})
105
+ collection.remove(FinderOptions.to_mongo_criteria(conditions))
106
+ end
107
+
108
+ def destroy(*ids)
109
+ find_some(ids.flatten).each(&:destroy)
110
+ end
111
+
112
+ def destroy_all(conditions={})
113
+ find(:all, :conditions => conditions).each(&:destroy)
114
+ end
115
+
116
+ def connection(mongo_connection=nil)
117
+ if mongo_connection.nil?
118
+ @connection ||= MongoMapper.connection
119
+ else
120
+ @connection = mongo_connection
121
+ end
122
+ @connection
123
+ end
124
+
125
+ def database(name=nil)
126
+ if name.nil?
127
+ @database ||= MongoMapper.database
128
+ else
129
+ @database = connection.db(name)
130
+ end
131
+ @database
132
+ end
133
+
134
+ def collection(name=nil)
135
+ if name.nil?
136
+ @collection ||= database.collection(self.to_s.demodulize.tableize)
137
+ else
138
+ @collection = database.collection(name)
139
+ end
140
+ @collection
141
+ end
142
+
143
+ def validates_uniqueness_of(*args)
144
+ add_validations(args, MongoMapper::Validations::ValidatesUniquenessOf)
145
+ end
146
+
147
+ def validates_exclusion_of(*args)
148
+ add_validations(args, MongoMapper::Validations::ValidatesExclusionOf)
149
+ end
150
+
151
+ def validates_inclusion_of(*args)
152
+ add_validations(args, MongoMapper::Validations::ValidatesInclusionOf)
153
+ end
154
+
155
+ private
156
+ def find_every(options)
157
+ criteria, options = FinderOptions.new(options).to_a
158
+ collection.find(criteria, options).to_a.map { |doc| new(doc) }
159
+ end
160
+
161
+ def find_first(options)
162
+ find_every(options.merge(:limit => 1, :order => 'created_at')).first
163
+ end
164
+
165
+ def find_last(options)
166
+ find_every(options.merge(:limit => 1, :order => 'created_at desc')).first
167
+ end
168
+
169
+ def find_some(ids)
170
+ documents = find_every(:conditions => {'_id' => ids})
171
+ if ids.size == documents.size
172
+ documents
173
+ else
174
+ raise DocumentNotFound, "Couldn't find all of the ids (#{ids.to_sentence}). Found #{documents.size}, but was expecting #{ids.size}"
175
+ end
176
+ end
177
+
178
+ def find_from_ids(*ids)
179
+ ids = ids.flatten.compact.uniq
180
+
181
+ case ids.size
182
+ when 0
183
+ raise(DocumentNotFound, "Couldn't find without an ID")
184
+ when 1
185
+ find_by_id(ids[0]) || raise(DocumentNotFound, "Document with id of #{ids[0]} does not exist in collection named #{collection.name}")
186
+ else
187
+ find_some(ids)
188
+ end
189
+ end
190
+
191
+ def update_single(id, attrs)
192
+ if id.blank? || attrs.blank? || !attrs.is_a?(Hash)
193
+ raise ArgumentError, "Updating a single document requires an id and a hash of attributes"
194
+ end
195
+
196
+ find(id).update_attributes(attrs)
197
+ end
198
+
199
+ def update_multiple(docs)
200
+ unless docs.is_a?(Hash)
201
+ raise ArgumentError, "Updating multiple documents takes 1 argument and it must be hash"
202
+ end
203
+ instances = []
204
+ docs.each_pair { |id, attrs| instances << update(id, attrs) }
205
+ instances
206
+ end
207
+ end
208
+
209
+ module InstanceMethods
210
+ def collection
211
+ self.class.collection
212
+ end
213
+
214
+ def new?
215
+ read_attribute('_id').blank? || self.class.find_by_id(id).blank?
216
+ end
217
+
218
+ def save
219
+ create_or_update
220
+ end
221
+
222
+ def save!
223
+ create_or_update || raise(DocumentNotValid.new(self))
224
+ end
225
+
226
+ def update_attributes(attrs={})
227
+ self.attributes = attrs
228
+ save
229
+ self
230
+ end
231
+
232
+ def destroy
233
+ collection.remove(:_id => id) unless new?
234
+ freeze
235
+ end
236
+
237
+ def ==(other)
238
+ other.is_a?(self.class) && id == other.id
239
+ end
240
+
241
+ def id
242
+ read_attribute('_id')
243
+ end
244
+
245
+ private
246
+ def create_or_update
247
+ result = new? ? create : update
248
+ result != false
249
+ end
250
+
251
+ def create
252
+ write_attribute('_id', generate_id) if read_attribute('_id').blank?
253
+ update_timestamps
254
+ save_to_collection
255
+ end
256
+
257
+ def update
258
+ update_timestamps
259
+ save_to_collection
260
+ end
261
+
262
+ def save_to_collection
263
+ collection.save(attributes.merge!(embedded_association_attributes))
264
+ end
265
+
266
+ def update_timestamps
267
+ write_attribute('created_at', Time.now.utc) if new?
268
+ write_attribute('updated_at', Time.now.utc)
269
+ end
270
+
271
+ def generate_id
272
+ XGen::Mongo::Driver::ObjectID.new
273
+ end
274
+ end
275
+ end # Document
276
+ end # MongoMapper