mongo_mapper-unstable 2009.12.30 → 2010.1.4
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.
- data/README.rdoc +2 -17
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/lib/mongo_mapper/associations/base.rb +19 -10
- data/lib/mongo_mapper/associations/in_array_proxy.rb +137 -0
- data/lib/mongo_mapper/associations/one_proxy.rb +64 -0
- data/lib/mongo_mapper/associations/proxy.rb +7 -4
- data/lib/mongo_mapper/associations.rb +11 -3
- data/lib/mongo_mapper/callbacks.rb +30 -78
- data/lib/mongo_mapper/dirty.rb +5 -24
- data/lib/mongo_mapper/document.rb +117 -144
- data/lib/mongo_mapper/embedded_document.rb +7 -11
- data/lib/mongo_mapper/finder_options.rb +13 -21
- data/lib/mongo_mapper/mongo_mapper.rb +125 -0
- data/lib/mongo_mapper/pagination.rb +12 -1
- data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +1 -0
- data/lib/mongo_mapper/serialization.rb +2 -2
- data/lib/mongo_mapper/serializers/json_serializer.rb +2 -46
- data/lib/mongo_mapper/support.rb +2 -2
- data/lib/mongo_mapper.rb +8 -2
- data/mongo_mapper.gemspec +14 -8
- data/specs.watchr +3 -5
- data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +8 -0
- data/test/functional/associations/test_belongs_to_proxy.rb +54 -9
- data/test/functional/associations/test_in_array_proxy.rb +309 -0
- data/test/functional/associations/test_many_documents_proxy.rb +103 -53
- data/test/functional/associations/test_many_embedded_proxy.rb +4 -14
- data/test/functional/associations/test_many_polymorphic_proxy.rb +2 -1
- data/test/functional/associations/test_one_proxy.rb +149 -0
- data/test/functional/test_binary.rb +13 -4
- data/test/functional/test_callbacks.rb +1 -5
- data/test/functional/test_dirty.rb +1 -4
- data/test/functional/test_document.rb +576 -640
- data/test/functional/test_embedded_document.rb +7 -20
- data/test/functional/test_modifiers.rb +238 -0
- data/test/functional/test_pagination.rb +1 -3
- data/test/functional/test_string_id_compatibility.rb +3 -8
- data/test/functional/test_validations.rb +13 -75
- data/test/models.rb +1 -1
- data/test/support/timing.rb +1 -1
- data/test/test_helper.rb +28 -0
- data/test/unit/associations/test_base.rb +54 -13
- data/test/unit/associations/test_proxy.rb +12 -0
- data/test/unit/test_document.rb +36 -26
- data/test/unit/test_embedded_document.rb +14 -51
- data/test/unit/test_finder_options.rb +20 -7
- data/test/unit/test_key.rb +1 -4
- data/test/unit/test_pagination.rb +6 -0
- data/test/unit/test_rails_compatibility.rb +4 -1
- data/test/unit/test_serializations.rb +1 -2
- data/test/unit/test_support.rb +4 -0
- data/test/unit/test_time_zones.rb +1 -2
- data/test/unit/test_validations.rb +3 -14
- metadata +12 -6
- data/lib/mongo_mapper/observing.rb +0 -50
- data/test/unit/test_observing.rb +0 -101
| @@ -6,7 +6,6 @@ module MongoMapper | |
| 6 6 | 
             
                  model.class_eval do
         | 
| 7 7 | 
             
                    include EmbeddedDocument
         | 
| 8 8 | 
             
                    include InstanceMethods
         | 
| 9 | 
            -
                    include Observing
         | 
| 10 9 | 
             
                    include Callbacks
         | 
| 11 10 | 
             
                    include Dirty
         | 
| 12 11 | 
             
                    include RailsCompatibility::Document
         | 
| @@ -19,6 +18,9 @@ module MongoMapper | |
| 19 18 | 
             
                    end unless respond_to?(:per_page)
         | 
| 20 19 | 
             
                  end
         | 
| 21 20 |  | 
| 21 | 
            +
                  extra_extensions.each { |extension| model.extend(extension) }
         | 
| 22 | 
            +
                  extra_inclusions.each { |inclusion| model.send(:include, inclusion) }
         | 
| 23 | 
            +
             | 
| 22 24 | 
             
                  descendants << model
         | 
| 23 25 | 
             
                end
         | 
| 24 26 |  | 
| @@ -26,6 +28,34 @@ module MongoMapper | |
| 26 28 | 
             
                  @descendants ||= Set.new
         | 
| 27 29 | 
             
                end
         | 
| 28 30 |  | 
| 31 | 
            +
                def self.append_extensions(*extensions)
         | 
| 32 | 
            +
                  extra_extensions.concat extensions
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  # Add the extension to existing descendants
         | 
| 35 | 
            +
                  descendants.each do |model|
         | 
| 36 | 
            +
                    extensions.each { |extension| model.extend(extension) }
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                # @api private
         | 
| 41 | 
            +
                def self.extra_extensions
         | 
| 42 | 
            +
                  @extra_extensions ||= []
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def self.append_inclusions(*inclusions)
         | 
| 46 | 
            +
                  extra_inclusions.concat inclusions
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  # Add the inclusion to existing descendants
         | 
