novelys_mongo_mapper 0.6.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. data/.gitignore +10 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +38 -0
  4. data/Rakefile +54 -0
  5. data/VERSION +1 -0
  6. data/bin/mmconsole +60 -0
  7. data/lib/mongo_mapper.rb +124 -0
  8. data/lib/mongo_mapper/descendant_appends.rb +44 -0
  9. data/lib/mongo_mapper/document.rb +423 -0
  10. data/lib/mongo_mapper/dynamic_finder.rb +74 -0
  11. data/lib/mongo_mapper/embedded_document.rb +67 -0
  12. data/lib/mongo_mapper/finder_options.rb +127 -0
  13. data/lib/mongo_mapper/plugins.rb +14 -0
  14. data/lib/mongo_mapper/plugins/associations.rb +104 -0
  15. data/lib/mongo_mapper/plugins/associations/base.rb +121 -0
  16. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +30 -0
  17. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +25 -0
  18. data/lib/mongo_mapper/plugins/associations/collection.rb +21 -0
  19. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +50 -0
  20. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +139 -0
  21. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
  22. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +117 -0
  23. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +31 -0
  24. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +23 -0
  25. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +13 -0
  26. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +68 -0
  27. data/lib/mongo_mapper/plugins/associations/proxy.rb +118 -0
  28. data/lib/mongo_mapper/plugins/callbacks.rb +76 -0
  29. data/lib/mongo_mapper/plugins/clone.rb +13 -0
  30. data/lib/mongo_mapper/plugins/descendants.rb +16 -0
  31. data/lib/mongo_mapper/plugins/dirty.rb +119 -0
  32. data/lib/mongo_mapper/plugins/equality.rb +23 -0
  33. data/lib/mongo_mapper/plugins/identity_map.rb +122 -0
  34. data/lib/mongo_mapper/plugins/inspect.rb +14 -0
  35. data/lib/mongo_mapper/plugins/keys.rb +300 -0
  36. data/lib/mongo_mapper/plugins/logger.rb +17 -0
  37. data/lib/mongo_mapper/plugins/pagination.rb +85 -0
  38. data/lib/mongo_mapper/plugins/protected.rb +41 -0
  39. data/lib/mongo_mapper/plugins/rails.rb +45 -0
  40. data/lib/mongo_mapper/plugins/serialization.rb +105 -0
  41. data/lib/mongo_mapper/plugins/validations.rb +62 -0
  42. data/lib/mongo_mapper/support.rb +214 -0
  43. data/mongo_mapper.gemspec +179 -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 +1245 -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 +139 -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 +55 -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 +125 -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 +248 -0
