mongo_mapper-unstable 2010.3.8 → 2010.06.23

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 (143) hide show
  1. data/README.rdoc +4 -8
  2. data/bin/mmconsole +1 -1
  3. data/examples/keys.rb +37 -0
  4. data/examples/plugins.rb +41 -0
  5. data/examples/querying.rb +35 -0
  6. data/examples/scopes.rb +52 -0
  7. data/lib/mongo_mapper/connection.rb +83 -0
  8. data/lib/mongo_mapper/document.rb +11 -329
  9. data/lib/mongo_mapper/embedded_document.rb +9 -38
  10. data/lib/mongo_mapper/exceptions.rb +30 -0
  11. data/lib/mongo_mapper/extensions/array.rb +19 -0
  12. data/lib/mongo_mapper/extensions/binary.rb +22 -0
  13. data/lib/mongo_mapper/extensions/boolean.rb +44 -0
  14. data/lib/mongo_mapper/extensions/date.rb +25 -0
  15. data/lib/mongo_mapper/extensions/float.rb +14 -0
  16. data/lib/mongo_mapper/extensions/hash.rb +14 -0
  17. data/lib/mongo_mapper/extensions/integer.rb +19 -0
  18. data/lib/mongo_mapper/extensions/kernel.rb +9 -0
  19. data/lib/mongo_mapper/extensions/nil_class.rb +18 -0
  20. data/lib/mongo_mapper/extensions/object.rb +27 -0
  21. data/lib/mongo_mapper/extensions/object_id.rb +30 -0
  22. data/lib/mongo_mapper/extensions/set.rb +20 -0
  23. data/lib/mongo_mapper/extensions/string.rb +18 -0
  24. data/lib/mongo_mapper/extensions/time.rb +29 -0
  25. data/lib/mongo_mapper/plugins/accessible.rb +44 -0
  26. data/lib/mongo_mapper/plugins/associations/base.rb +7 -6
  27. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +5 -6
  28. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +5 -6
  29. data/lib/mongo_mapper/plugins/associations/collection.rb +1 -0
  30. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +2 -1
  31. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +25 -39
  32. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +4 -4
  33. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +36 -46
  34. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +1 -0
  35. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +5 -4
  36. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +1 -0
  37. data/lib/mongo_mapper/plugins/associations/one_embedded_proxy.rb +40 -0
  38. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +7 -7
  39. data/lib/mongo_mapper/plugins/associations/proxy.rb +16 -8
  40. data/lib/mongo_mapper/plugins/associations.rb +14 -22
  41. data/lib/mongo_mapper/plugins/caching.rb +21 -0
  42. data/lib/mongo_mapper/plugins/callbacks.rb +17 -5
  43. data/lib/mongo_mapper/plugins/clone.rb +10 -4
  44. data/lib/mongo_mapper/plugins/descendants.rb +3 -2
  45. data/lib/mongo_mapper/plugins/dirty.rb +1 -0
  46. data/lib/mongo_mapper/plugins/document.rb +41 -0
  47. data/lib/mongo_mapper/{support/find.rb → plugins/dynamic_querying/dynamic_finder.rb} +3 -36
  48. data/lib/mongo_mapper/plugins/dynamic_querying.rb +43 -0
  49. data/lib/mongo_mapper/plugins/embedded_document.rb +49 -0
  50. data/lib/mongo_mapper/plugins/equality.rb +4 -10
  51. data/lib/mongo_mapper/plugins/identity_map.rb +29 -23
  52. data/lib/mongo_mapper/plugins/indexes.rb +12 -0
  53. data/lib/mongo_mapper/plugins/inspect.rb +1 -0
  54. data/lib/mongo_mapper/plugins/keys/key.rb +55 -0
  55. data/lib/mongo_mapper/plugins/keys.rb +85 -110
  56. data/lib/mongo_mapper/plugins/logger.rb +1 -0
  57. data/lib/mongo_mapper/plugins/modifiers.rb +41 -16
  58. data/lib/mongo_mapper/plugins/pagination.rb +5 -15
  59. data/lib/mongo_mapper/plugins/persistence.rb +69 -0
  60. data/lib/mongo_mapper/plugins/protected.rb +9 -1
  61. data/lib/mongo_mapper/plugins/querying/decorator.rb +46 -0
  62. data/lib/mongo_mapper/plugins/querying/plucky_methods.rb +15 -0
  63. data/lib/mongo_mapper/plugins/querying.rb +176 -0
  64. data/lib/mongo_mapper/plugins/rails.rb +6 -1
  65. data/lib/mongo_mapper/plugins/safe.rb +28 -0
  66. data/lib/mongo_mapper/plugins/sci.rb +32 -0
  67. data/lib/mongo_mapper/plugins/scopes.rb +21 -0
  68. data/lib/mongo_mapper/plugins/serialization.rb +5 -4
  69. data/lib/mongo_mapper/plugins/timestamps.rb +2 -1
  70. data/lib/mongo_mapper/plugins/userstamps.rb +1 -0
  71. data/lib/mongo_mapper/plugins/validations.rb +9 -5
  72. data/lib/mongo_mapper/plugins.rb +1 -20
  73. data/lib/mongo_mapper/support/descendant_appends.rb +5 -6
  74. data/lib/mongo_mapper/version.rb +4 -0
  75. data/lib/mongo_mapper.rb +71 -128
  76. data/test/{NOTE_ON_TESTING → _NOTE_ON_TESTING} +0 -0
  77. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +5 -5
  78. data/test/functional/associations/test_belongs_to_proxy.rb +13 -21
  79. data/test/functional/associations/test_in_array_proxy.rb +7 -9
  80. data/test/functional/associations/test_many_documents_as_proxy.rb +5 -5
  81. data/test/functional/associations/test_many_documents_proxy.rb +186 -64
  82. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +22 -22
  83. data/test/functional/associations/test_many_embedded_proxy.rb +32 -32
  84. data/test/functional/associations/test_many_polymorphic_proxy.rb +47 -47
  85. data/test/functional/associations/test_one_embedded_proxy.rb +67 -0
  86. data/test/functional/associations/test_one_proxy.rb +70 -49
  87. data/test/functional/test_accessible.rb +168 -0
  88. data/test/functional/test_associations.rb +11 -11
  89. data/test/functional/test_binary.rb +5 -5
  90. data/test/functional/test_caching.rb +76 -0
  91. data/test/functional/test_callbacks.rb +104 -34
  92. data/test/functional/test_dirty.rb +16 -16
  93. data/test/functional/test_document.rb +12 -924
  94. data/test/functional/test_dynamic_querying.rb +75 -0
  95. data/test/functional/test_embedded_document.rb +88 -8
  96. data/test/functional/test_identity_map.rb +41 -43
  97. data/test/functional/{test_indexing.rb → test_indexes.rb} +3 -5
  98. data/test/functional/test_logger.rb +1 -1
  99. data/test/functional/test_modifiers.rb +275 -181
  100. data/test/functional/test_pagination.rb +13 -15
  101. data/test/functional/test_protected.rb +25 -11
  102. data/test/functional/test_querying.rb +873 -0
  103. data/test/functional/test_safe.rb +76 -0
  104. data/test/functional/test_sci.rb +230 -0
  105. data/test/functional/test_scopes.rb +171 -0
  106. data/test/functional/test_string_id_compatibility.rb +11 -11
  107. data/test/functional/test_timestamps.rb +0 -2
  108. data/test/functional/test_userstamps.rb +0 -1
  109. data/test/functional/test_validations.rb +44 -31
  110. data/test/models.rb +18 -17
  111. data/test/{active_model_lint_test.rb → test_active_model_lint.rb} +3 -1
  112. data/test/test_helper.rb +59 -16
  113. data/test/unit/associations/test_base.rb +47 -42
  114. data/test/unit/associations/test_proxy.rb +15 -15
  115. data/test/unit/serializers/test_json_serializer.rb +29 -29
  116. data/test/unit/test_clone.rb +69 -0
  117. data/test/unit/test_descendant_appends.rb +3 -3
  118. data/test/unit/test_document.rb +49 -67
  119. data/test/unit/test_dynamic_finder.rb +53 -51
  120. data/test/unit/test_embedded_document.rb +19 -38
  121. data/test/unit/{test_support.rb → test_extensions.rb} +136 -122
  122. data/test/unit/test_key.rb +185 -0
  123. data/test/unit/test_keys.rb +29 -147
  124. data/test/unit/test_mongo_mapper.rb +3 -48
  125. data/test/unit/test_pagination.rb +1 -150
  126. data/test/unit/test_rails.rb +77 -19
  127. data/test/unit/test_rails_compatibility.rb +12 -12
  128. data/test/unit/test_serialization.rb +5 -5
  129. data/test/unit/test_time_zones.rb +9 -9
  130. data/test/unit/test_validations.rb +46 -46
  131. metadata +157 -155
  132. data/.gitignore +0 -10
  133. data/Rakefile +0 -55
  134. data/VERSION +0 -1
  135. data/lib/mongo_mapper/plugins/pagination/proxy.rb +0 -72
  136. data/lib/mongo_mapper/query.rb +0 -130
  137. data/lib/mongo_mapper/support.rb +0 -215
  138. data/mongo_mapper.gemspec +0 -196
  139. data/performance/read_write.rb +0 -52
  140. data/specs.watchr +0 -51
  141. data/test/support/custom_matchers.rb +0 -55
  142. data/test/support/timing.rb +0 -16
  143. data/test/unit/test_query.rb +0 -340