| 49 | 
            +
                  descendants.each do |model|
         | 
| 50 | 
            +
                    inclusions.each { |inclusion| model.send :include, inclusion }
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                # @api private
         | 
| 55 | 
            +
                def self.extra_inclusions
         | 
| 56 | 
            +
                  @extra_inclusions ||= []
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
                
         | 
| 29 59 | 
             
                module ClassMethods
         | 
| 30 60 | 
             
                  def key(*args)
         | 
| 31 61 | 
             
                    key = super
         | 
| @@ -43,18 +73,6 @@ module MongoMapper | |
| 43 73 | 
             
                    MongoMapper.ensure_index(self, keys_to_index, options)
         | 
| 44 74 | 
             
                  end
         | 
| 45 75 |  | 
| 46 | 
            -
                  # @overload find(:first, options)
         | 
| 47 | 
            -
                  #   @see Document.first
         | 
| 48 | 
            -
                  #
         | 
| 49 | 
            -
                  # @overload find(:last, options)
         | 
| 50 | 
            -
                  #   @see Document.last
         | 
| 51 | 
            -
                  #
         | 
| 52 | 
            -
                  # @overload find(:all, options)
         | 
| 53 | 
            -
                  #   @see Document.all
         | 
| 54 | 
            -
                  #
         | 
| 55 | 
            -
                  # @overload find(ids, options)
         | 
| 56 | 
            -
                  #
         | 
| 57 | 
            -
                  # @raise DocumentNotFound raised when no ID or arguments are provided
         | 
| 58 76 | 
             
                  def find!(*args)
         | 
| 59 77 | 
             
                    options = args.extract_options!
         | 
| 60 78 | 
             
                    case args.first
         | 
| @@ -91,45 +109,21 @@ module MongoMapper | |
| 91 109 | 
             
                    pagination
         | 
| 92 110 | 
             
                  end
         | 
| 93 111 |  | 
| 94 | 
            -
                  # @param [Hash] options any conditions understood by 
         | 
| 95 | 
            -
                  #   FinderOptions.to_mongo_criteria
         | 
| 96 | 
            -
                  #
         | 
| 97 | 
            -
                  # @return the first document in the ordered collection as described by 
         | 
| 98 | 
            -
                  #   +options+
         | 
| 99 | 
            -
                  #
         | 
| 100 | 
            -
                  # @see FinderOptions
         | 
| 101 112 | 
             
                  def first(options={})
         | 
| 102 113 | 
             
                    find_one(options)
         | 
| 103 114 | 
             
                  end
         | 
| 104 115 |  | 
| 105 | 
            -
                  # @param [Hash] options any conditions understood by 
         | 
| 106 | 
            -
                  #   FinderOptions.to_mongo_criteria
         | 
| 107 | 
            -
                  # @option [String] :order this *mandatory* option describes how to 
         | 
| 108 | 
            -
                  #   identify the ordering of the documents in your collection. Note that 
         | 
| 109 | 
            -
                  #   the *last* document in this collection will be selected.
         | 
| 110 | 
            -
                  #
         | 
| 111 | 
            -
                  # @return the last document in the ordered collection as described by 
         | 
| 112 | 
            -
                  #   +options+
         | 
| 113 | 
            -
                  #
         | 
| 114 | 
            -
                  # @raise Exception when no <tt>:order</tt> option has been defined
         | 
| 115 116 | 
             
                  def last(options={})
         | 
| 116 117 | 
             
                    raise ':order option must be provided when using last' if options[:order].blank?
         | 
| 117 118 | 
             
                    find_one(options.merge(:order => invert_order_clause(options[:order])))
         | 
| 118 119 | 
             
                  end
         | 
| 119 120 |  | 
| 120 | 
            -
                  # @param [Hash] options any conditions understood by 
         | 
| 121 | 
            -
                  #   FinderOptions.to_mongo_criteria
         | 
| 122 | 
            -
                  #
         | 
| 123 | 
            -
                  # @return [Array] all documents in your collection that match the 
         | 
| 124 | 
            -
                  #   provided conditions
         | 
| 125 | 
            -
                  #
         | 
| 126 | 
            -
                  # @see FinderOptions
         | 
| 127 121 | 
             
                  def all(options={})
         | 
| 128 122 | 
             
                    find_every(options)
         | 
| 129 123 | 
             
                  end
         | 
| 130 124 |  | 
| 131 125 | 
             
                  def find_by_id(id)
         | 
| 132 | 
            -
                     | 
| 126 | 
            +
                    find(id)
         | 
| 133 127 | 
             
                  end
         | 
| 134 128 |  | 
| 135 129 | 
             
                  def count(options={})
         | 
| @@ -140,52 +134,14 @@ module MongoMapper | |
| 140 134 | 
             
                    !count(options).zero?
         | 
| 141 135 | 
             
                  end
         | 
| 142 136 |  | 
| 143 | 
            -
                  # @overload create(doc_attributes)
         | 
| 144 | 
            -
                  #   Create a single new document
         | 
| 145 | 
            -
                  #   @param [Hash] doc_attributes key/value pairs to create a new 
         | 
| 146 | 
            -
                  #     document
         | 
