drogus-mongo_mapper 0.6.10

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 (91) hide show
  1. data/.gitignore +10 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +29 -0
  4. data/Rakefile +55 -0
  5. data/VERSION +1 -0
  6. data/bin/mmconsole +60 -0
  7. data/lib/mongo_mapper.rb +131 -0
  8. data/lib/mongo_mapper/document.rb +417 -0
  9. data/lib/mongo_mapper/embedded_document.rb +55 -0
  10. data/lib/mongo_mapper/finder_options.rb +127 -0
  11. data/lib/mongo_mapper/plugins.rb +30 -0
  12. data/lib/mongo_mapper/plugins/associations.rb +104 -0
  13. data/lib/mongo_mapper/plugins/associations/base.rb +121 -0
  14. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +30 -0
  15. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +25 -0
  16. data/lib/mongo_mapper/plugins/associations/collection.rb +21 -0
  17. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +50 -0
  18. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +139 -0
  19. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
  20. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +117 -0
  21. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +31 -0
  22. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +23 -0
  23. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +13 -0
  24. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +68 -0
  25. data/lib/mongo_mapper/plugins/associations/proxy.rb +118 -0
  26. data/lib/mongo_mapper/plugins/callbacks.rb +134 -0
  27. data/lib/mongo_mapper/plugins/clone.rb +13 -0
  28. data/lib/mongo_mapper/plugins/descendants.rb +16 -0
  29. data/lib/mongo_mapper/plugins/dirty.rb +119 -0
  30. data/lib/mongo_mapper/plugins/equality.rb +23 -0
  31. data/lib/mongo_mapper/plugins/identity_map.rb +122 -0
  32. data/lib/mongo_mapper/plugins/inspect.rb +14 -0
  33. data/lib/mongo_mapper/plugins/keys.rb +324 -0
  34. data/lib/mongo_mapper/plugins/logger.rb +17 -0
  35. data/lib/mongo_mapper/plugins/pagination.rb +85 -0
  36. data/lib/mongo_mapper/plugins/protected.rb +45 -0
  37. data/lib/mongo_mapper/plugins/rails.rb +45 -0
  38. data/lib/mongo_mapper/plugins/serialization.rb +105 -0
  39. data/lib/mongo_mapper/plugins/validations.rb +57 -0
  40. data/lib/mongo_mapper/support.rb +217 -0
  41. data/lib/mongo_mapper/support/descendant_appends.rb +46 -0
  42. data/lib/mongo_mapper/support/find.rb +77 -0
  43. data/mongo_mapper.gemspec +195 -0
  44. data/performance/read_write.rb +52 -0
  45. data/specs.watchr +51 -0
  46. data/test/NOTE_ON_TESTING +1 -0
  47. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +63 -0
  48. data/test/functional/associations/test_belongs_to_proxy.rb +101 -0
  49. data/test/functional/associations/test_in_array_proxy.rb +309 -0
  50. data/test/functional/associations/test_many_documents_as_proxy.rb +229 -0
  51. data/test/functional/associations/test_many_documents_proxy.rb +431 -0
  52. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +176 -0
  53. data/test/functional/associations/test_many_embedded_proxy.rb +256 -0
  54. data/test/functional/associations/test_many_polymorphic_proxy.rb +302 -0
  55. data/test/functional/associations/test_one_proxy.rb +161 -0
  56. data/test/functional/test_associations.rb +44 -0
  57. data/test/functional/test_binary.rb +27 -0
  58. data/test/functional/test_callbacks.rb +81 -0
  59. data/test/functional/test_dirty.rb +163 -0
  60. data/test/functional/test_document.rb +1264 -0
  61. data/test/functional/test_embedded_document.rb +125 -0
  62. data/test/functional/test_identity_map.rb +508 -0
  63. data/test/functional/test_logger.rb +20 -0
  64. data/test/functional/test_modifiers.rb +252 -0
  65. data/test/functional/test_pagination.rb +93 -0
  66. data/test/functional/test_protected.rb +155 -0
  67. data/test/functional/test_string_id_compatibility.rb +67 -0
  68. data/test/functional/test_validations.rb +329 -0
  69. data/test/models.rb +232 -0
  70. data/test/support/custom_matchers.rb +55 -0
  71. data/test/support/timing.rb +16 -0
  72. data/test/test_helper.rb +60 -0
  73. data/test/unit/associations/test_base.rb +207 -0
  74. data/test/unit/associations/test_proxy.rb +105 -0
  75. data/test/unit/serializers/test_json_serializer.rb +189 -0
  76. data/test/unit/test_descendant_appends.rb +71 -0
  77. data/test/unit/test_document.rb +231 -0
  78. data/test/unit/test_dynamic_finder.rb +123 -0
  79. data/test/unit/test_embedded_document.rb +663 -0
  80. data/test/unit/test_finder_options.rb +329 -0
  81. data/test/unit/test_keys.rb +169 -0
  82. data/test/unit/test_mongo_mapper.rb +65 -0
  83. data/test/unit/test_pagination.rb +127 -0
  84. data/test/unit/test_plugins.rb +50 -0
  85. data/test/unit/test_rails.rb +123 -0
  86. data/test/unit/test_rails_compatibility.rb +52 -0
  87. data/test/unit/test_serialization.rb +51 -0
  88. data/test/unit/test_support.rb +354 -0
  89. data/test/unit/test_time_zones.rb +39 -0
  90. data/test/unit/test_validations.rb +544 -0
  91. metadata +290 -0
