numon 0.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 (99) hide show
  1. data/.gitignore +10 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +31 -0
  4. data/Rakefile +52 -0
  5. data/bin/mmconsole +60 -0
  6. data/lib/mongo_mapper.rb +138 -0
  7. data/lib/mongo_mapper/document.rb +359 -0
  8. data/lib/mongo_mapper/embedded_document.rb +61 -0
  9. data/lib/mongo_mapper/plugins.rb +34 -0
  10. data/lib/mongo_mapper/plugins/associations.rb +105 -0
  11. data/lib/mongo_mapper/plugins/associations/base.rb +123 -0
  12. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +30 -0
  13. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +25 -0
  14. data/lib/mongo_mapper/plugins/associations/collection.rb +21 -0
  15. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +39 -0
  16. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +144 -0
  17. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
  18. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +120 -0
  19. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +31 -0
  20. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +23 -0
  21. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +13 -0
  22. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +68 -0
  23. data/lib/mongo_mapper/plugins/associations/proxy.rb +118 -0
  24. data/lib/mongo_mapper/plugins/callbacks.rb +234 -0
  25. data/lib/mongo_mapper/plugins/clone.rb +13 -0
  26. data/lib/mongo_mapper/plugins/descendants.rb +16 -0
  27. data/lib/mongo_mapper/plugins/dirty.rb +119 -0
  28. data/lib/mongo_mapper/plugins/equality.rb +23 -0
  29. data/lib/mongo_mapper/plugins/identity_map.rb +122 -0
  30. data/lib/mongo_mapper/plugins/inspect.rb +14 -0
  31. data/lib/mongo_mapper/plugins/keys.rb +336 -0
  32. data/lib/mongo_mapper/plugins/logger.rb +17 -0
  33. data/lib/mongo_mapper/plugins/modifiers.rb +87 -0
  34. data/lib/mongo_mapper/plugins/pagination.rb +24 -0
  35. data/lib/mongo_mapper/plugins/pagination/proxy.rb +72 -0
  36. data/lib/mongo_mapper/plugins/protected.rb +45 -0
  37. data/lib/mongo_mapper/plugins/rails.rb +53 -0
  38. data/lib/mongo_mapper/plugins/serialization.rb +75 -0
  39. data/lib/mongo_mapper/plugins/timestamps.rb +21 -0
  40. data/lib/mongo_mapper/plugins/userstamps.rb +14 -0
  41. data/lib/mongo_mapper/plugins/validations.rb +46 -0
  42. data/lib/mongo_mapper/query.rb +130 -0
  43. data/lib/mongo_mapper/support.rb +216 -0
  44. data/lib/mongo_mapper/support/descendant_appends.rb +46 -0
  45. data/lib/mongo_mapper/support/find.rb +77 -0
  46. data/lib/mongo_mapper/version.rb +3 -0
  47. data/numon.gemspec +207 -0
  48. data/performance/read_write.rb +52 -0
  49. data/specs.watchr +51 -0
  50. data/test/NOTE_ON_TESTING +1 -0
  51. data/test/active_model_lint_test.rb +11 -0
  52. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +63 -0
  53. data/test/functional/associations/test_belongs_to_proxy.rb +101 -0
  54. data/test/functional/associations/test_in_array_proxy.rb +325 -0
  55. data/test/functional/associations/test_many_documents_as_proxy.rb +229 -0
  56. data/test/functional/associations/test_many_documents_proxy.rb +453 -0
  57. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +176 -0
  58. data/test/functional/associations/test_many_embedded_proxy.rb +256 -0
  59. data/test/functional/associations/test_many_polymorphic_proxy.rb +302 -0
  60. data/test/functional/associations/test_one_proxy.rb +161 -0
  61. data/test/functional/test_associations.rb +44 -0
  62. data/test/functional/test_binary.rb +27 -0
  63. data/test/functional/test_callbacks.rb +151 -0
  64. data/test/functional/test_dirty.rb +163 -0
  65. data/test/functional/test_document.rb +1165 -0
  66. data/test/functional/test_embedded_document.rb +130 -0
  67. data/test/functional/test_identity_map.rb +508 -0
  68. data/test/functional/test_indexing.rb +44 -0
  69. data/test/functional/test_logger.rb +20 -0
  70. data/test/functional/test_modifiers.rb +322 -0
  71. data/test/functional/test_pagination.rb +93 -0
  72. data/test/functional/test_protected.rb +161 -0
  73. data/test/functional/test_string_id_compatibility.rb +67 -0
  74. data/test/functional/test_timestamps.rb +64 -0
  75. data/test/functional/test_userstamps.rb +28 -0
  76. data/test/functional/test_validations.rb +329 -0
  77. data/test/models.rb +232 -0
  78. data/test/support/custom_matchers.rb +55 -0
  79. data/test/support/timing.rb +16 -0
  80. data/test/test_helper.rb +61 -0
  81. data/test/unit/associations/test_base.rb +207 -0
  82. data/test/unit/associations/test_proxy.rb +105 -0
  83. data/test/unit/serializers/test_json_serializer.rb +202 -0
  84. data/test/unit/test_descendant_appends.rb +71 -0
  85. data/test/unit/test_document.rb +231 -0
  86. data/test/unit/test_dynamic_finder.rb +123 -0
  87. data/test/unit/test_embedded_document.rb +663 -0
  88. data/test/unit/test_keys.rb +173 -0
  89. data/test/unit/test_mongo_mapper.rb +155 -0
  90. data/test/unit/test_pagination.rb +160 -0
  91. data/test/unit/test_plugins.rb +50 -0
  92. data/test/unit/test_query.rb +340 -0
  93. data/test/unit/test_rails.rb +123 -0
  94. data/test/unit/test_rails_compatibility.rb +52 -0
  95. data/test/unit/test_serialization.rb +51 -0
  96. data/test/unit/test_support.rb +366 -0
  97. data/test/unit/test_time_zones.rb +39 -0
  98. data/test/unit/test_validations.rb +544 -0
  99. metadata +305 -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,31 @@