| 147 | 
            -
                  #
         | 
| 148 | 
            -
                  # @overload create(docs_attributes)
         | 
| 149 | 
            -
                  #   Create many new documents
         | 
| 150 | 
            -
                  #   @param [Array<Hash>] provide many Hashes of key/value pairs to create 
         | 
| 151 | 
            -
                  #     multiple documents
         | 
| 152 | 
            -
                  #
         | 
| 153 | 
            -
                  # @example Creating a single document
         | 
| 154 | 
            -
                  #   MyModel.create({ :foo => "bar" })
         | 
| 155 | 
            -
                  #
         | 
| 156 | 
            -
                  # @example Creating multiple documents
         | 
| 157 | 
            -
                  #   MyModel.create([{ :foo => "bar" }, { :foo => "baz" })
         | 
| 158 | 
            -
                  #
         | 
| 159 | 
            -
                  # @return [Boolean] when a document is successfully created, +true+ will 
         | 
| 160 | 
            -
                  #   be returned. If a document fails to create, +false+ will be returned.
         | 
| 161 137 | 
             
                  def create(*docs)
         | 
| 162 138 | 
             
                    initialize_each(*docs) { |doc| doc.save }
         | 
| 163 139 | 
             
                  end
         | 
| 164 140 |  | 
| 165 | 
            -
                  # @see Document.create
         | 
| 166 | 
            -
                  #
         | 
| 167 | 
            -
                  # @raise [DocumentNotValid] raised if a document fails to create
         | 
| 168 141 | 
             
                  def create!(*docs)
         | 
| 169 142 | 
             
                    initialize_each(*docs) { |doc| doc.save! }
         | 
| 170 143 | 
             
                  end
         | 
| 171 144 |  | 
| 172 | 
            -
                  # @overload update(id, attributes)
         | 
| 173 | 
            -
                  #   Update a single document
         | 
| 174 | 
            -
                  #   @param id the ID of the document you wish to update
         | 
| 175 | 
            -
                  #   @param [Hash] attributes the key to update on the document with a new 
         | 
| 176 | 
            -
                  #     value
         | 
| 177 | 
            -
                  #
         | 
| 178 | 
            -
                  # @overload update(ids_and_attributes)
         | 
| 179 | 
            -
                  #   Update multiple documents
         | 
| 180 | 
            -
                  #   @param [Hash] ids_and_attributes each key is the ID of some document 
         | 
| 181 | 
            -
                  #     you wish to update. The value each key points toward are those 
         | 
| 182 | 
            -
                  #     applied to the target document
         | 
| 183 | 
            -
                  #
         | 
| 184 | 
            -
                  # @example Updating single document
         | 
| 185 | 
            -
                  #   Person.update(1, {:foo => 'bar'})
         | 
| 186 | 
            -
                  #
         | 
| 187 | 
            -
                  # @example Updating multiple documents at once:
         | 
| 188 | 
            -
                  #   Person.update({'1' => {:foo => 'bar'}, '2' => {:baz => 'wick'}})
         | 
| 189 145 | 
             
                  def update(*args)
         | 
| 190 146 | 
             
                    if args.length == 1
         | 
| 191 147 | 
             
                      update_multiple(args[0])
         | 
| @@ -195,10 +151,6 @@ module MongoMapper | |
| 195 151 | 
             
                    end
         | 
| 196 152 | 
             
                  end
         | 
| 197 153 |  | 
| 198 | 
            -
                  # Removes ("deletes") one or many documents from the collection. Note 
         | 
| 199 | 
            -
                  # that this will bypass any +destroy+ hooks defined by your class.
         | 
| 200 | 
            -
                  #
         | 
| 201 | 
            -
                  # @param [Array] ids the ID or IDs of the records you wish to delete
         | 
| 202 154 | 
             
                  def delete(*ids)
         | 
| 203 155 | 
             
                    collection.remove(to_criteria(:_id => ids.flatten))
         | 
| 204 156 | 
             
                  end
         | 
| @@ -207,27 +159,6 @@ module MongoMapper | |
| 207 159 | 
             
                    collection.remove(to_criteria(options))
         | 
| 208 160 | 
             
                  end
         | 
| 209 161 |  | 
| 210 | 
            -
                  # Iterates over each document found by the provided IDs and calls their 
         | 
| 211 | 
            -
                  # +destroy+ method. This has the advantage of processing your document's 
         | 
| 212 | 
            -
                  # +destroy+ call-backs.
         | 
| 213 | 
            -
                  #
         | 
| 214 | 
            -
                  # @overload destroy(id)
         | 
| 215 | 
            -
                  #   Destroy a single document by ID
         | 
| 216 | 
            -
                  #   @param id the ID of the document to destroy
         | 
| 217 | 
            -
                  #
         | 
| 218 | 
            -
                  # @overload destroy(ids)
         | 
| 219 | 
            -
                  #   Destroy many documents by their IDs
         | 
| 220 | 
            -
                  #   @param [Array] the IDs of each document you wish to destroy
         | 
| 221 | 
            -
                  #
         | 
| 222 | 
            -
                  # @example Destroying a single document
         | 
| 223 | 
            -
                  #   Person.destroy("34")
         | 