data/README.rdoc CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  A Ruby Object Mapper for Mongo.
4
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
5
  == Note on Patches/Pull Requests
8
6
 
9
7
  * Fork the project.
@@ -18,14 +16,12 @@ Releases are tagged on github and released on gemcutter. Master is pushed to whe
18
16
 
19
17
  == Problems or Questions?
20
18
 
21
- Hit up the google group.
19
+ Hit up the google group:
22
20
  http://groups.google.com/group/mongomapper
23
21
 
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!
22
+ Hop on IRC:
23
+ irc://chat.freenode.net/#mongomapper
28
24
 
29
25
  == Copyright
30
26
 
31
- Copyright (c) 2009 John Nunemaker. See LICENSE for details.
27
+ See LICENSE for details.
data/bin/mmconsole CHANGED
@@ -18,7 +18,7 @@ irb.context.evaluate("require 'irb/completion'", 0)
18
18
  irb.context.evaluate(%@
19
19
  include MongoMapper
20
20
 
21
- MongoMapper.database = "mmtest"
21
+ MongoMapper.database = "mm-test"
22
22
  $db = MongoMapper.database
23
23
 
24
24
  @, 0)
data/examples/keys.rb ADDED
@@ -0,0 +1,37 @@
1
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
2
+ require 'mongo_mapper'
3
+ require 'pp'
4
+
5
+ MongoMapper.database = 'testing'
6
+
7
+ class User
8
+ include MongoMapper::Document
9
+
10
+ key :first_name, String, :required => true
11
+ key :last_name, String, :required => true
12
+ key :token, String, :default => lambda { 'some random string' }
13
+ key :age, Integer
14
+ key :skills, Array
15
+ key :friend_ids, Array, :typecast => 'ObjectId'
16
+ timestamps!
17
+ end
18
+ User.collection.remove # empties collection
19
+
20
+ john = User.create({
21
+ :first_name => 'John',
22
+ :last_name => 'Nunemaker',
23
+ :age => 28,
24
+ :skills => ['ruby', 'mongo', 'javascript'],
25
+ })
26
+
27
+ steve = User.create({
28
+ :first_name => 'Steve',
29
+ :last_name => 'Smith',
30
+ :age => 29,
31
+ :skills => ['html', 'css', 'javascript', 'design'],
32
+ })
33
+
34
+ john.friend_ids << steve.id.to_s # will get typecast to ObjectId
35
+ john.save
36
+
37
+ pp john
@@ -0,0 +1,41 @@
1
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
2
+ require 'mongo_mapper'
3
+ require 'pp'
4
+
5
+ MongoMapper.database = 'testing'
6
+
7
+ # To create your own plugin, just create a module.
8
+ module FooPlugin
9
+
10
+ # ClassMethods module will automatically get extended
11
+ module ClassMethods
12
+ def foo
13
+ 'Foo class method!'
14
+ end
15
+ end
16
+
17
+ # InstanceMethods module will automatically get included
18
+ module InstanceMethods
19
+ def foo
20
+ 'Foo instance method!'
21
+ end
22
+ end
23
+
24
+ # If present, configure method will be called and passed the
25
+ # model as an argument. Feel free to class_eval or add keys.
26
+ # if method is not present, it doesn't call it.
27
+ def self.configure(model)
28
+ puts "Configuring #{model}..."
29
+ model.key :foo, String
30
+ end
31
+
32
+ end
33
+
34
+ class User
35
+ include MongoMapper::Document
36
+ plugin FooPlugin
37
+ end
38
+
39
+ puts User.foo
40
+ puts User.new.foo
41
+ puts User.key?(:foo)
@@ -0,0 +1,35 @@
1
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
2
+ require 'mongo_mapper'
3
+ require 'pp'
4
+
5
+ MongoMapper.database = 'testing'
6
+
7
+ class User
8
+ include MongoMapper::Document
9
+
10
+ key :name, String
11
+ key :tags, Array
12
+ end
13
+ User.collection.remove # empties collection
14
+
15
+ User.create(:name => 'John', :tags => %w[ruby mongo], :age => 28)
16
+ User.create(:name => 'Bill', :tags => %w[ruby mongo], :age => 30)
17
+ User.create(:name => 'Frank', :tags => %w[mongo], :age => 35)
18
+ User.create(:name => 'Steve', :tags => %w[html5 css3], :age => 27)
19
+
20
+ [
21
+
22
+ User.all(:name => 'John'),
23
+ User.all(:tags => %w[mongo]),
24
+ User.all(:tags.all => %w[ruby mongo]),
25
+ User.all(:age.gte => 30),
26
+
27
+ User.where(:age.gt => 27).sort(:age).all,
28
+ User.where(:age.gt => 27).sort(:age.desc).all,
29
+ User.where(:age.gt => 27).sort(:age).limit(1).all,
30
+ User.where(:age.gt => 27).sort(:age).skip(1).limit(1).all,
31
+
32
+ ].each do |result|
33
+ pp result
34
+ puts
35
+ end
@@ -0,0 +1,52 @@
1
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
2
+ require 'mongo_mapper'
3
+ require 'pp'
4
+
5
+ MongoMapper.database = 'testing'
6
+
7
+ class User
8
+ include MongoMapper::Document
9
+
10
+ # plain old vanilla scopes with fancy queries
11
+ scope :johns, where(:name => 'John')
12
+
13
+ # plain old vanilla scopes with hashes
14
+ scope :bills, :name => 'Bill'
15
+
16
+ # dynamic scopes with parameters
17
+ scope :by_name, lambda { |name| where(:name => name) }
18
+ scope :by_ages, lambda { |low, high| where(:age.gte => low, :age.lte => high) }
19
+
20
+ # Yep, even plain old methods work as long as they return a query
21
+ def self.by_tag(tag)
22
+ where(:tags => tag)
23
+ end
24
+
25
+ # You can even make a method that returns a scope
26
+ def self.twenties; by_ages(20, 29) end
27
+
28
+ key :name, String
29
+ key :tags, Array
30
+ end
31
+ User.collection.remove # empties collection
32
+
33
+ User.create(:name => 'John', :tags => %w[ruby mongo], :age => 28)
34
+ User.create(:name => 'Bill', :tags => %w[ruby mongo], :age => 30)
35
+ User.create(:name => 'Frank', :tags => %w[mongo], :age => 35)
36
+ User.create(:name => 'Steve', :tags => %w[html5 css3], :age => 27)
37
+
38
+ # simple scopes
39
+ pp User.johns.first
40
+ pp User.bills.first
41
+
42
+ # scope with arg
43
+ pp User.by_name('Frank').first
44
+
45
+ # scope with two args
46
+ pp User.by_ages(20, 29).all
47
+
48
+ # chaining class methods on scopes
49
+ pp User.by_ages(20, 40).by_tag('ruby').all
50
+
51
+ # scope made using method that returns scope
52
+ pp User.twenties.all
@@ -0,0 +1,83 @@
1
+ # encoding: UTF-8
2
+ require 'uri'
3
+
4
+ module MongoMapper
5
+ module Connection
6
+ # @api public
7
+ def connection
8
+ @@connection ||= Mongo::Connection.new
9
+ end
10
+
11
+ # @api public
12
+ def connection=(new_connection)
13
+ @@connection = new_connection
14
+ end
15
+
16
+ # @api public
17
+ def logger
18
+ connection.logger
19
+ end
20
+
21
+ # @api public
22
+ def database=(name)
23
+ @@database = nil
24
+ @@database_name = name
25
+ end
26
+
27
+ # @api public
28
+ def database
29
+ if @@database_name.blank?
30
+ raise 'You forgot to set the default database name: MongoMapper.database = "foobar"'
31
+ end
32
+
33
+ @@database ||= MongoMapper.connection.db(@@database_name)
34
+ end
35
+
36
+ def config=(hash)
37
+ @@config = hash
38
+ end
39
+
40
+ def config
41
+ raise 'Set config before connecting. MongoMapper.config = {...}' unless defined?(@@config)
42
+ @@config
43
+ end
44
+
45
+ # @api private
46
+ def config_for_environment(environment)
47
+ env = config[environment]
48
+ return env if env['uri'].blank?
49
+
50
+ uri = URI.parse(env['uri'])
51
+ raise InvalidScheme.new('must be mongodb') unless uri.scheme == 'mongodb'
52
+ {
53
+ 'host' => uri.host,
54
+ 'port' => uri.port,
55
+ 'database' => uri.path.gsub(/^\//, ''),
56
+ 'username' => uri.user,
57
+ 'password' => uri.password,
58
+ }
59
+ end
60
+
61
+ def connect(environment, options={})
62
+ raise 'Set config before connecting. MongoMapper.config = {...}' if config.blank?
63
+ env = config_for_environment(environment)
64
+ MongoMapper.connection = Mongo::Connection.new(env['host'], env['port'], options)
65
+ MongoMapper.database = env['database']
66
+ MongoMapper.database.authenticate(env['username'], env['password']) if env['username'] && env['password']
67
+ end
68
+
69
+ def setup(config, environment, options={})
70
+ handle_passenger_forking
71
+ self.config = config
72
+ connect(environment, options)
73
+ end
74
+
75
+ def handle_passenger_forking
76
+ if defined?(PhusionPassenger)
77
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
78
+ connection.connect_to_master if forked
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -1,359 +1,41 @@
1
+ # encoding: UTF-8
1
2
  module MongoMapper
2
3
  module Document
3
4
  extend Support::DescendantAppends
4
5
 
5
6
  def self.included(model)
6
7
  model.class_eval do
7
- include InstanceMethods
8
- extend Support::Find
9
- extend ClassMethods
10
8
  extend Plugins
11
9
 
10
+ plugin Plugins::Document
11
+ plugin Plugins::Querying # for now needs to be before associations (save_to_collection)
12
12
  plugin Plugins::Associations
13
+ plugin Plugins::Caching
13
14
  plugin Plugins::Clone
14
15
  plugin Plugins::Descendants
16
+ plugin Plugins::DynamicQuerying
15
17
  plugin Plugins::Equality
16
18
  plugin Plugins::Inspect
19
+ plugin Plugins::Indexes
17
20
  plugin Plugins::Keys
18
21
  plugin Plugins::Dirty # for now dirty needs to be after keys
19
22
  plugin Plugins::Logger
20
23
  plugin Plugins::Modifiers
21
24
  plugin Plugins::Pagination
25
+ plugin Plugins::Persistence
26
+ plugin Plugins::Accessible
22
27
  plugin Plugins::Protected
23
28
  plugin Plugins::Rails
29
+ plugin Plugins::Safe # needs to be after querying (save_to_collection)
30
+ plugin Plugins::Sci
31
+ plugin Plugins::Scopes
24
32
  plugin Plugins::Serialization
25
33
  plugin Plugins::Timestamps
26
34
  plugin Plugins::Userstamps
27
35
  plugin Plugins::Validations
28
36
  plugin Plugins::Callbacks # for now callbacks needs to be after validations
29
-
30
- extend Plugins::Validations::DocumentMacros
31
37
  end
32
-
33
38
  super
34
39
  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
40
  end # Document
359
41
  end # MongoMapper