mongo_mapper-rails3 0.7.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/.gitignore +10 -0
  2. data/Gemfile +15 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +60 -0
  5. data/Rakefile +58 -0
  6. data/VERSION +1 -0
  7. data/bin/mmconsole +60 -0
  8. data/lib/mongo_mapper.rb +131 -0
  9. data/lib/mongo_mapper/document.rb +439 -0
  10. data/lib/mongo_mapper/embedded_document.rb +68 -0
  11. data/lib/mongo_mapper/plugins.rb +30 -0
  12. data/lib/mongo_mapper/plugins/associations.rb +106 -0
  13. data/lib/mongo_mapper/plugins/associations/base.rb +123 -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 +141 -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 +120 -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 +119 -0
  26. data/lib/mongo_mapper/plugins/callbacks.rb +87 -0
  27. data/lib/mongo_mapper/plugins/clone.rb +14 -0
  28. data/lib/mongo_mapper/plugins/descendants.rb +17 -0
  29. data/lib/mongo_mapper/plugins/dirty.rb +120 -0
  30. data/lib/mongo_mapper/plugins/equality.rb +24 -0
  31. data/lib/mongo_mapper/plugins/identity_map.rb +124 -0
  32. data/lib/mongo_mapper/plugins/inspect.rb +15 -0
  33. data/lib/mongo_mapper/plugins/keys.rb +310 -0
  34. data/lib/mongo_mapper/plugins/logger.rb +19 -0
  35. data/lib/mongo_mapper/plugins/pagination.rb +26 -0
  36. data/lib/mongo_mapper/plugins/pagination/proxy.rb +72 -0
  37. data/lib/mongo_mapper/plugins/protected.rb +46 -0
  38. data/lib/mongo_mapper/plugins/rails.rb +46 -0
  39. data/lib/mongo_mapper/plugins/serialization.rb +50 -0
  40. data/lib/mongo_mapper/plugins/validations.rb +88 -0
  41. data/lib/mongo_mapper/query.rb +130 -0
  42. data/lib/mongo_mapper/support.rb +217 -0
  43. data/lib/mongo_mapper/support/descendant_appends.rb +46 -0
  44. data/lib/mongo_mapper/support/find.rb +77 -0
  45. data/mongo_mapper-rails3.gemspec +208 -0
  46. data/performance/read_write.rb +52 -0
  47. data/specs.watchr +51 -0
  48. data/test/NOTE_ON_TESTING +1 -0
  49. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +63 -0
  50. data/test/functional/associations/test_belongs_to_proxy.rb +101 -0
  51. data/test/functional/associations/test_in_array_proxy.rb +321 -0
  52. data/test/functional/associations/test_many_documents_as_proxy.rb +229 -0
  53. data/test/functional/associations/test_many_documents_proxy.rb +453 -0
  54. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +176 -0
  55. data/test/functional/associations/test_many_embedded_proxy.rb +256 -0
  56. data/test/functional/associations/test_many_polymorphic_proxy.rb +302 -0
  57. data/test/functional/associations/test_one_proxy.rb +161 -0
  58. data/test/functional/test_associations.rb +44 -0
  59. data/test/functional/test_binary.rb +27 -0
  60. data/test/functional/test_callbacks.rb +81 -0
  61. data/test/functional/test_dirty.rb +163 -0
  62. data/test/functional/test_document.rb +1244 -0
  63. data/test/functional/test_embedded_document.rb +125 -0
  64. data/test/functional/test_identity_map.rb +508 -0
  65. data/test/functional/test_logger.rb +20 -0
  66. data/test/functional/test_modifiers.rb +252 -0
  67. data/test/functional/test_pagination.rb +93 -0
  68. data/test/functional/test_protected.rb +161 -0
  69. data/test/functional/test_string_id_compatibility.rb +67 -0
  70. data/test/functional/test_validations.rb +329 -0
  71. data/test/models.rb +232 -0
  72. data/test/support/custom_matchers.rb +55 -0
  73. data/test/support/timing.rb +16 -0
  74. data/test/test_helper.rb +59 -0
  75. data/test/unit/associations/test_base.rb +207 -0
  76. data/test/unit/associations/test_proxy.rb +105 -0
  77. data/test/unit/serializers/test_json_serializer.rb +189 -0
  78. data/test/unit/test_descendant_appends.rb +71 -0
  79. data/test/unit/test_document.rb +231 -0
  80. data/test/unit/test_dynamic_finder.rb +123 -0
  81. data/test/unit/test_embedded_document.rb +663 -0
  82. data/test/unit/test_keys.rb +169 -0
  83. data/test/unit/test_lint.rb +8 -0
  84. data/test/unit/test_mongo_mapper.rb +125 -0
  85. data/test/unit/test_pagination.rb +160 -0
  86. data/test/unit/test_plugins.rb +51 -0
  87. data/test/unit/test_query.rb +334 -0
  88. data/test/unit/test_rails.rb +123 -0
  89. data/test/unit/test_rails_compatibility.rb +57 -0
  90. data/test/unit/test_serialization.rb +51 -0
  91. data/test/unit/test_support.rb +362 -0
  92. data/test/unit/test_time_zones.rb +39 -0
  93. data/test/unit/test_validations.rb +557 -0
  94. metadata +344 -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/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source :gemcutter