| 224 | 
            -
                  #
         | 
| 225 | 
            -
                  # @example Destroying multiple documents
         | 
| 226 | 
            -
                  #   Person.destroy("34", "45", ..., "54")
         | 
| 227 | 
            -
                  #
         | 
| 228 | 
            -
                  #   # OR...
         | 
| 229 | 
            -
                  #
         | 
| 230 | 
            -
                  #   Person.destroy(["34", "45", ..., "54"])
         | 
| 231 162 | 
             
                  def destroy(*ids)
         | 
| 232 163 | 
             
                    find_some(ids.flatten).each(&:destroy)
         | 
| 233 164 | 
             
                  end
         | 
| @@ -235,15 +166,58 @@ module MongoMapper | |
| 235 166 | 
             
                  def destroy_all(options={})
         | 
| 236 167 | 
             
                    all(options).each(&:destroy)
         | 
| 237 168 | 
             
                  end
         | 
| 169 | 
            +
                  
         | 
| 170 | 
            +
                  def increment(*args)
         | 
| 171 | 
            +
                    modifier_update('$inc', args)
         | 
| 172 | 
            +
                  end
         | 
| 173 | 
            +
                  
         | 
| 174 | 
            +
                  def decrement(*args)
         | 
| 175 | 
            +
                    criteria, keys = criteria_and_keys_from_args(args)
         | 
| 176 | 
            +
                    values, to_decrement = keys.values, {}
         | 
| 177 | 
            +
                    keys.keys.each_with_index { |k, i| to_decrement[k] = -values[i].abs }
         | 
| 178 | 
            +
                    collection.update(criteria, {'$inc' => to_decrement}, :multi => true)
         | 
| 179 | 
            +
                  end
         | 
| 180 | 
            +
                  
         | 
| 181 | 
            +
                  def set(*args)
         | 
| 182 | 
            +
                    modifier_update('$set', args)
         | 
| 183 | 
            +
                  end
         | 
| 184 | 
            +
                  
         | 
| 185 | 
            +
                  def push(*args)
         | 
| 186 | 
            +
                    modifier_update('$push', args)
         | 
| 187 | 
            +
                  end
         | 
| 188 | 
            +
                  
         | 
| 189 | 
            +
                  def push_all(*args)
         | 
| 190 | 
            +
                    modifier_update('$pushAll', args)
         | 
| 191 | 
            +
                  end
         | 
| 192 | 
            +
                  
         | 
| 193 | 
            +
                  def push_uniq(*args)
         | 
| 194 | 
            +
                    criteria, keys = criteria_and_keys_from_args(args)
         | 
| 195 | 
            +
                    keys.each { |key, value | criteria[key] = {'$ne' => value} }
         | 
| 196 | 
            +
                    collection.update(criteria, {'$push' => keys}, :multi => true)
         | 
| 197 | 
            +
                  end
         | 
| 198 | 
            +
                  
         | 
| 199 | 
            +
                  def pull(*args)
         | 
| 200 | 
            +
                    modifier_update('$pull', args)
         | 
| 201 | 
            +
                  end
         | 
| 202 | 
            +
                  
         | 
| 203 | 
            +
                  def pull_all(*args)
         | 
| 204 | 
            +
                    modifier_update('$pullAll', args)
         | 
| 205 | 
            +
                  end
         | 
| 206 | 
            +
                  
         | 
| 207 | 
            +
                  def modifier_update(modifier, args)
         | 
| 208 | 
            +
                    criteria, keys = criteria_and_keys_from_args(args)
         | 
| 209 | 
            +
                    modifiers = {modifier => keys}
         | 
| 210 | 
            +
                    collection.update(criteria, modifiers, :multi => true)
         | 
| 211 | 
            +
                  end
         | 
| 212 | 
            +
                  private :modifier_update
         | 
| 213 | 
            +
                  
         | 
| 214 | 
            +
                  def criteria_and_keys_from_args(args)
         | 
| 215 | 
            +
                    keys     = args.pop
         | 
| 216 | 
            +
                    criteria = args[0].is_a?(Hash) ? args[0] : {:id => args}
         | 
| 217 | 
            +
                    [to_criteria(criteria), keys]
         | 
| 218 | 
            +
                  end
         | 
| 219 | 
            +
                  private :criteria_and_keys_from_args
         | 
| 238 220 |  | 
| 239 | 
            -
                  # @overload connection()
         | 
| 240 | 
            -
                  #   @return [Mongo::Connection] the connection used by your document class
         | 
| 241 | 
            -
                  #
         | 
| 242 | 
            -
                  # @overload connection(mongo_connection)
         | 
| 243 | 
            -
                  #   @param [Mongo::Connection] mongo_connection a new connection for your 
         | 
| 244 | 
            -
                  #     document class to use
         | 
| 245 | 
            -
                  #   @return [Mongo::Connection] a new Mongo::Connection for yoru document 
         | 
| 246 | 
            -
                  #     class
         | 
| 247 221 | 
             
                  def connection(mongo_connection=nil)
         | 
| 248 222 | 
             
                    if mongo_connection.nil?
         | 
| 249 223 | 
             
                      @connection ||= MongoMapper.connection
         | 