data/.gitignore ADDED
@@ -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.
data/README.rdoc ADDED
@@ -0,0 +1,38 @@
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
+ == Dependencies
20
+
21
+ * ActiveSupport (typically the latest version)
22
+ * Mongo Ruby Driver (mongo)
23
+ * My fork of the validatable gem (jnunemaker-validatable)
24
+
25
+ == Documentation
26
+
27
+ Documentation is lacking right now because if you can't look through the code right now and feel comfortable, this is probably too young for you to use. Wait til it stabilizes a bit more.
28
+
29
+ http://groups.google.com/group/mongomapper
30
+
31
+ == More Info
32
+
33
+ * http://www.mongodb.org/
34
+ * http://github.com/mongodb/mongo-ruby-driver/
35
+
36
+ == Copyright
37
+
38
+ Copyright (c) 2009 John Nunemaker. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,54 @@
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 = "novelys_mongo_mapper"
9
+ gem.summary = %Q{Fork of MongoMapper with Rails 3 patches}
10
+ gem.email = "nicolas.blanco@novelys.com"
11
+ gem.homepage = "http://github.com/novelys/mongomapper"
12
+ gem.authors = ["John Nunemaker", "Nicolas Blanco"]
13
+
14
+ gem.add_dependency('activesupport', '>= 2.3')
15
+ gem.add_dependency('mongo', '0.18.3')
16
+
17
+ gem.add_development_dependency('jnunemaker-matchy', '0.4.0')
18
+ gem.add_development_dependency('shoulda', '2.10.2')
19
+ gem.add_development_dependency('timecop', '0.3.1')
20
+ gem.add_development_dependency('mocha', '0.9.8')
21
+ end
22
+
23
+ Jeweler::GemcutterTasks.new
24
+
25
+ require 'rake/testtask'
26
+ Rake::TestTask.new(:test) do |test|
27
+ test.libs << 'test'
28
+ test.ruby_opts << '-rubygems'
29
+ test.pattern = 'test/**/test_*.rb'
30
+ test.verbose = true
31
+ end
32
+
33
+ namespace :test do
34
+ Rake::TestTask.new(:units) do |test|
35
+ test.libs << 'test'
36
+ test.ruby_opts << '-rubygems'
37
+ test.pattern = 'test/unit/**/test_*.rb'
38
+ test.verbose = true
39
+ end
40
+
41
+ Rake::TestTask.new(:functionals) do |test|
42
+ test.libs << 'test'
43
+ test.ruby_opts << '-rubygems'
44
+ test.pattern = 'test/functional/**/test_*.rb'
45
+ test.verbose = true
46
+ end
47
+ end
48
+
49
+ task :default => :test
50
+ task :test => :check_dependencies
51
+
52
+ YARD::Rake::YardocTask.new(:doc) do |t|
53
+ t.options = ["--legacy"] if RUBY_VERSION < "1.9.0"
54
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.6.10
data/bin/mmconsole ADDED
@@ -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,124 @@
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
+ if self.class.const_defined?(:Gem)
8
+ gem 'activesupport', '>= 2.3'
9
+ gem 'mongo', '0.18.3'
10
+ end
11
+
12
+ require 'active_support'
13
+ require 'mongo'
14
+
15
+ module MongoMapper
16
+ # generic MM error
17
+ class MongoMapperError < StandardError; end
18
+
19
+ # raised when key expected to exist but not found
20
+ class KeyNotFound < MongoMapperError; end
21
+
22
+ # raised when document expected but not found
23
+ class DocumentNotFound < MongoMapperError; end
24
+
25
+ # raised when document not valid and using !
26
+ class DocumentNotValid < MongoMapperError
27
+ def initialize(document)
28
+ super("Validation failed: #{document.errors.full_messages.join(", ")}")
29
+ end
30
+ end
31
+
32
+ # @api public
33
+ def self.connection
34
+ @@connection ||= Mongo::Connection.new
35
+ end
36
+
37
+ # @api public
38
+ def self.connection=(new_connection)
39
+ @@connection = new_connection
40
+ end
41
+
42
+ # @api public
43
+ def self.logger
44
+ connection.logger
45
+ end
46
+
47
+ # @api public
48
+ def self.database=(name)
49
+ @@database = nil
50
+ @@database_name = name
51
+ end
52
+
53
+ # @api public
54
+ def self.database
55
+ if @@database_name.blank?
56
+ raise 'You forgot to set the default database name: MongoMapper.database = "foobar"'
57
+ end
58
+
59
+ @@database ||= MongoMapper.connection.db(@@database_name)
60
+ end
61
+
62
+ # @api private
63
+ def self.ensured_indexes
64
+ @@ensured_indexes ||= []
65
+ end
66
+
67
+ # @api private
68
+ def self.ensured_indexes=(value)
69
+ @@ensured_indexes = value
70
+ end
71
+
72
+ # @api private
73
+ def self.ensure_index(klass, keys, options={})
74
+ ensured_indexes << {:klass => klass, :keys => keys, :options => options}
75
+ end
76
+
77
+ # @api public
78
+ def self.ensure_indexes!
79
+ ensured_indexes.each do |index|
80
+ unique = index[:options].delete(:unique)
81
+ index[:klass].collection.create_index(index[:keys], unique)
82
+ end
83
+ end
84
+
85
+ # @api private
86
+ def self.use_time_zone?
87
+ Time.respond_to?(:zone) && Time.zone ? true : false
88
+ end
89
+
90
+ # @api private
91
+ def self.time_class
92
+ use_time_zone? ? Time.zone : Time
93
+ end
94
+
95
+ # @api private
96
+ def self.normalize_object_id(value)
97
+ value.is_a?(String) ? Mongo::ObjectID.from_string(value) : value
98
+ end
99
+ end
100
+
101
+ require 'mongo_mapper/support'
102
+ require 'mongo_mapper/finder_options'
103
+ require 'mongo_mapper/dynamic_finder'
104
+ require 'mongo_mapper/descendant_appends'
105
+
106
+ require 'mongo_mapper/plugins'
107
+ require 'mongo_mapper/plugins/associations'
108
+ require 'mongo_mapper/plugins/callbacks'
109
+ require 'mongo_mapper/plugins/clone'
110
+ require 'mongo_mapper/plugins/descendants'
111
+ require 'mongo_mapper/plugins/dirty'
112
+ require 'mongo_mapper/plugins/equality'
113
+ require 'mongo_mapper/plugins/identity_map'
114
+ require 'mongo_mapper/plugins/inspect'
115
+ require 'mongo_mapper/plugins/keys'
116
+ require 'mongo_mapper/plugins/logger'
117
+ require 'mongo_mapper/plugins/pagination'
118
+ require 'mongo_mapper/plugins/protected'
119
+ require 'mongo_mapper/plugins/rails'
120
+ require 'mongo_mapper/plugins/serialization'
121
+ require 'mongo_mapper/plugins/validations'
122
+
123
+ require 'mongo_mapper/document'
124
+ require 'mongo_mapper/embedded_document'
@@ -0,0 +1,44 @@
1
+ module MongoMapper
2
+ module DescendantAppends
3
+ def included(model)
4
+ extra_extensions.each { |extension| model.extend(extension) }
5
+ extra_inclusions.each { |inclusion| model.send(:include, inclusion) }
6
+ descendants << model
7
+ end
8
+
9
+ # @api public
10
+ def descendants
11
+ @descendants ||= Set.new
12
+ end
13
+
14
+ # @api public
15
+ def append_extensions(*extensions)
16
+ extra_extensions.concat extensions
17
+
18
+ # Add the extension to existing descendants
19
+ descendants.each do |model|
20
+ extensions.each { |extension| model.extend(extension) }
21
+ end
22
+ end
23
+
24
+ # @api public
25
+ def append_inclusions(*inclusions)
26
+ extra_inclusions.concat inclusions
27
+
28
+ # Add the inclusion to existing descendants
29
+ descendants.each do |model|
30
+ inclusions.each { |inclusion| model.send(:include, inclusion) }
31
+ end
32
+ end
33
+
34
+ # @api private
35
+ def extra_extensions
36
+ @extra_extensions ||= []
37
+ end
38
+
39
+ # @api private
40
+ def extra_inclusions
41
+ @extra_inclusions ||= []
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,423 @@
1
+ module MongoMapper
2
+ module Document
3
+ extend DescendantAppends
4
+
5
+ def self.included(model)
6
+ model.class_eval do
7
+ include InstanceMethods
8
+ extend ClassMethods
9
+ extend Finders
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
+ find_all_first_last_error(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
+ find_all_first_last_error(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_or_create(arg)
74
+ first(arg) || create(arg)
75
+ end
76
+
77
+ def find_each(options={})
78
+ criteria, options = to_finder_options(options)
79
+ collection.find(criteria, options).each do |doc|
80
+ yield load(doc)
81
+ end
82
+ end
83
+
84
+ def find_by_id(id)
85
+ find(id)
86
+ end
87
+
88
+ def first(options={})
89
+ find_one(options)
90
+ end
91
+
92
+ def last(options={})
93
+ raise ':order option must be provided when using last' if options[:order].blank?
94
+ find_one(options.merge(:order => invert_order_clause(options[:order])))
95
+ end
96
+
97
+ def all(options={})
98
+ find_many(options)
99
+ end
100
+
101
+ def count(options={})
102
+ collection.find(to_criteria(options)).count
103
+ end
104
+
105
+ def exists?(options={})
106
+ !count(options).zero?
107
+ end
108
+
109
+ def create(*docs)
110
+ initialize_each(*docs) { |doc| doc.save }
111
+ end
112
+
113
+ def create!(*docs)
114
+ initialize_each(*docs) { |doc| doc.save! }
115
+ end
116
+
117
+ def update(*args)
118
+ if args.length == 1
119
+ update_multiple(args[0])
120
+ else
121
+ id, attributes = args
122
+ update_single(id, attributes)
123
+ end
124
+ end
125
+
126
+ def delete(*ids)
127
+ collection.remove(to_criteria(:_id => ids.flatten))
128
+ end
129
+
130
+ def delete_all(options={})
131
+ collection.remove(to_criteria(options))
132
+ end
133
+
134
+ def destroy(*ids)
135
+ find_some!(ids.flatten).each(&:destroy)
136
+ end
137
+
138
+ def destroy_all(options={})
139
+ all(options).each(&:destroy)
140
+ end
141
+
142
+ def increment(*args)
143
+ modifier_update('$inc', args)
144
+ end
145
+
146
+ def decrement(*args)
147
+ criteria, keys = criteria_and_keys_from_args(args)
148
+ values, to_decrement = keys.values, {}
149
+ keys.keys.each_with_index { |k, i| to_decrement[k] = -values[i].abs }
150
+ collection.update(criteria, {'$inc' => to_decrement}, :multi => true)
151
+ end
152
+
153
+ def set(*args)
154
+ modifier_update('$set', args)
155
+ end
156
+
157
+ def push(*args)
158
+ modifier_update('$push', args)
159
+ end
160
+
161
+ def push_all(*args)
162
+ modifier_update('$pushAll', args)
163
+ end
164
+
165
+ def push_uniq(*args)
166
+ criteria, keys = criteria_and_keys_from_args(args)
167
+ keys.each { |key, value | criteria[key] = {'$ne' => value} }
168
+ collection.update(criteria, {'$push' => keys}, :multi => true)
169
+ end
170
+
171
+ def pull(*args)
172
+ modifier_update('$pull', args)
173
+ end
174
+
175
+ def pull_all(*args)
176
+ modifier_update('$pullAll', args)
177
+ end
178
+
179
+ def pop(*args)
180
+ modifier_update('$pop', args)
181
+ end
182
+
183
+ def embeddable?
184
+ false
185
+ end
186
+
187
+ def connection(mongo_connection=nil)
188
+ if mongo_connection.nil?
189
+ @connection ||= MongoMapper.connection
190
+ else
191
+ @connection = mongo_connection
192
+ end
193
+ @connection
194
+ end
195
+
196
+ def set_database_name(name)
197
+ @database_name = name
198
+ end
199
+
200
+ def database_name
201
+ @database_name
202
+ end
203
+
204
+ def database
205
+ if database_name.nil?
206
+ MongoMapper.database
207
+ else
208
+ connection.db(database_name)
209
+ end
210
+ end
211
+
212
+ def set_collection_name(name)
213
+ @collection_name = name
214
+ end
215
+
216
+ def collection_name
217
+ @collection_name ||= self.to_s.tableize.gsub(/\//, '.')
218
+ end
219
+
220
+ def collection
221
+ database.collection(collection_name)
222
+ end
223
+
224
+ def timestamps!
225
+ key :created_at, Time
226
+ key :updated_at, Time
227
+ class_eval { before_save :update_timestamps }
228
+ end
229
+
230
+ def userstamps!
231
+ key :creator_id, ObjectId
232
+ key :updater_id, ObjectId
233
+ belongs_to :creator, :class_name => 'User'
234
+ belongs_to :updater, :class_name => 'User'
235
+ end
236
+
237
+ def single_collection_inherited?
238
+ keys.key?(:_type) && single_collection_inherited_superclass?
239
+ end
240
+
241
+ def single_collection_inherited_superclass?
242
+ superclass.respond_to?(:keys) && superclass.keys.key?(:_type)
243
+ end
244
+
245
+ private
246
+ def initialize_each(*docs)
247
+ instances = []
248
+ docs = [{}] if docs.blank?
249
+ docs.flatten.each do |attrs|
250
+ doc = new(attrs)
251
+ yield(doc)
252
+ instances << doc
253
+ end
254
+ instances.size == 1 ? instances[0] : instances
255
+ end
256
+
257
+ def modifier_update(modifier, args)
258
+ criteria, keys = criteria_and_keys_from_args(args)
259
+ modifiers = {modifier => keys}
260
+ collection.update(criteria, modifiers, :multi => true)
261
+ end
262
+
263
+ def criteria_and_keys_from_args(args)
264
+ keys = args.pop
265
+ criteria = args[0].is_a?(Hash) ? args[0] : {:id => args}
266
+ [to_criteria(criteria), keys]
267
+ end
268
+
269
+ def find_all_first_last_error(args)
270
+ if args[0] == :first || args[0] == :last || args[0] == :all
271
+ raise ArgumentError, "#{self}.find(:#{args}) is no longer supported, use #{self}.#{args} instead."
272
+ end
273
+ end
274
+
275
+ def find_some(ids, options={})
276
+ ids = ids.flatten.compact.uniq
277
+ find_many(options.merge(:_id => ids)).compact
278
+ end
279
+
280
+ def find_some!(ids, options={})
281
+ ids = ids.flatten.compact.uniq
282
+ documents = find_some(ids, options)
283
+
284
+ if ids.size == documents.size
285
+ documents
286
+ else
287
+ raise DocumentNotFound, "Couldn't find all of the ids (#{ids.to_sentence}). Found #{documents.size}, but was expecting #{ids.size}"
288
+ end
289
+ end
290
+
291
+ # All query methods that load documents pass through find_one or find_many
292
+ def find_one(options={})
293
+ criteria, options = to_finder_options(options)
294
+ if doc = collection.find_one(criteria, options)
295
+ load(doc)
296
+ end
297
+ end
298
+
299
+ # All query methods that load documents pass through find_one or find_many
300
+ def find_many(options)
301
+ criteria, options = to_finder_options(options)
302
+ collection.find(criteria, options).to_a.map do |doc|
303
+ load(doc)
304
+ end
305
+ end
306
+
307
+ def invert_order_clause(order)
308
+ order.split(',').map do |order_segment|
309
+ if order_segment =~ /\sasc/i
310
+ order_segment.sub /\sasc/i, ' desc'
311
+ elsif order_segment =~ /\sdesc/i
312
+ order_segment.sub /\sdesc/i, ' asc'
313
+ else
314
+ "#{order_segment.strip} desc"
315
+ end
316
+ end.join(',')
317
+ end
318
+
319
+ def update_single(id, attrs)
320
+ if id.blank? || attrs.blank? || !attrs.is_a?(Hash)
321
+ raise ArgumentError, "Updating a single document requires an id and a hash of attributes"
322
+ end
323
+
324
+ doc = find(id)
325
+ doc.update_attributes(attrs)
326
+ doc
327
+ end
328
+
329
+ def update_multiple(docs)
330
+ unless docs.is_a?(Hash)
331
+ raise ArgumentError, "Updating multiple documents takes 1 argument and it must be hash"
332
+ end
333
+
334
+ instances = []
335
+ docs.each_pair { |id, attrs| instances << update(id, attrs) }
336
+ instances
337
+ end
338
+
339
+ def to_criteria(options={})
340
+ FinderOptions.new(self, options).criteria
341
+ end
342
+
343
+ def to_finder_options(options={})
344
+ FinderOptions.new(self, options).to_a
345
+ end
346
+ end
347
+
348
+ module InstanceMethods
349
+ def collection
350
+ self.class.collection
351
+ end
352
+
353
+ def database
354
+ self.class.database
355
+ end
356
+
357
+ def save(options={})
358
+ options.assert_valid_keys(:validate, :safe)
359
+ options.reverse_merge!(:validate => true)
360
+ !options[:validate] || valid? ? create_or_update(options) : false
361
+ end
362
+
363
+ def save!(options={})
364
+ options.assert_valid_keys(:safe)
365
+ save(options) || raise(DocumentNotValid.new(self))
366
+ end
367
+
368
+ def update_attributes(attrs={})
369
+ self.attributes = attrs
370
+ save
371
+ end
372
+
373
+ def update_attributes!(attrs={})
374
+ self.attributes = attrs
375
+ save!
376
+ end
377
+
378
+ def destroy
379
+ delete
380
+ end
381
+
382
+ def delete
383
+ self.class.delete(id) unless new?
384
+ end
385
+
386
+ def reload
387
+ if attrs = collection.find_one({:_id => _id})
388
+ self.class.associations.each { |name, assoc| send(name).reset if respond_to?(name) }
389
+ self.attributes = attrs
390
+ self
391
+ else
392
+ raise DocumentNotFound, "Document match #{_id.inspect} does not exist in #{collection.name} collection"
393
+ end
394
+ end
395
+
396
+ private
397
+ def create_or_update(options={})
398
+ result = new? ? create(options) : update(options)
399
+ result != false
400
+ end
401
+
402
+ def create(options={})
403
+ save_to_collection(options)
404
+ end
405
+
406
+ def update(options={})
407
+ save_to_collection(options)
408
+ end
409
+
410
+ def save_to_collection(options={})
411
+ safe = options.delete(:safe) || false
412
+ @new = false
413
+ collection.save(to_mongo, :safe => safe)
414
+ end
415
+
416
+ def update_timestamps
417
+ now = Time.now.utc
418
+ self[:created_at] = now if new? && !created_at?
419
+ self[:updated_at] = now
420
+ end
421
+ end
422
+ end # Document
423
+ end # MongoMapper