@@ -0,0 +1,10 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ *~
7
+ *.gem
8
+ tmp
9
+ .yardoc
10
+ doc/*
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 John Nunemaker
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,29 @@
1
+ = MongoMapper
2
+
3
+ Awesome gem for modeling your domain and storing it in mongo.
4
+
5
+ Releases are tagged on github and released on gemcutter. Master is pushed to whenever I add a patch or a new feature, but I do not release a new gem version each time I push.
6
+
7
+ == Note on Patches/Pull Requests
8
+
9
+ * Fork the project.
10
+ * Make your feature addition or bug fix.
11
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
12
+ * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself in another branch so I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Install
16
+
17
+ $ gem install mongo_mapper
18
+
19
+ == Problems or Questions?
20
+
21
+ Hit up the google group.
22
+ http://groups.google.com/group/mongomapper
23
+
24
+ To see if the problem you are having is a verified issue, you can see the MM pivotal tracker project:
25
+ http://www.pivotaltracker.com/projects/33576
26
+
27
+ == Copyright
28
+
29
+ Copyright (c) 2009 John Nunemaker. See LICENSE for details.
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'jeweler'
4
+ require 'yard'
5
+ require 'yard/rake/yardoc_task'
6
+
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "mongo_mapper"
9
+ gem.summary = %Q{Awesome gem for modeling your domain and storing it in mongo}
10
+ gem.email = "nunemaker@gmail.com"
11
+ gem.homepage = "http://github.com/jnunemaker/mongomapper"
12
+ gem.authors = ["John Nunemaker"]
13
+
14
+ gem.add_dependency('activesupport', '>= 2.3')
15
+ gem.add_dependency('mongo', '0.19')
16
+ gem.add_dependency('jnunemaker-validatable', '1.8.1')
17
+
18
+ gem.add_development_dependency('jnunemaker-matchy', '0.4.0')
19
+ gem.add_development_dependency('shoulda', '2.10.2')
20
+ gem.add_development_dependency('timecop', '0.3.1')
21
+ gem.add_development_dependency('mocha', '0.9.8')
22
+ end
23
+
24
+ Jeweler::GemcutterTasks.new
25
+
26
+ require 'rake/testtask'
27
+ Rake::TestTask.new(:test) do |test|
28
+ test.libs << 'test'
29
+ test.ruby_opts << '-rubygems'
30
+ test.pattern = 'test/**/test_*.rb'
31
+ test.verbose = true
32
+ end
33
+
34
+ namespace :test do
35
+ Rake::TestTask.new(:units) do |test|
36
+ test.libs << 'test'
37
+ test.ruby_opts << '-rubygems'
38
+ test.pattern = 'test/unit/**/test_*.rb'
39
+ test.verbose = true
40
+ end
41
+
42
+ Rake::TestTask.new(:functionals) do |test|
43
+ test.libs << 'test'
44
+ test.ruby_opts << '-rubygems'
45
+ test.pattern = 'test/functional/**/test_*.rb'
46
+ test.verbose = true
47
+ end
48
+ end
49
+
50
+ task :default => :test
51
+ task :test => :check_dependencies
52
+
53
+ YARD::Rake::YardocTask.new(:doc) do |t|
54
+ t.options = ["--legacy"] if RUBY_VERSION < "1.9.0"
55
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.6.10
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
3
+
4
+ begin
5
+ require 'mongo_mapper'
6
+ require 'irb'
7
+ rescue LoadError
8
+ require 'rubygems'
9
+ retry
10
+ end
11
+
12
+ IRB.setup(nil)
13
+ irb = IRB::Irb.new
14
+
15
+ IRB.conf[:MAIN_CONTEXT] = irb.context
16
+
17
+ irb.context.evaluate("require 'irb/completion'", 0)
18
+ irb.context.evaluate(%@
19
+ include MongoMapper
20
+
21
+ MongoMapper.database = "mmtest"
22
+ $db = MongoMapper.database
23
+
24
+ @, 0)
25
+
26
+ puts %@
27
+ Welcome to the MongoMapper Console!
28
+
29
+ Example 1:
30
+ things = $db.collection("things")
31
+ things.insert("name" => "Raw Thing")
32
+ things.insert("name" => "Another Thing", "date" => Time.now)
33
+
34
+ cursor = things.find("name" => "Raw Thing")
35
+ puts cursor.next_object.inspect
36
+
37
+ Example 2:
38
+ class Thing
39
+ include MongoMapper::Document
40
+ key :name, String, :required => true
41
+ key :date, Time
42
+ end
43
+
44
+ thing = Thing.new
45
+ thing.name = "My thing"
46
+ thing.date = Time.now
47
+ thing.save
48
+
49
+ all_things = Thing.all
50
+ puts all_things.map { |object| object.name }.inspect
51
+ @
52
+
53
+ trap("SIGINT") do
54
+ irb.signal_handle
55
+ end
56
+
57
+ catch(:IRB_EXIT) do
58
+ irb.eval_input
59
+ end
60
+
@@ -0,0 +1,131 @@
1
+ require 'set'
2
+
3
+ # if Gem is defined i'll assume you are using rubygems and lock specific versions
4
+ # call me crazy but a plain old require will just get the latest version you have installed
5
+ # so i want to make sure that if you are using gems you do in fact have the correct versions
6
+ # if there is a better way to do this, please enlighten me!
7
+
8
+ if self.class.const_defined?(:Gem)
9
+ if defined?(ActiveModel)
10
+ # Rails 3 are used, require activesupport 3.0
11
+ gem 'activesupport', '>= 3.0.0.beta'
12
+ else
13
+ gem 'activesupport', '2.3.5'
14
+ end
15
+
16
+ gem 'mongo', '0.19'
17
+ gem 'jnunemaker-validatable', '1.8.1'
18
+ end
19
+
20
+ require 'active_support'
21
+ if defined?(ActiveModel)
22
+ require 'active_model'
23
+ end
24
+ require 'mongo'
25
+ require 'validatable'
26
+
27
+ if defined?(ActiveModel)
28
+ require 'active_support/hash_with_indifferent_access'
29
+ require 'active_support/json/encoding'
30
+ require 'active_support/core_ext/module/delegation'
31
+ require 'active_support/core_ext/hash/indifferent_access'
32
+ require 'active_support/core_ext/object/try'
33
+ require 'active_support/core_ext/class/attribute_accessors'
34
+ require 'active_support/json'
35
+ require 'logger'
36
+ else
37
+ require 'active_support/core_ext/hash/indifferent_access'
38
+ end
39
+
40
+ module MongoMapper
41
+ # generic MM error
42
+ class MongoMapperError < StandardError; end
43
+
44
+ # raised when key expected to exist but not found
45
+ class KeyNotFound < MongoMapperError; end
46
+
47
+ # raised when document expected but not found
48
+ class DocumentNotFound < MongoMapperError; end
49
+
50
+ # raised when document not valid and using !
51
+ class DocumentNotValid < MongoMapperError
52
+ def initialize(document)
53
+ super("Validation failed: #{document.errors.full_messages.join(", ")}")
54
+ end
55
+ end
56
+
57
+ # @api public
58
+ def self.connection
59
+ @@connection ||= Mongo::Connection.new
60
+ end
61
+
62
+ # @api public
63
+ def self.connection=(new_connection)
64
+ @@connection = new_connection
65
+ end
66
+
67
+ # @api public
68
+ def self.logger
69
+ connection.logger
70
+ end
71
+
72
+ # @api public
73
+ def self.database=(name)
74
+ @@database = nil
75
+ @@database_name = name
76
+ end
77
+
78
+ # @api public
79
+ def self.database
80
+ if @@database_name.blank?
81
+ raise 'You forgot to set the default database name: MongoMapper.database = "foobar"'
82
+ end
83
+
84
+ @@database ||= MongoMapper.connection.db(@@database_name)
85
+ end
86
+
87
+ # @api private
88
+ def self.ensured_indexes
89
+ @@ensured_indexes ||= []
90
+ end
91
+
92
+ # @api private
93
+ def self.ensured_indexes=(value)
94
+ @@ensured_indexes = value
95
+ end
96
+
97
+ # @api private
98
+ def self.ensure_index(klass, keys, options={})
99
+ ensured_indexes << {:klass => klass, :keys => keys, :options => options}
100
+ end
101
+
102
+ # @api public
103
+ def self.ensure_indexes!
104
+ ensured_indexes.each do |index|
105
+ unique = index[:options].delete(:unique)
106
+ index[:klass].collection.create_index(index[:keys], unique)
107
+ end
108
+ end
109
+
110
+ # @api private
111
+ def self.use_time_zone?
112
+ Time.respond_to?(:zone) && Time.zone ? true : false
113
+ end
114
+
115
+ # @api private
116
+ def self.time_class
117
+ use_time_zone? ? Time.zone : Time
118
+ end
119
+
120
+ # @api private
121
+ def self.normalize_object_id(value)
122
+ value.is_a?(String) ? Mongo::ObjectID.from_string(value) : value
123
+ end
124
+ end
125
+
126
+ require 'mongo_mapper/support'
127
+ require 'mongo_mapper/finder_options'
128
+ require 'mongo_mapper/plugins'
129
+
130
+ require 'mongo_mapper/document'
131
+ require 'mongo_mapper/embedded_document'
@@ -0,0 +1,417 @@
1
+ module MongoMapper
2
+ module Document
3
+ extend Support::DescendantAppends
4
+
5
+ def self.included(model)
6
+ model.class_eval do
7
+ include InstanceMethods
8
+ extend Support::Find
9
+ extend ClassMethods
10
+ extend Plugins
11
+
12
+ plugin Plugins::Associations
13
+ plugin Plugins::Clone
14
+ plugin Plugins::Descendants
15
+ plugin Plugins::Equality
16
+ plugin Plugins::Inspect
17
+ plugin Plugins::Keys
18
+ plugin Plugins::Dirty # for now dirty needs to be after keys
19
+ plugin Plugins::Logger
20
+ plugin Plugins::Pagination
21
+ plugin Plugins::Protected
22
+ plugin Plugins::Rails
23
+ plugin Plugins::Serialization
24
+ plugin Plugins::Validations
25
+ plugin Plugins::Callbacks # for now callbacks needs to be after validations
26
+
27
+ extend Plugins::Validations::DocumentMacros
28
+ end
29
+
30
+ super
31
+ end
32
+
33
+ module ClassMethods
34
+ def inherited(subclass)
35
+ subclass.set_collection_name(collection_name)
36
+ super
37
+ end
38
+
39
+ def ensure_index(name_or_array, options={})
40
+ keys_to_index = if name_or_array.is_a?(Array)
41
+ name_or_array.map { |pair| [pair[0], pair[1]] }
42
+ else
43
+ name_or_array
44
+ end
45
+
46
+ MongoMapper.ensure_index(self, keys_to_index, options)
47
+ end
48
+
49
+ def find(*args)
50
+ assert_no_first_last_or_all(args)
51
+ options = args.extract_options!
52
+ return nil if args.size == 0
53
+
54
+ if args.first.is_a?(Array) || args.size > 1
55
+ find_some(args, options)
56
+ else
57
+ find_one(options.merge({:_id => args[0]}))
58
+ end
59
+ end
60
+
61
+ def find!(*args)
62
+ assert_no_first_last_or_all(args)
63
+ options = args.extract_options!
64
+ raise DocumentNotFound, "Couldn't find without an ID" if args.size == 0
65
+
66
+ if args.first.is_a?(Array) || args.size > 1
67
+ find_some!(args, options)
68
+ else
69
+ find_one(options.merge({:_id => args[0]})) || raise(DocumentNotFound, "Document match #{options.inspect} does not exist in #{collection.name} collection")
70
+ end
71
+ end
72
+
73
+ def find_each(options={})
74
+ criteria, options = to_finder_options(options)
75
+ collection.find(criteria, options).each do |doc|
76
+ yield load(doc)
77
+ end
78
+ end
79
+
80
+ def find_by_id(id)
81
+ find(id)
82
+ end
83
+
84
+ def first_or_create(arg)
85
+ first(arg) || create(arg)
86
+ end
87
+
88
+ def first_or_new(arg)
89
+ first(arg) || new(arg)
90
+ end
91
+
92
+ def first(options={})
93
+ find_one(options)
94
+ end
95
+
96
+ def last(options={})
97
+ raise ':order option must be provided when using last' if options[:order].blank?
98
+ find_one(options.merge(:order => invert_order_clause(options[:order])))
99
+ end
100
+
101
+ def all(options={})
102
+ find_many(options)
103
+ end
104
+
105
+ def count(options={})
106
+ collection.find(to_criteria(options)).count
107
+ end
108
+
109
+ def exists?(options={})
110
+ !count(options).zero?
111
+ end
112
+
113
+ def create(*docs)
114
+ initialize_each(*docs) { |doc| doc.save }
115
+ end
116
+
117
+ def create!(*docs)
118
+ initialize_each(*docs) { |doc| doc.save! }
119
+ end
120
+
121
+ def update(*args)
122
+ if args.length == 1
123
+ update_multiple(args[0])
124
+ else
125
+ id, attributes = args
126
+ update_single(id, attributes)
127
+ end
128
+ end
129
+
130
+ def delete(*ids)
131
+ collection.remove(to_criteria(:_id => ids.flatten))
132
+ end
133
+
134
+ def delete_all(options={})
135
+ collection.remove(to_criteria(options))
136
+ end
137
+
138
+ def destroy(*ids)
139
+ find_some!(ids.flatten).each(&:destroy)
140
+ end
141
+
142
+ def destroy_all(options={})
143
+ find_each(options) { |document| document.destroy }
144
+ end
145
+
146
+ def increment(*args)
147
+ modifier_update('$inc', args)
148
+ end
149
+
150
+ def decrement(*args)
151
+ criteria, keys = criteria_and_keys_from_args(args)
152
+ values, to_decrement = keys.values, {}
153
+ keys.keys.each_with_index { |k, i| to_decrement[k] = -values[i].abs }
154
+ collection.update(criteria, {'$inc' => to_decrement}, :multi => true)
155
+ end
156
+
157
+ def set(*args)
158
+ modifier_update('$set', args)
159
+ end
160
+
161
+ def push(*args)
162
+ modifier_update('$push', args)
163
+ end
164
+
165
+ def push_all(*args)
166
+ modifier_update('$pushAll', args)
167
+ end
168
+
169
+ def push_uniq(*args)
170
+ criteria, keys = criteria_and_keys_from_args(args)
171
+ keys.each { |key, value | criteria[key] = {'$ne' => value} }
172
+ collection.update(criteria, {'$push' => keys}, :multi => true)
173
+ end
174
+
175
+ def pull(*args)
176
+ modifier_update('$pull', args)
177
+ end
178
+
179
+ def pull_all(*args)
180
+ modifier_update('$pullAll', args)
181
+ end
182
+
183
+ def pop(*args)
184
+ modifier_update('$pop', args)
185
+ end
186
+
187
+ def embeddable?
188
+ false
189
+ end
190
+
191
+ def connection(mongo_connection=nil)
192
+ if mongo_connection.nil?
193
+ @connection ||= MongoMapper.connection
194
+ else
195
+ @connection = mongo_connection
196
+ end
197
+ @connection
198
+ end
199
+
200
+ def set_database_name(name)
201
+ @database_name = name
202
+ end
203
+
204
+ def database_name
205
+ @database_name
206
+ end
207
+
208
+ def database
209
+ if database_name.nil?
210
+ MongoMapper.database
211
+ else
212
+ connection.db(database_name)
213
+ end
214
+ end
215
+
216
+ def set_collection_name(name)
217
+ @collection_name = name
218
+ end
219
+
220
+ def collection_name
221
+ @collection_name ||= self.to_s.tableize.gsub(/\//, '.')
222
+ end
223
+
224
+ def collection
225
+ database.collection(collection_name)
226
+ end
227
+
228
+ def timestamps!
229
+ key :created_at, Time
230
+ key :updated_at, Time
231
+ class_eval { before_save :update_timestamps }
232
+ end
233
+
234
+ def userstamps!
235
+ key :creator_id, ObjectId
236
+ key :updater_id, ObjectId
237
+ belongs_to :creator, :class_name => 'User'
238
+ belongs_to :updater, :class_name => 'User'
239
+ end
240
+
241
+ def single_collection_inherited?
242
+ keys.key?(:_type) && single_collection_inherited_superclass?
243
+ end
244
+
245
+ def single_collection_inherited_superclass?
246
+ superclass.respond_to?(:keys) && superclass.keys.key?(:_type)
247
+ end
248
+
249
+ private
250
+ def initialize_each(*docs)
251
+ instances = []
252
+ docs = [{}] if docs.blank?
253
+ docs.flatten.each do |attrs|
254
+ doc = new(attrs)
255
+ yield(doc)
256
+ instances << doc
257
+ end
258
+ instances.size == 1 ? instances[0] : instances
259
+ end
260
+
261
+ def modifier_update(modifier, args)
262
+ criteria, keys = criteria_and_keys_from_args(args)
263
+ modifiers = {modifier => keys}
264
+ collection.update(criteria, modifiers, :multi => true)
265
+ end
266
+
267
+ def criteria_and_keys_from_args(args)
268
+ keys = args.pop
269
+ criteria = args[0].is_a?(Hash) ? args[0] : {:id => args}
270
+ [to_criteria(criteria), keys]
271
+ end
272
+
273
+ def assert_no_first_last_or_all(args)
274
+ if args[0] == :first || args[0] == :last || args[0] == :all
275
+ raise ArgumentError, "#{self}.find(:#{args}) is no longer supported, use #{self}.#{args} instead."
276
+ end
277
+ end
278
+
279
+ def find_some(ids, options={})
280
+ ids = ids.flatten.compact.uniq
281
+ find_many(options.merge(:_id => ids)).compact
282
+ end
283
+
284
+ def find_some!(ids, options={})
285
+ ids = ids.flatten.compact.uniq
286
+ documents = find_some(ids, options)
287
+
288
+ if ids.size == documents.size
289
+ documents
290
+ else
291
+ raise DocumentNotFound, "Couldn't find all of the ids (#{ids.to_sentence}). Found #{documents.size}, but was expecting #{ids.size}"
292
+ end
293
+ end
294
+
295
+ # All query methods that load documents pass through find_one or find_many
296
+ def find_one(options={})
297
+ criteria, options = to_finder_options(options)
298
+ if doc = collection.find_one(criteria, options)
299
+ load(doc)
300
+ end
301
+ end
302
+
303
+ # All query methods that load documents pass through find_one or find_many
304
+ def find_many(options)
305
+ criteria, options = to_finder_options(options)
306
+ collection.find(criteria, options).to_a.map do |doc|
307
+ load(doc)
308
+ end
309
+ end
310
+
311
+ def invert_order_clause(order)
312
+ order.split(',').map do |order_segment|
313
+ if order_segment =~ /\sasc/i
314
+ order_segment.sub /\sasc/i, ' desc'
315
+ elsif order_segment =~ /\sdesc/i
316
+ order_segment.sub /\sdesc/i, ' asc'
317
+ else
318
+ "#{order_segment.strip} desc"
319
+ end
320
+ end.join(',')
321
+ end
322
+
323
+ def update_single(id, attrs)
324
+ if id.blank? || attrs.blank? || !attrs.is_a?(Hash)
325
+ raise ArgumentError, "Updating a single document requires an id and a hash of attributes"
326
+ end
327
+
328
+ doc = find(id)
329
+ doc.update_attributes(attrs)
330
+ doc
331
+ end
332
+
333
+ def update_multiple(docs)
334
+ unless docs.is_a?(Hash)
335
+ raise ArgumentError, "Updating multiple documents takes 1 argument and it must be hash"
336
+ end
337
+
338
+ instances = []
339
+ docs.each_pair { |id, attrs| instances << update(id, attrs) }
340
+ instances
341
+ end
342
+
343
+ def to_criteria(options={})
344
+ FinderOptions.new(self, options).criteria
345
+ end
346
+
347
+ def to_finder_options(options={})
348
+ FinderOptions.new(self, options).to_a
349
+ end
350
+ end
351
+
352
+ module InstanceMethods
353
+ def collection
354
+ self.class.collection
355
+ end
356
+
357
+ def database
358
+ self.class.database
359
+ end
360
+
361
+ def save(options={})
362
+ options.assert_valid_keys(:validate, :safe)
363
+ options.reverse_merge!(:validate => true)
364
+ !options[:validate] || valid? ? create_or_update(options) : false
365
+ end
366
+
367
+ def save!(options={})
368
+ options.assert_valid_keys(:safe)
369
+ save(options) || raise(DocumentNotValid.new(self))
370
+ end
371
+
372
+ def destroy
373
+ delete
374
+ end
375
+
376
+ def delete
377
+ self.class.delete(id) unless new?
378
+ end
379
+
380
+ def reload
381
+ if attrs = collection.find_one({:_id => _id})
382
+ self.class.associations.each { |name, assoc| send(name).reset if respond_to?(name) }
383
+ self.attributes = attrs
384
+ self
385
+ else
386
+ raise DocumentNotFound, "Document match #{_id.inspect} does not exist in #{collection.name} collection"
387
+ end
388
+ end
389
+
390
+ private
391
+ def create_or_update(options={})
392
+ result = new? ? create(options) : update(options)
393
+ result != false
394
+ end
395
+
396
+ def create(options={})
397
+ save_to_collection(options)
398
+ end
399
+
400
+ def update(options={})
401
+ save_to_collection(options)
402
+ end
403
+
404
+ def save_to_collection(options={})
405
+ safe = options[:safe] || false
406
+ @new = false
407
+ collection.save(to_mongo, :safe => safe)
408
+ end
409
+
410
+ def update_timestamps
411
+ now = Time.now.utc
412
+ self[:created_at] = now if new? && !created_at?
413
+ self[:updated_at] = now
414
+ end
415
+ end
416
+ end # Document
417
+ end # MongoMapper