| @@ -253,24 +227,14 @@ module MongoMapper | |
| 253 227 | 
             
                    @connection
         | 
| 254 228 | 
             
                  end
         | 
| 255 229 |  | 
| 256 | 
            -
                  # Changes the database name from the default to whatever you want
         | 
| 257 | 
            -
                  #
         | 
| 258 | 
            -
                  # @param [#to_s] name the new database name to use.
         | 
| 259 230 | 
             
                  def set_database_name(name)
         | 
| 260 231 | 
             
                    @database_name = name
         | 
| 261 232 | 
             
                  end
         | 
| 262 233 |  | 
| 263 | 
            -
                  # Returns the database name
         | 
| 264 | 
            -
                  #
         | 
| 265 | 
            -
                  # @return [String] the database name
         | 
| 266 234 | 
             
                  def database_name
         | 
| 267 235 | 
             
                    @database_name
         | 
| 268 236 | 
             
                  end
         | 
| 269 237 |  | 
| 270 | 
            -
                  # Returns the database the document should use. Defaults to
         | 
| 271 | 
            -
                  #   MongoMapper.database if other database is not set.
         | 
| 272 | 
            -
                  #
         | 
| 273 | 
            -
                  # @return [Mongo::DB] the mongo database instance
         | 
| 274 238 | 
             
                  def database
         | 
| 275 239 | 
             
                    if database_name.nil?
         | 
| 276 240 | 
             
                      MongoMapper.database
         | 
| @@ -279,35 +243,31 @@ module MongoMapper | |
| 279 243 | 
             
                    end
         | 
| 280 244 | 
             
                  end
         | 
| 281 245 |  | 
| 282 | 
            -
                  # Changes the collection name from the default to whatever you want
         | 
| 283 | 
            -
                  #
         | 
| 284 | 
            -
                  # @param [#to_s] name the new collection name to use.
         | 
| 285 246 | 
             
                  def set_collection_name(name)
         | 
| 286 247 | 
             
                    @collection_name = name
         | 
| 287 248 | 
             
                  end
         | 
| 288 249 |  | 
| 289 | 
            -
                  # Returns the collection name, if not set, defaults to class name tableized
         | 
| 290 | 
            -
                  #
         | 
| 291 | 
            -
                  # @return [String] the collection name, if not set, defaults to class 
         | 
| 292 | 
            -
                  #   name tableized
         | 
| 293 250 | 
             
                  def collection_name
         | 