2
+
3
+ gem 'activesupport', '>= 3.0.0.beta'
4
+ gem "activemodel", ">= 3.0.0.beta"
5
+ gem "mongo", ">= 0.18.2"
6
+ gem "mongo_ext", ">= 0.18.2"
7
+ gem "yard", ">= 0.5.0"
8
+ gem "jeweler", ">= 1.4.0"
9
+
10
+ group :test do
11
+ gem 'jnunemaker-matchy', '0.4.0'
12
+ gem 'shoulda', '2.10.2'
13
+ gem 'timecop', '0.3.1'
14
+ gem 'mocha', '0.9.8'
15
+ end
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,60 @@
1
+ = MongoMapper (Rails 3 Fork)
2
+
3
+ == Updated to work on Rails3
4
+
5
+ === To Install
6
+
7
+ This project is a fork of MongoMapper that works with Rails3. To install the gem run:
8
+
9
+ gem install `mongo_mapper-rails3`
10
+
11
+ To use with Rails3 / Bundler, add this to your Gemfile:
12
+
13
+ gem "mongo_mapper-rails3", :require => "mongo_mapper"
14
+
15
+ Then run:
16
+
17
+ bundle install
18
+
19
+
20
+ If not using bundler, you can just clone it to your projects vendor/plugins folder.
21
+
22
+
23
+ === Bugs / Issues
24
+
25
+ Please report all Rails 3 bugs via the Github Issue tracker associated with this fork (merbjedi/mongomapper).
26
+
27
+ === Commit access
28
+
29
+ Want to make changes? Either fork this repo or just msg me for direct commit access to my fork.
30
+
31
+ Would greatly appreciate your help. Thanks!
32
+
33
+
34
+ == Original README
35
+
36
+ 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.
37
+
38
+ === Note on Patches/Pull Requests
39
+
40
+ * Fork the project.
41
+ * Make your feature addition or bug fix.
42
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
43
+ * 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)
44
+ * Send me a pull request. Bonus points for topic branches.
45
+
46
+ === Install
47
+
48
+ $ gem install mongo_mapper
49
+
50
+ === Problems or Questions?
51
+
52
+ Hit up the google group.
53
+ http://groups.google.com/group/mongomapper
54
+
55
+ To see if the problem you are having is a verified issue, you can see the MM pivotal tracker project:
56
+ http://www.pivotaltracker.com/projects/33576
57
+
58
+ === Copyright
59
+
60
+ Copyright (c) 2009 John Nunemaker. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,58 @@
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-rails3"
9
+ gem.summary = %Q{Rails3 / ActiveModel compatible fork of MongoMapper.}
10
+ gem.email = "merbjedi@gmail.com"
11
+ gem.homepage = "http://github.com/merbjedi/mongomapper"
12
+ gem.authors = ["Mike Harris", "Paul Bowsher", "Jacques Crocker", "John Nunemaker"]
13
+
14
+ gem.add_dependency('activesupport', '>= 3.0.0.beta')
15
+ gem.add_dependency('activemodel', '>= 3.0.0.beta')
16
+ gem.add_dependency('bundler', '>= 0.9.7')
17
+ gem.add_dependency('mongo', '>= 0.18.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
+ gem.add_development_dependency('yard', '>= 0.5.3')
24
+ gem.add_development_dependency('jeweler', '>= 1.4.0')
25
+ end
26
+
27
+ Jeweler::GemcutterTasks.new
28
+
29
+ require 'rake/testtask'
30
+ Rake::TestTask.new(:test) do |test|
31
+ test.libs << 'test'
32
+ test.ruby_opts << '-rubygems'
33
+ test.pattern = 'test/**/test_*.rb'
34
+ test.verbose = true
35
+ end
36
+
37
+ namespace :test do
38
+ Rake::TestTask.new(:units) do |test|
39
+ test.libs << 'test'
40
+ test.ruby_opts << '-rubygems'
41
+ test.pattern = 'test/unit/**/test_*.rb'
42
+ test.verbose = true
43
+ end
44
+
45
+ Rake::TestTask.new(:functionals) do |test|
46
+ test.libs << 'test'
47
+ test.ruby_opts << '-rubygems'
48
+ test.pattern = 'test/functional/**/test_*.rb'
49
+ test.verbose = true
50
+ end
51
+ end
52
+
53
+ task :default => :test
54
+ task :test => :check_dependencies
55
+
56
+ YARD::Rake::YardocTask.new(:doc) do |t|
57
+ t.options = ["--legacy"] if RUBY_VERSION < "1.9.0"
58
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.7.0.1
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,131 @@
1
+ require 'set'
2
+
3
+ begin
4
+ # Require the preresolved locked set of gems.
5
+ require File.expand_path('../.bundle/environment', __FILE__)
6
+ rescue LoadError
7
+ # Fallback on doing the resolve at runtime.
8
+ require "rubygems"
9
+ require "bundler"
10
+ Bundler.setup
11
+ end
12
+
13
+ require "active_model"
14
+ require "active_model/callbacks"
15
+ require "active_model/conversion"
16
+ require "active_model/deprecated_error_methods"
17
+ require "active_model/errors"
18
+ require "active_model/naming"
19
+ require "active_model/serialization"
20
+ require "active_model/translation"
21
+ require "active_model/validator"
22
+ require "active_model/validations"
23
+ require 'active_support/core_ext/hash/indifferent_access'
24
+ require 'active_support/core_ext/hash'
25
+ require 'active_support/core_ext/object/try'
26
+ require 'active_support/core_ext/module'
27
+
28
+ require 'mongo'
29
+
30
+ module MongoMapper
31
+ # generic MM error
32
+ class MongoMapperError < StandardError; end
33
+
34
+ # raised when key expected to exist but not found
35
+ class KeyNotFound < MongoMapperError; end
36
+
37
+ # raised when document expected but not found
38
+ class DocumentNotFound < MongoMapperError; end
39
+
40
+ # raised when document not valid and using !
41
+ class DocumentNotValid < MongoMapperError
42
+ def initialize(document)
43
+ super("Validation failed: #{document.errors.full_messages.join(", ")}")
44
+ end
45
+ end
46
+
47
+ # @api public
48
+ def self.connection
49
+ @@connection ||= Mongo::Connection.new
50
+ end
51
+
52
+ # @api public
53
+ def self.connection=(new_connection)
54
+ @@connection = new_connection
55
+ end
56
+
57
+ # @api public
58
+ def self.logger
59
+ connection.logger
60
+ end
61
+
62
+ # @api public
63
+ def self.database=(name)
64
+ @@database = nil
65
+ @@database_name = name
66
+ end
67
+
68
+ # @api public
69
+ def self.database
70
+ if @@database_name.blank?
71
+ raise 'You forgot to set the default database name: MongoMapper.database = "foobar"'
72
+ end
73
+
74
+ @@database ||= MongoMapper.connection.db(@@database_name)
75
+ end
76
+
77
+ def self.config=(hash)
78
+ @@config = hash
79
+ end
80
+
81
+ def self.config
82
+ raise 'Set config before connecting. MongoMapper.config = {...}' unless defined?(@@config)
83
+ @@config
84
+ end
85
+
86
+ def self.connect(environment, options={})
87
+ raise 'Set config before connecting. MongoMapper.config = {...}' if config.blank?
88
+ MongoMapper.connection = Mongo::Connection.new(config[environment]['host'], config[environment]['port'], options)
89
+ MongoMapper.database = config[environment]['database']
90
+ if config[environment]['username'].present? && config[environment]['password'].present?
91
+ MongoMapper.database.authenticate(config[environment]['username'], config[environment]['password'])
92
+ end
93
+ end
94
+
95
+ def self.setup(config, environment, options={})
96
+ using_passenger = options.delete(:passenger)
97
+ handle_passenger_forking if using_passenger
98
+ self.config = config
99
+ connect(environment, options)
100
+ end
101
+
102
+ def self.handle_passenger_forking
103
+ if defined?(PhusionPassenger)
104
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
105
+ connection.connect_to_master if forked
106
+ end
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
+
125
+ autoload :Query, 'mongo_mapper/query'
126
+ autoload :Document, 'mongo_mapper/document'
127
+ autoload :EmbeddedDocument, 'mongo_mapper/embedded_document'
128
+ end
129
+
130
+ require 'mongo_mapper/support'
131
+ require 'mongo_mapper/plugins'
@@ -0,0 +1,439 @@
1
+ module MongoMapper
2
+ module Document
3
+ extend Support::DescendantAppends
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include InstanceMethods
8
+ include ActiveModel::Conversion
9
+ extend Support::Find
10
+ extend ClassMethods
11
+ extend Plugins
12
+
13
+ plugin Plugins::Associations
14
+ plugin Plugins::Clone
15
+ plugin Plugins::Descendants
16
+ plugin Plugins::Equality
17
+ plugin Plugins::Inspect
18
+ plugin Plugins::Keys
19
+ plugin Plugins::Dirty # for now dirty needs to be after keys
20
+ plugin Plugins::Logger
21
+ plugin Plugins::Pagination
22
+ plugin Plugins::Protected
23
+ plugin Plugins::Rails
24
+ plugin Plugins::Serialization
25
+ plugin Plugins::Validations
26
+ plugin Plugins::Callbacks
27
+
28
+ extend Plugins::Validations::DocumentMacros
29
+ end
30
+
31
+ module ClassMethods
32
+ def inherited(subclass)
33
+ subclass.set_collection_name(collection_name)
34
+ super
35
+ end
36
+
37
+ def ensure_index(name_or_array, options={})
38
+ keys_to_index = if name_or_array.is_a?(Array)
39
+ name_or_array.map { |pair| [pair[0], pair[1]] }
40
+ else
41
+ name_or_array
42
+ end
43
+
44
+ collection.create_index(keys_to_index, options[:unique])
45
+ end
46
+
47
+ def find(*args)
48
+ assert_no_first_last_or_all(args)
49
+ options = args.extract_options!
50
+ return nil if args.size == 0
51
+
52
+ if args.first.is_a?(Array) || args.size > 1
53
+ find_some(args, options)
54
+ else
55
+ find_one(options.merge({:_id => args[0]}))
56
+ end
57
+ end
58
+
59
+ def find!(*args)
60
+ assert_no_first_last_or_all(args)
61
+ options = args.extract_options!
62
+ raise DocumentNotFound, "Couldn't find without an ID" if args.size == 0
63
+
64
+ if args.first.is_a?(Array) || args.size > 1
65
+ find_some!(args, options)
66
+ else
67
+ find_one(options.merge({:_id => args[0]})) || raise(DocumentNotFound, "Document match #{options.inspect} does not exist in #{collection.name} collection")
68
+ end
69
+ end
70
+
71
+ def find_each(options={})
72
+ criteria, options = to_query(options)
73
+ collection.find(criteria, options).each do |doc|
74
+ yield load(doc)
75
+ end
76
+ end
77
+
78
+ def find_by_id(id)
79
+ find(id)
80
+ end
81
+
82
+ def first_or_create(arg)
83
+ first(arg) || create(arg)
84
+ end
85
+
86
+ def first_or_new(arg)
87
+ first(arg) || new(arg)
88
+ end
89
+
90
+ def first(options={})
91
+ find_one(options)
92
+ end
93
+
94
+ def last(options={})
95
+ raise ':order option must be provided when using last' if options[:order].blank?
96
+ find_one(options.merge(:order => invert_order_clause(options[:order])))
97
+ end
98
+
99
+ def all(options={})
100
+ find_many(options)
101
+ end
102
+
103
+ def count(options={})
104
+ collection.find(to_criteria(options)).count
105
+ end
106
+
107
+ def exists?(options={})
108
+ !count(options).zero?
109
+ end
110
+
111
+ def create(*docs)
112
+ initialize_each(*docs) { |doc| doc.save }
113
+ end
114
+
115
+ def create!(*docs)
116
+ initialize_each(*docs) { |doc| doc.save! }
117
+ end
118
+
119
+ def update(*args)
120
+ if args.length == 1
121
+ update_multiple(args[0])
122
+ else
123
+ id, attributes = args
124
+ update_single(id, attributes)
125
+ end
126
+ end
127
+
128
+ def delete(*ids)
129
+ collection.remove(to_criteria(:_id => ids.flatten))
130
+ end
131
+
132
+ def delete_all(options={})
133
+ collection.remove(to_criteria(options))
134
+ end
135
+
136
+ def destroy(*ids)
137
+ find_some!(ids.flatten).each(&:destroy)
138
+ end
139
+
140
+ def destroy_all(options={})
141
+ find_each(options) { |document| document.destroy }
142
+ end
143
+
144
+ def increment(*args)
145
+ modifier_update('$inc', args)
146
+ end
147
+
148
+ def decrement(*args)
149
+ criteria, keys = criteria_and_keys_from_args(args)
150
+ values, to_decrement = keys.values, {}
151
+ keys.keys.each_with_index { |k, i| to_decrement[k] = -values[i].abs }
152
+ collection.update(criteria, {'$inc' => to_decrement}, :multi => true)
153
+ end
154
+
155
+ def set(*args)
156
+ modifier_update('$set', args)
157
+ end
158
+
159
+ def push(*args)
160
+ modifier_update('$push', args)
161
+ end
162
+
163
+ def push_all(*args)
164
+ modifier_update('$pushAll', args)
165
+ end
166
+
167
+ def push_uniq(*args)
168
+ criteria, keys = criteria_and_keys_from_args(args)
169
+ keys.each { |key, value | criteria[key] = {'$ne' => value} }
170
+ collection.update(criteria, {'$push' => keys}, :multi => true)
171
+ end
172
+
173
+ def pull(*args)
174
+ modifier_update('$pull', args)
175
+ end
176
+
177
+ def pull_all(*args)
178
+ modifier_update('$pullAll', args)
179
+ end
180
+
181
+ def pop(*args)
182
+ modifier_update('$pop', args)
183
+ end
184
+
185
+ def embeddable?
186
+ false
187
+ end
188
+
189
+ def connection(mongo_connection=nil)
190
+ if mongo_connection.nil?
191
+ @connection ||= MongoMapper.connection
192
+ else
193
+ @connection = mongo_connection
194
+ end
195
+ @connection
196
+ end
197
+
198
+ def set_database_name(name)
199
+ @database_name = name
200
+ end
201
+
202
+ def database_name
203
+ @database_name
204
+ end
205
+
206
+ def database
207
+ if database_name.nil?
208
+ MongoMapper.database
209
+ else
210
+ connection.db(database_name)
211
+ end
212
+ end
213
+
214
+ def set_collection_name(name)
215
+ @collection_name = name
216
+ end
217
+
218
+ def collection_name
219
+ @collection_name ||= self.to_s.tableize.gsub(/\//, '.')
220
+ end
221
+
222
+ def collection
223
+ database.collection(collection_name)
224
+ end
225
+
226
+ def timestamps!
227
+ key :created_at, Time
228
+ key :updated_at, Time
229
+ class_eval { before_save :update_timestamps }
230
+ end
231
+
232
+ def userstamps!
233
+ key :creator_id, ObjectId
234
+ key :updater_id, ObjectId
235
+ belongs_to :creator, :class_name => 'User'
236
+ belongs_to :updater, :class_name => 'User'
237
+ end
238
+
239
+ def single_collection_inherited?
240
+ keys.key?(:_type) && single_collection_inherited_superclass?
241
+ end
242
+
243
+ def single_collection_inherited_superclass?
244
+ superclass.respond_to?(:keys) && superclass.keys.key?(:_type)
245
+ end
246
+
247
+ private
248
+ def initialize_each(*docs)
249
+ instances = []
250
+ docs = [{}] if docs.blank?
251
+ docs.flatten.each do |attrs|
252
+ doc = new(attrs)
253
+ yield(doc)
254
+ instances << doc
255
+ end
256
+ instances.size == 1 ? instances[0] : instances
257
+ end
258
+
259
+ def modifier_update(modifier, args)
260
+ criteria, keys = criteria_and_keys_from_args(args)
261
+ modifiers = {modifier => keys}
262
+ collection.update(criteria, modifiers, :multi => true)
263
+ end
264
+
265
+ def criteria_and_keys_from_args(args)
266
+ keys = args.pop
267
+ criteria = args[0].is_a?(Hash) ? args[0] : {:id => args}
268
+ [to_criteria(criteria), keys]
269
+ end
270
+
271
+ def assert_no_first_last_or_all(args)
272
+ if args[0] == :first || args[0] == :last || args[0] == :all
273
+ raise ArgumentError, "#{self}.find(:#{args}) is no longer supported, use #{self}.#{args} instead."
274
+ end
275
+ end
276
+
277
+ def find_some(ids, options={})
278
+ ids = ids.flatten.compact.uniq
279
+ find_many(options.merge(:_id => ids)).compact
280
+ end
281
+
282
+ def find_some!(ids, options={})
283
+ ids = ids.flatten.compact.uniq
284
+ documents = find_some(ids, options)
285
+
286
+ if ids.size == documents.size
287
+ documents
288
+ else
289
+ raise DocumentNotFound, "Couldn't find all of the ids (#{ids.to_sentence}). Found #{documents.size}, but was expecting #{ids.size}"
290
+ end
291
+ end
292
+
293
+ # All query methods that load documents pass through find_one or find_many
294
+ def find_one(options={})
295
+ criteria, options = to_query(options)
296
+ if doc = collection.find_one(criteria, options)
297
+ load(doc)
298
+ end
299
+ end
300
+
301
+ # All query methods that load documents pass through find_one or find_many
302
+ def find_many(options)
303
+ criteria, options = to_query(options)
304
+ collection.find(criteria, options).to_a.map do |doc|
305
+ load(doc)
306
+ end
307
+ end
308
+
309
+ def invert_order_clause(order)
310
+ order.split(',').map do |order_segment|
311
+ if order_segment =~ /\sasc/i
312
+ order_segment.sub /\sasc/i, ' desc'
313
+ elsif order_segment =~ /\sdesc/i
314
+ order_segment.sub /\sdesc/i, ' asc'
315
+ else
316
+ "#{order_segment.strip} desc"
317
+ end
318
+ end.join(',')
319
+ end
320
+
321
+ def update_single(id, attrs)
322
+ if id.blank? || attrs.blank? || !attrs.is_a?(Hash)
323
+ raise ArgumentError, "Updating a single document requires an id and a hash of attributes"
324
+ end
325
+
326
+ doc = find(id)
327
+ doc.update_attributes(attrs)
328
+ doc
329
+ end
330
+
331
+ def update_multiple(docs)
332
+ unless docs.is_a?(Hash)
333
+ raise ArgumentError, "Updating multiple documents takes 1 argument and it must be hash"
334
+ end
335
+
336
+ instances = []
337
+ docs.each_pair { |id, attrs| instances << update(id, attrs) }
338
+ instances
339
+ end
340
+
341
+ def to_criteria(options={})
342
+ Query.new(self, options).criteria
343
+ end
344
+
345
+ def to_query(options={})
346
+ Query.new(self, options).to_a
347
+ end
348
+ end
349
+
350
+ module InstanceMethods
351
+ def initialize(attrs={}, from_database=false)
352
+ unless attrs.nil?
353
+ provided_keys = attrs.keys.map { |k| k.to_s }
354
+ unless provided_keys.include?('_id') || provided_keys.include?('id')
355
+ write_key :_id, Mongo::ObjectID.new
356
+ end
357
+ end
358
+
359
+ #here for ActiveModel compatibility, should do something real with this
360
+ def destroyed?
361
+ false
362
+ end
363
+
364
+ assign_type_if_present
365
+
366
+ if from_database
367
+ @new = false
368
+ self.attributes = attrs
369
+ else
370
+ @new = true
371
+ assign(attrs)
372
+ end
373
+ end
374
+
375
+ def collection
376
+ self.class.collection
377
+ end
378
+
379
+ def database
380
+ self.class.database
381
+ end
382
+
383
+ def save(options={})
384
+ options.assert_valid_keys(:validate, :safe)
385
+ options.reverse_merge!(:validate => true)
386
+ !options[:validate] || valid? ? create_or_update(options) : false
387
+ end
388
+
389
+ def save!(options={})
390
+ options.assert_valid_keys(:safe)
391
+ save(options) || raise(DocumentNotValid.new(self))
392
+ end
393
+
394
+ def destroy
395
+ delete
396
+ end
397
+
398
+ def delete
399
+ self.class.delete(id) unless new?
400
+ end
401
+
402
+ def reload
403
+ if attrs = collection.find_one({:_id => _id})
404
+ self.class.associations.each { |name, assoc| send(name).reset if respond_to?(name) }
405
+ self.attributes = attrs
406
+ self
407
+ else
408
+ raise DocumentNotFound, "Document match #{_id.inspect} does not exist in #{collection.name} collection"
409
+ end
410
+ end
411
+
412
+ private
413
+ def create_or_update(options={})
414
+ result = new? ? create(options) : update(options)
415
+ result != false
416
+ end
417
+
418
+ def create(options={})
419
+ save_to_collection(options)
420
+ end
421
+
422
+ def update(options={})
423
+ save_to_collection(options)
424
+ end
425
+
426
+ def save_to_collection(options={})
427
+ safe = options[:safe] || false
428
+ @new = false
429
+ collection.save(to_mongo, :safe => safe)
430
+ end
431
+
432
+ def update_timestamps
433
+ now = Time.now.utc
434
+ self[:created_at] = now if new? && !created_at?
435
+ self[:updated_at] = now
436
+ end
437
+ end
438
+ end # Document
439
+ end # MongoMapper