1
+ = MongoMapper
2
+
3
+ A Ruby Object Mapper for 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
+ There is no need to request to join the Pivotal Tracker project as I am only granting access to a select few (easier to keep things organized). If you have a problem, please use the mailing list. If I confirm it to be a bug, I am happy to add it to PT. Thanks!
28
+
29
+ == Copyright
30
+
31
+ Copyright (c) 2009 John Nunemaker. See LICENSE for details.
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'jeweler'
4
+
5
+ require File.dirname(__FILE__) + '/lib/mongo_mapper/version'
6
+
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "mongo_mapper"
9
+ gem.summary = %Q{A Ruby Object Mapper for Mongo}
10
+ gem.email = "nunemaker@gmail.com"
11
+ gem.homepage = "http://github.com/jnunemaker/mongomapper"
12
+ gem.authors = ["John Nunemaker"]
13
+ gem.version = MongoMapper::Version
14
+
15
+ gem.add_dependency('activesupport', '>= 2.3')
16
+ gem.add_dependency('mongo', '0.19.1')
17
+ gem.add_dependency('jnunemaker-validatable', '1.8.3')
18
+
19
+ gem.add_development_dependency('jnunemaker-matchy', '0.4.0')
20
+ gem.add_development_dependency('shoulda', '2.10.2')
21
+ gem.add_development_dependency('timecop', '0.3.1')
22
+ gem.add_development_dependency('mocha', '0.9.8')
23
+ end
24
+
25
+ Jeweler::GemcutterTasks.new
26
+
27
+ require 'rake/testtask'
28
+ Rake::TestTask.new(:test) do |test|
29
+ test.libs << 'test'
30
+ test.ruby_opts << '-rubygems'
31
+ test.pattern = 'test/**/test_*.rb'
32
+ test.verbose = true
33
+ end
34
+
35
+ namespace :test do
36
+ Rake::TestTask.new(:units) do |test|
37
+ test.libs << 'test'
38
+ test.ruby_opts << '-rubygems'
39
+ test.pattern = 'test/unit/**/test_*.rb'
40
+ test.verbose = true
41
+ end
42
+
43
+ Rake::TestTask.new(:functionals) do |test|
44
+ test.libs << 'test'
45
+ test.ruby_opts << '-rubygems'
46
+ test.pattern = 'test/functional/**/test_*.rb'
47
+ test.verbose = true
48
+ end
49
+ end
50
+
51
+ task :default => :test
52
+ task :test => :check_dependencies
@@ -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,138 @@
1
+ require 'set'
2
+ require 'uri'
3
+
4
+ # if Gem is defined i'll assume you are using rubygems and lock specific versions
5
+ # call me crazy but a plain old require will just get the latest version you have installed
6
+ # so i want to make sure that if you are using gems you do in fact have the correct versions
7
+ # if there is a better way to do this, please enlighten me!
8
+ if self.class.const_defined?(:Gem)
9
+ gem 'activesupport', '>= 2.3'
10
+ gem 'mongo', '0.19.1'
11
+ gem 'jnunemaker-validatable', '1.8.3'
12
+ end
13
+
14
+ require 'active_support/all'
15
+ require 'mongo'
16
+ require 'validatable'
17
+
18
+ module MongoMapper
19
+ # generic MM error
20
+ class MongoMapperError < StandardError; end
21
+
22
+ # raised when key expected to exist but not found
23
+ class KeyNotFound < MongoMapperError; end
24
+
25
+ # raised when document expected but not found
26
+ class DocumentNotFound < MongoMapperError; end
27
+
28
+ # raised when trying to connect using uri with incorrect scheme
29
+ class InvalidScheme < MongoMapperError; end
30
+
31
+ # raised when document not valid and using !
32
+ class DocumentNotValid < MongoMapperError
33
+ def initialize(document)
34
+ super("Validation failed: #{document.errors.full_messages.join(", ")}")
35
+ end
36
+ end
37
+
38
+ # @api public
39
+ def self.connection
40
+ @@connection ||= Mongo::Connection.new
41
+ end
42
+
43
+ # @api public
44
+ def self.connection=(new_connection)
45
+ @@connection = new_connection
46
+ end
47
+
48
+ # @api public
49
+ def self.logger
50
+ connection.logger
51
+ end
52
+
53
+ # @api public
54
+ def self.database=(name)
55
+ @@database = nil
56
+ @@database_name = name
57
+ end
58
+
59
+ # @api public
60
+ def self.database
61
+ if @@database_name.blank?
62
+ raise 'You forgot to set the default database name: MongoMapper.database = "foobar"'
63
+ end
64
+
65
+ @@database ||= MongoMapper.connection.db(@@database_name)
66
+ end
67
+
68
+ def self.config=(hash)
69
+ @@config = hash
70
+ end
71
+
72
+ def self.config
73
+ raise 'Set config before connecting. MongoMapper.config = {...}' unless defined?(@@config)
74
+ @@config
75
+ end
76
+
77
+ # @api private
78
+ def self.config_for_environment(environment)
79
+ env = config[environment]
80
+ return env if env['uri'].blank?
81
+
82
+ uri = URI.parse(env['uri'])
83
+ raise InvalidScheme.new('must be mongodb') unless uri.scheme == 'mongodb'
84
+ {
85
+ 'host' => uri.host,
86
+ 'port' => uri.port,
87
+ 'database' => uri.path.gsub(/^\//, ''),
88
+ 'username' => uri.user,
89
+ 'password' => uri.password,
90
+ }
91
+ end
92
+
93
+ def self.connect(environment, options={})
94
+ raise 'Set config before connecting. MongoMapper.config = {...}' if config.blank?
95
+ env = config_for_environment(environment)
96
+ MongoMapper.connection = Mongo::Connection.new(env['host'], env['port'], options)
97
+ MongoMapper.database = env['database']
98
+ MongoMapper.database.authenticate(env['username'], env['password']) if env['username'] && env['password']
99
+ end
100
+
101
+ def self.setup(config, environment, options={})
102
+ using_passenger = options.delete(:passenger)
103
+ handle_passenger_forking if using_passenger
104
+ self.config = config
105
+ connect(environment, options)
106
+ end
107
+
108
+ def self.handle_passenger_forking
109
+ if defined?(PhusionPassenger)
110
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
111
+ connection.connect_to_master if forked
112
+ end
113
+ end
114
+ end
115
+
116
+ # @api private
117
+ def self.use_time_zone?
118
+ Time.respond_to?(:zone) && Time.zone ? true : false
119
+ end
120
+
121
+ # @api private
122
+ def self.time_class
123
+ use_time_zone? ? Time.zone : Time
124
+ end
125
+
126
+ # @api private
127
+ def self.normalize_object_id(value)
128
+ value.is_a?(String) ? Mongo::ObjectID.from_string(value) : value
129
+ end
130
+
131
+ autoload :Query, 'mongo_mapper/query'
132
+ autoload :Document, 'mongo_mapper/document'
133
+ autoload :EmbeddedDocument, 'mongo_mapper/embedded_document'
134
+ autoload :Version, 'mongo_mapper/version'
135
+ end
136
+
137
+ require 'mongo_mapper/support'
138
+ require 'mongo_mapper/plugins'
@@ -0,0 +1,359 @@
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::Modifiers
21
+ plugin Plugins::Pagination
22
+ plugin Plugins::Protected
23
+ plugin Plugins::Rails
24
+ plugin Plugins::Serialization
25
+ plugin Plugins::Timestamps
26
+ plugin Plugins::Userstamps
27
+ plugin Plugins::Validations
28
+ plugin Plugins::Callbacks # for now callbacks needs to be after validations
29
+
30
+ extend Plugins::Validations::DocumentMacros
31
+ end
32
+
33
+ super
34
+ end
35
+
36
+ module ClassMethods
37
+ def inherited(subclass)
38
+ subclass.set_collection_name(collection_name)
39
+ super
40
+ end
41
+
42
+ def ensure_index(name_or_array, options={})
43
+ keys_to_index = if name_or_array.is_a?(Array)
44
+ name_or_array.map { |pair| [pair[0], pair[1]] }
45
+ else
46
+ name_or_array
47
+ end
48
+
49
+ collection.create_index(keys_to_index, options[:unique])
50
+ end
51
+
52
+ def find(*args)
53
+ assert_no_first_last_or_all(args)
54
+ options = args.extract_options!
55
+ return nil if args.size == 0
56
+
57
+ if args.first.is_a?(Array) || args.size > 1
58
+ find_some(args, options)
59
+ else
60
+ find_one(options.merge({:_id => args[0]}))
61
+ end
62
+ end
63
+
64
+ def find!(*args)
65
+ assert_no_first_last_or_all(args)
66
+ options = args.extract_options!
67
+ raise DocumentNotFound, "Couldn't find without an ID" if args.size == 0
68
+
69
+ if args.first.is_a?(Array) || args.size > 1
70
+ find_some!(args, options)
71
+ else
72
+ find_one(options.merge({:_id => args[0]})) || raise(DocumentNotFound, "Document match #{options.inspect} does not exist in #{collection.name} collection")
73
+ end
74
+ end
75
+
76
+ def find_each(options={})
77
+ criteria, options = to_query(options)
78
+ collection.find(criteria, options).each do |doc|
79
+ yield load(doc)
80
+ end
81
+ end
82
+
83
+ def find_by_id(id)
84
+ find(id)
85
+ end
86
+
87
+ def first_or_create(arg)
88
+ first(arg) || create(arg)
89
+ end
90
+
91
+ def first_or_new(arg)
92
+ first(arg) || new(arg)
93
+ end
94
+
95
+ def first(options={})
96
+ find_one(options)
97
+ end
98
+
99
+ def last(options={})
100
+ raise ':order option must be provided when using last' if options[:order].blank?
101
+ find_one(options.merge(:order => invert_order_clause(options[:order])))
102
+ end
103
+
104
+ def all(options={})
105
+ find_many(options)
106
+ end
107
+
108
+ def count(options={})
109
+ collection.find(to_criteria(options)).count
110
+ end
111
+
112
+ def exists?(options={})
113
+ !count(options).zero?
114
+ end
115
+
116
+ def create(*docs)
117
+ initialize_each(*docs) { |doc| doc.save }
118
+ end
119
+
120
+ def create!(*docs)
121
+ initialize_each(*docs) { |doc| doc.save! }
122
+ end
123
+
124
+ def update(*args)
125
+ if args.length == 1
126
+ update_multiple(args[0])
127
+ else
128
+ id, attributes = args
129
+ update_single(id, attributes)
130
+ end
131
+ end
132
+
133
+ def delete(*ids)
134
+ collection.remove(to_criteria(:_id => ids.flatten))
135
+ end
136
+
137
+ def delete_all(options={})
138
+ collection.remove(to_criteria(options))
139
+ end
140
+
141
+ def destroy(*ids)
142
+ find_some!(ids.flatten).each(&:destroy)
143
+ end
144
+
145
+ def destroy_all(options={})
146
+ find_each(options) { |document| document.destroy }
147
+ end
148
+
149
+ def embeddable?
150
+ false
151
+ end
152
+
153
+ def connection(mongo_connection=nil)
154
+ if mongo_connection.nil?
155
+ @connection ||= MongoMapper.connection
156
+ else
157
+ @connection = mongo_connection
158
+ end
159
+ @connection
160
+ end
161
+
162
+ def set_database_name(name)
163
+ @database_name = name
164
+ end
165
+
166
+ def database_name
167
+ @database_name
168
+ end
169
+
170
+ def database
171
+ if database_name.nil?
172
+ MongoMapper.database
173
+ else
174
+ connection.db(database_name)
175
+ end
176
+ end
177
+
178
+ def set_collection_name(name)
179
+ @collection_name = name
180
+ end
181
+
182
+ def collection_name
183
+ @collection_name ||= self.to_s.tableize.gsub(/\//, '.')
184
+ end
185
+
186
+ def collection
187
+ database.collection(collection_name)
188
+ end
189
+
190
+ def single_collection_inherited?
191
+ keys.key?(:_type) && single_collection_inherited_superclass?
192
+ end
193
+
194
+ def single_collection_inherited_superclass?
195
+ superclass.respond_to?(:keys) && superclass.keys.key?(:_type)
196
+ end
197
+
198
+ private
199
+ def initialize_each(*docs)
200
+ instances = []
201
+ docs = [{}] if docs.blank?
202
+ docs.flatten.each do |attrs|
203
+ doc = new(attrs)
204
+ yield(doc)
205
+ instances << doc
206
+ end
207
+ instances.size == 1 ? instances[0] : instances
208
+ end
209
+
210
+ def assert_no_first_last_or_all(args)
211
+ if args[0] == :first || args[0] == :last || args[0] == :all
212
+ raise ArgumentError, "#{self}.find(:#{args}) is no longer supported, use #{self}.#{args} instead."
213
+ end
214
+ end
215
+
216
+ def find_some(ids, options={})
217
+ ids = ids.flatten.compact.uniq
218
+ find_many(options.merge(:_id => ids)).compact
219
+ end
220
+
221
+ def find_some!(ids, options={})
222
+ ids = ids.flatten.compact.uniq
223
+ documents = find_some(ids, options)
224
+
225
+ if ids.size == documents.size
226
+ documents
227
+ else
228
+ raise DocumentNotFound, "Couldn't find all of the ids (#{ids.to_sentence}). Found #{documents.size}, but was expecting #{ids.size}"
229
+ end
230
+ end
231
+
232
+ # All query methods that load documents pass through find_one or find_many
233
+ def find_one(options={})
234
+ criteria, options = to_query(options)
235
+ if doc = collection.find_one(criteria, options)
236
+ load(doc)
237
+ end
238
+ end
239
+
240
+ # All query methods that load documents pass through find_one or find_many
241
+ def find_many(options)
242
+ criteria, options = to_query(options)
243
+ collection.find(criteria, options).to_a.map do |doc|
244
+ load(doc)
245
+ end
246
+ end
247
+
248
+ def invert_order_clause(order)
249
+ order.split(',').map do |order_segment|
250
+ if order_segment =~ /\sasc/i
251
+ order_segment.sub /\sasc/i, ' desc'
252
+ elsif order_segment =~ /\sdesc/i
253
+ order_segment.sub /\sdesc/i, ' asc'
254
+ else
255
+ "#{order_segment.strip} desc"
256
+ end
257
+ end.join(',')
258
+ end
259
+
260
+ def update_single(id, attrs)
261
+ if id.blank? || attrs.blank? || !attrs.is_a?(Hash)
262
+ raise ArgumentError, "Updating a single document requires an id and a hash of attributes"
263
+ end
264
+
265
+ doc = find(id)
266
+ doc.update_attributes(attrs)
267
+ doc
268
+ end
269
+
270
+ def update_multiple(docs)
271
+ unless docs.is_a?(Hash)
272
+ raise ArgumentError, "Updating multiple documents takes 1 argument and it must be hash"
273
+ end
274
+
275
+ instances = []
276
+ docs.each_pair { |id, attrs| instances << update(id, attrs) }
277
+ instances
278
+ end
279
+
280
+ def to_criteria(options={})
281
+ Query.new(self, options).criteria
282
+ end
283
+
284
+ def to_query(options={})
285
+ Query.new(self, options).to_a
286
+ end
287
+ end
288
+
289
+ module InstanceMethods
290
+ def collection
291
+ self.class.collection
292
+ end
293
+
294
+ def database
295
+ self.class.database
296
+ end
297
+
298
+ def save(options={})
299
+ options.assert_valid_keys(:validate, :safe)
300
+ options.reverse_merge!(:validate => true)
301
+ !options[:validate] || valid? ? create_or_update(options) : false
302
+ end
303
+
304
+ def save!(options={})
305
+ options.assert_valid_keys(:safe)
306
+ save(options) || raise(DocumentNotValid.new(self))
307
+ end
308
+
309
+ def destroy
310
+ delete
311
+ end
312
+
313
+ def delete
314
+ @_destroyed = true
315
+ self.class.delete(id) unless new?
316
+ end
317
+
318
+ def destroyed?
319
+ @_destroyed == true
320
+ end
321
+
322
+ def reload
323
+ if attrs = collection.find_one({:_id => _id})
324
+ self.class.associations.each { |name, assoc| send(name).reset if respond_to?(name) }
325
+ self.attributes = attrs
326
+ self
327
+ else
328
+ raise DocumentNotFound, "Document match #{_id.inspect} does not exist in #{collection.name} collection"
329
+ end
330
+ end
331
+
332
+ # Used by embedded docs to find root easily without if/respond_to? stuff.
333
+ # Documents are always root documents.
334
+ def _root_document
335
+ self
336
+ end
337
+
338
+ private
339
+ def create_or_update(options={})
340
+ result = new? ? create(options) : update(options)
341
+ result != false
342
+ end
343
+
344
+ def create(options={})
345
+ save_to_collection(options)
346
+ end
347
+
348
+ def update(options={})
349
+ save_to_collection(options)
350
+ end
351
+
352
+ def save_to_collection(options={})
353
+ safe = options[:safe] || false
354
+ @new = false
355
+ collection.save(to_mongo, :safe => safe)
356
+ end
357
+ end
358
+ end # Document
359
+ end # MongoMapper