| 294 251 | 
             
                    @collection_name ||= self.to_s.tableize.gsub(/\//, '.')
         | 
| 295 252 | 
             
                  end
         | 
| 296 253 |  | 
| 297 | 
            -
                  # @return the Mongo Ruby driver +collection+ object
         | 
| 298 254 | 
             
                  def collection
         | 
| 299 255 | 
             
                    database.collection(collection_name)
         | 
| 300 256 | 
             
                  end
         | 
| 301 | 
            -
             | 
| 302 | 
            -
                  # Defines a +created_at+ and +updated_at+ attribute (with a +Time+ 
         | 
| 303 | 
            -
                  # value) on your document. These attributes are updated by an 
         | 
| 304 | 
            -
                  # injected +update_timestamps+ +before_save+ hook.
         | 
| 257 | 
            +
             | 
| 305 258 | 
             
                  def timestamps!
         | 
| 306 259 | 
             
                    key :created_at, Time
         | 
| 307 260 | 
             
                    key :updated_at, Time
         | 
| 308 261 | 
             
                    class_eval { before_save :update_timestamps }
         | 
| 309 262 | 
             
                  end
         | 
| 310 263 |  | 
| 264 | 
            +
                  def userstamps!
         | 
| 265 | 
            +
                    key :creator_id, ObjectId
         | 
| 266 | 
            +
                    key :updater_id, ObjectId
         | 
| 267 | 
            +
                    belongs_to :creator, :class_name => 'User'
         | 
| 268 | 
            +
                    belongs_to :updater, :class_name => 'User'
         | 
| 269 | 
            +
                  end
         | 
| 270 | 
            +
             | 
| 311 271 | 
             
                  def single_collection_inherited?
         | 
| 312 272 | 
             
                    keys.has_key?('_type') && single_collection_inherited_superclass?
         | 
| 313 273 | 
             
                  end
         | 
| @@ -406,13 +366,23 @@ module MongoMapper | |
| 406 366 | 
             
                  def collection
         | 
| 407 367 | 
             
                    self.class.collection
         | 
| 408 368 | 
             
                  end
         | 
| 369 | 
            +
                  
         | 
| 370 | 
            +
                  def database
         | 
| 371 | 
            +
                    self.class.database
         | 
| 372 | 
            +
                  end
         | 
| 409 373 |  | 
| 410 374 | 
             
                  def new?
         | 
| 411 375 | 
             
                    read_attribute('_id').blank? || using_custom_id?
         | 
| 412 376 | 
             
                  end
         | 
| 413 377 |  | 
| 414 | 
            -
                  def save( | 
| 415 | 
            -
                     | 
| 378 | 
            +
                  def save(options={})
         | 
| 379 | 
            +
                    if options === false
         | 
| 380 | 
            +
                      ActiveSupport::Deprecation.warn "save with true/false is deprecated. You should now use :validate => true/false."
         | 
| 381 | 
            +
                      options = {:validate => false}
         | 
| 382 | 
            +
                    end
         | 
| 383 | 
            +
                    options.reverse_merge!(:validate => true)
         | 
| 384 | 
            +
                    perform_validations = options.delete(:validate)
         | 
| 385 | 
            +
                    !perform_validations || valid? ? create_or_update(options) : false
         | 
| 416 386 | 
             
                  end
         | 
| 417 387 |  | 
| 418 388 | 
             
                  def save!
         | 
| @@ -420,9 +390,11 @@ module MongoMapper | |
| 420 390 | 
             
                  end
         | 
| 421 391 |  | 
| 422 392 | 
             
                  def destroy
         | 
| 423 | 
            -
                     | 
| 424 | 
            -
             | 
| 425 | 
            -
             | 
| 393 | 
            +
                    self.class.delete(id) unless new?
         | 
| 394 | 
            +
                  end
         | 
| 395 | 
            +
                  
         | 
| 396 | 
            +
                  def delete
         | 
| 397 | 
            +
                    self.class.delete(id) unless new?
         | 
| 426 398 | 
             
                  end
         | 
| 427 399 |  | 
| 428 400 | 
             
                  def reload
         | 
| @@ -433,14 +405,14 @@ module MongoMapper | |
| 433 405 | 
             
                  end
         | 
| 434 406 |  | 
| 435 407 | 
             
                private
         | 
| 436 | 
            -
                  def create_or_update
         | 
| 437 | 
            -
                    result = new? ? create : update
         | 
| 408 | 
            +
                  def create_or_update(options={})
         | 
| 409 | 
            +
                    result = new? ? create(options) : update(options)
         | 
| 438 410 | 
             
                    result != false
         | 
| 439 411 | 
             
                  end
         | 
| 440 412 |  | 
| 441 | 
            -
                  def create
         | 
| 413 | 
            +
                  def create(options={})
         | 
| 442 414 | 
             
                    assign_id
         | 
| 443 | 
            -
                    save_to_collection
         | 
| 415 | 
            +
                    save_to_collection(options)
         | 
| 444 416 | 
             
                  end
         | 
| 445 417 |  | 
| 446 418 | 
             
                  def assign_id
         | 
| @@ -449,13 +421,14 @@ module MongoMapper | |
| 449 421 | 
             
                    end
         | 
| 450 422 | 
             
                  end
         | 
| 451 423 |  | 
| 452 | 
            -
                  def update
         | 
| 453 | 
            -
                    save_to_collection
         | 
| 424 | 
            +
                  def update(options={})
         | 
| 425 | 
            +
                    save_to_collection(options)
         | 
| 454 426 | 
             
                  end
         | 
| 455 427 |  | 
| 456 | 
            -
                  def save_to_collection
         | 
| 428 | 
            +
                  def save_to_collection(options={})
         | 
| 457 429 | 
             
                    clear_custom_id_flag
         | 
| 458 | 
            -
                     | 
| 430 | 
            +
                    safe = options.delete(:safe) || false
         | 
| 431 | 
            +
                    collection.save(to_mongo, :safe => safe)
         | 
| 459 432 | 
             
                  end
         | 
| 460 433 |  | 
| 461 434 | 
             
                  def update_timestamps
         | 
| @@ -281,7 +281,7 @@ module MongoMapper | |
| 281 281 | 
             
                  end
         | 
| 282 282 |  | 
| 283 283 | 
             
                  def id
         | 
| 284 | 
            -
                     | 
| 284 | 
            +
                    self[:_id]
         | 
| 285 285 | 
             
                  end
         | 
| 286 286 |  | 
| 287 287 | 
             
                  def id=(value)
         | 
| @@ -291,7 +291,7 @@ module MongoMapper | |
| 291 291 | 
             
                      @using_custom_id = true
         | 
| 292 292 | 
             
                    end
         | 
| 293 293 |  | 
| 294 | 
            -
                     | 
| 294 | 
            +
                    self[:_id] = value
         | 
| 295 295 | 
             
                  end
         | 
| 296 296 |  | 
| 297 297 | 
             
                  def using_custom_id?
         | 
| @@ -305,16 +305,12 @@ module MongoMapper | |
| 305 305 | 
             
                    "#<#{self.class} #{attributes_as_nice_string}>"
         | 
| 306 306 | 
             
                  end
         | 
| 307 307 |  | 
| 308 | 
            -
                  def save
         | 
| 309 | 
            -
                     | 
| 310 | 
            -
                      _root_document.save
         | 
| 311 | 
            -
                    end
         | 
| 308 | 
            +
                  def save(options={})
         | 
| 309 | 
            +
                    _root_document.try(:save, options)
         | 
| 312 310 | 
             
                  end
         | 
| 313 311 |  | 
| 314 | 
            -
                  def save!
         | 
| 315 | 
            -
                     | 
| 316 | 
            -
                      _root_document.save!
         | 
| 317 | 
            -
                    end
         | 
| 312 | 
            +
                  def save!(options={})
         | 
| 313 | 
            +
                    _root_document.try(:save!, options)
         | 
| 318 314 | 
             
                  end
         | 
| 319 315 |  | 
| 320 316 | 
             
                  def update_attributes(attrs={})
         | 
| @@ -355,7 +351,7 @@ module MongoMapper | |
| 355 351 | 
             
                    def read_attribute(name)
         | 
| 356 352 | 
             
                      if key = _keys[name]
         | 
| 357 353 | 
             
                        value = key.get(instance_variable_get("@#{name}"))
         | 
| 358 | 
            -
                        instance_variable_set "@#{name}", value | 
| 354 | 
            +
                        instance_variable_set "@#{name}", value
         | 
| 359 355 | 
             
                        value
         | 
| 360 356 | 
             
                      else
         | 
| 361 357 | 
             
                        raise KeyNotFound, "Could not find key: #{name.inspect}"
         | 
| @@ -1,25 +1,20 @@ | |
| 1 1 | 
             
            module MongoMapper
         | 
| 2 | 
            -
              #  | 
| 3 | 
            -
              #
         | 
| 4 | 
            -
              # == Important Note
         | 
| 5 | 
            -
              #
         | 
| 2 | 
            +
              # = Important Note
         | 
| 6 3 | 
             
              # This class is private to MongoMapper and should not be considered part of 
         | 
| 7 | 
            -
              # MongoMapper's public API. | 
| 8 | 
            -
              # useful for understanding how MongoMapper handles the parsing of finder 
         | 
| 9 | 
            -
              # conditions and options.
         | 
| 4 | 
            +
              # MongoMapper's public API.
         | 
| 10 5 | 
             
              #
         | 
| 11 6 | 
             
              class FinderOptions
         | 
| 12 7 | 
             
                OptionKeys = [:fields, :select, :skip, :offset, :limit, :sort, :order]
         | 
| 13 | 
            -
             | 
| 8 | 
            +
             | 
| 14 9 | 
             
                def self.normalized_field(field)
         | 
| 15 10 | 
             
                  field.to_s == 'id' ? :_id : field
         | 
| 16 11 | 
             
                end
         | 
| 17 | 
            -
             | 
| 12 | 
            +
             | 
| 18 13 | 
             
                def self.normalized_order_direction(direction)
         | 
| 19 14 | 
             
                  direction ||= 'ASC'
         | 
| 20 15 | 
             
                  direction.upcase == 'ASC' ? 1 : -1
         | 
| 21 16 | 
             
                end
         | 
| 22 | 
            -
             | 
| 17 | 
            +
             | 
| 23 18 | 
             
                def initialize(model, options)
         | 
| 24 19 | 
             
                  raise ArgumentError, "Options must be a hash" unless options.is_a?(Hash)
         | 
| 25 20 | 
             
                  options = options.symbolize_keys
         | 
| @@ -38,15 +33,11 @@ module MongoMapper | |
| 38 33 |  | 
| 39 34 | 
             
                  add_sci_scope
         | 
| 40 35 | 
             
                end
         | 
| 41 | 
            -
             | 
| 42 | 
            -
                # @return [Hash] Mongo compatible criteria options
         | 
| 43 | 
            -
                #
         | 
| 44 | 
            -
                # @see FinderOptions#to_mongo_criteria
         | 
| 36 | 
            +
             | 
| 45 37 | 
             
                def criteria
         | 
| 46 38 | 
             
                  to_mongo_criteria(@conditions)
         | 
| 47 39 | 
             
                end
         | 
| 48 | 
            -
             | 
| 49 | 
            -
                # @return [Hash] Mongo compatible options
         | 
| 40 | 
            +
             | 
| 50 41 | 
             
                def options
         | 
| 51 42 | 
             
                  fields = @options.delete(:fields) || @options.delete(:select)
         | 
| 52 43 | 
             
                  skip   = @options.delete(:skip)   || @options.delete(:offset) || 0
         | 
| @@ -55,8 +46,7 @@ module MongoMapper | |
| 55 46 |  | 
| 56 47 | 
             
                  {:fields => to_mongo_fields(fields), :skip => skip.to_i, :limit => limit.to_i, :sort => sort}
         | 
| 57 48 | 
             
                end
         | 
| 58 | 
            -
             | 
| 59 | 
            -
                # @return [Array<Hash>] Mongo criteria and options enclosed in an Array
         | 
| 49 | 
            +
             | 
| 60 50 | 
             
                def to_a
         | 
| 61 51 | 
             
                  [criteria, options]
         | 
| 62 52 | 
             
                end
         | 
| @@ -82,6 +72,8 @@ module MongoMapper | |
| 82 72 | 
             
                          criteria[field] = operator?(field) ? value : {'$in' => value}
         | 
| 83 73 | 
             
                        when Hash
         | 
| 84 74 | 
             
                          criteria[field] = to_mongo_criteria(value, field)
         | 
| 75 | 
            +
                        when Time
         | 
| 76 | 
            +
                          criteria[field] = value.utc
         | 
| 85 77 | 
             
                        else            
         | 
| 86 78 | 
             
                          criteria[field] = value
         | 
| 87 79 | 
             
                      end
         | 
| @@ -104,10 +96,10 @@ module MongoMapper | |
| 104 96 | 
             
                  def to_mongo_fields(fields)
         | 
| 105 97 | 
             
                    return if fields.blank?
         | 
| 106 98 |  | 
| 107 | 
            -
                    if fields. | 
| 108 | 
            -
                      fields.split(',').map { |field| field.strip }
         | 
| 109 | 
            -
                    else
         | 
| 99 | 
            +
                    if fields.respond_to?(:flatten, :compact)
         | 
| 110 100 | 
             
                      fields.flatten.compact
         | 
| 101 | 
            +
                    else
         | 
| 102 | 
            +
                      fields.split(',').map { |field| field.strip }
         | 
| 111 103 | 
             
                    end
         | 
| 112 104 | 
             
                  end
         | 
| 113 105 |  | 
| @@ -0,0 +1,125 @@ | |
| 1 | 
            +
            # if Gem is defined i'll assume you are using rubygems and lock specific versions
         | 
| 2 | 
            +
            # call me crazy but a plain old require will just get the latest version you have installed
         | 
| 3 | 
            +
            # so i want to make sure that if you are using gems you do in fact have the correct versions
         | 
| 4 | 
            +
            # if there is a better way to do this, please enlighten me!
         | 
| 5 | 
            +
            if self.class.const_defined?(:Gem)
         | 
| 6 | 
            +
              gem 'activesupport', '>= 2.3'
         | 
| 7 | 
            +
              gem 'mongo', '0.18.2'
         | 
| 8 | 
            +
              gem 'jnunemaker-validatable', '1.8.1'
         | 
| 9 | 
            +
            end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            require 'active_support'
         | 
| 12 | 
            +
            require 'mongo'
         | 
| 13 | 
            +
            require 'validatable'
         | 
| 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/callbacks'
         | 
| 103 | 
            +
            require 'mongo_mapper/finder_options'
         | 
| 104 | 
            +
            require 'mongo_mapper/dirty'
         | 
| 105 | 
            +
            require 'mongo_mapper/dynamic_finder'
         | 
| 106 | 
            +
            require 'mongo_mapper/key'
         | 
| 107 | 
            +
            require 'mongo_mapper/pagination'
         | 
| 108 | 
            +
            require 'mongo_mapper/serialization'
         | 
| 109 | 
            +
            require 'mongo_mapper/validations'
         | 
| 110 | 
            +
            require 'mongo_mapper/rails_compatibility/document'
         | 
| 111 | 
            +
            require 'mongo_mapper/rails_compatibility/embedded_document'
         | 
| 112 | 
            +
            require 'mongo_mapper/embedded_document'
         | 
| 113 | 
            +
            require 'mongo_mapper/document'
         | 
| 114 | 
            +
            require 'mongo_mapper/associations'
         | 
| 115 | 
            +
            require 'mongo_mapper/associations/base'
         | 
| 116 | 
            +
            require 'mongo_mapper/associations/proxy'
         | 
| 117 | 
            +
            require 'mongo_mapper/associations/collection'
         | 
| 118 | 
            +
            require 'mongo_mapper/associations/many_documents_proxy'
         | 
| 119 | 
            +
            require 'mongo_mapper/associations/belongs_to_proxy'
         | 
| 120 | 
            +
            require 'mongo_mapper/associations/belongs_to_polymorphic_proxy'
         | 
| 121 | 
            +
            require 'mongo_mapper/associations/many_polymorphic_proxy'
         | 
| 122 | 
            +
            require 'mongo_mapper/associations/many_embedded_proxy'
         | 
| 123 | 
            +
            require 'mongo_mapper/associations/many_embedded_polymorphic_proxy'
         | 
| 124 | 
            +
            require 'mongo_mapper/associations/many_documents_as_proxy'
         | 
| 125 | 
            +
            require 'mongo_mapper/associations/one_proxy'
         | 
| @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            module MongoMapper
         | 
| 2 2 | 
             
              module Pagination
         | 
| 3 3 | 
             
                class PaginationProxy
         | 
| 4 | 
            -
                  instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil | 
| 4 | 
            +
                  instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
         | 
| 5 5 |  | 
| 6 6 | 
             
                  attr_accessor :subject
         | 
| 7 7 | 
             
                  attr_reader :total_entries, :per_page, :current_page
         | 
| @@ -33,7 +33,18 @@ module MongoMapper | |
| 33 33 | 
             
                    (current_page - 1) * per_page
         | 
| 34 34 | 
             
                  end
         | 
| 35 35 | 
             
                  alias offset skip # for will paginate support
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  def send(method, *args, &block)
         | 
| 38 | 
            +
                    if respond_to?(method)
         | 
| 39 | 
            +
                      super
         | 
| 40 | 
            +
                    else
         | 
| 41 | 
            +
                      subject.send(method, *args, &block)
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
                  end
         | 
| 36 44 |  | 
| 45 | 
            +
                  def ===(other)
         | 
| 46 | 
            +
                    other === subject
         | 
| 47 | 
            +
                  end
         | 
| 37 48 |  | 
| 38 49 | 
             
                  def method_missing(name, *args, &block)
         | 
| 39 50 | 
             
                    @subject.send(name, *args, &block)
         |