Class: MongoRecord::Base

Inherits:
Object show all
Defined in:
lib/mongo_record/base.rb

Overview

A superclass for database collection instances. The API is very similar to ActiveRecord. See #find for examples.

If you override initialize, make sure to call the superclass version, passing it the database row or hash that it was given.

Example:

class MP3Track < MongoRecord::Base
  collection_name :mp3_track
  fields :artist, :album, :song, :track
  def to_s
    "artist: #{self.artist}, album: #{self.album}, song: #{self.song}, track: #{track}"
  end
end

track = MP3Track.find_by_song('She Blinded Me With Science')
puts track.to_s

The database connection defaults to the global $db. You can set the connection using MongoRecord::Base.connection= and read it with MongoRecord::Base.connection.

# Set the connection to something besides $db
MongoRecord::Base.connection = connect('my-database')

Direct Known Subclasses

Subobject

Constant Summary collapse

@@connection =
nil

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(row = {}) {|_self| ... } ⇒ Base

Initialize a new object with either a hash of values or a row returned from the database.

Yields:

  • (_self)

Yield Parameters:



749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
# File 'lib/mongo_record/base.rb', line 749

def initialize(row={})
  case row
  when Hash
    row.each { |k, val|
      k = '_id' if k == 'id' # Rails helper
      init_ivar("@#{k}", val)
    }
  else
    row.instance_variables.each { |iv|
      init_ivar(iv, row.instance_variable_get(iv))
    }
  end
  # Default values for remaining fields
  (self.class.field_names + self.class.subobjects.keys).each { |iv|
    iv = "@#{iv}"
    instance_variable_set(iv, nil) unless instance_variable_defined?(iv)
  }
  self.class.arrays.keys.each { |iv|
    iv = "@#{iv}"
    instance_variable_set(iv, []) unless instance_variable_defined?(iv)
  }
  yield self if block_given?
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(sym, *args) ⇒ Object



889
890
891
892
893
894
895
896
# File 'lib/mongo_record/base.rb', line 889

def method_missing(sym, *args)
  if self.instance_variables.include?("@#{sym}")
    self.class.field(sym)
    return self.send(sym)
  else
    super
  end
end

Class Method Details

.all(*args) ⇒ Object



358
359
360
361
# File 'lib/mongo_record/base.rb', line 358

def all(*args)
  options = extract_options_from_args!(args)
  find_every(options)
end

.arraysObject

Return the names of all instance variables that hold objects declared using has_many. The names do not start with ‘@’.



191
# File 'lib/mongo_record/base.rb', line 191

def arrays; @arrays; end

.belongs_to(name, options = {}) ⇒ Object

Tells Mongo that this object belongs to another. A no-op.



239
240
# File 'lib/mongo_record/base.rb', line 239

def belongs_to(name, options={})
end

.collectionObject

The collection object for this class, which will be different for every subclass of MongoRecord::Base.



244
245
246
# File 'lib/mongo_record/base.rb', line 244

def collection
  connection.collection(@coll_name.to_s)
end

.collection_name(coll_name) ⇒ Object

Call this method to set the Mongo collection name for this class. The default value is the class name turned into lower_case_with_underscores.



118
119
120
121
# File 'lib/mongo_record/base.rb', line 118

def collection_name(coll_name)
  @coll_name = coll_name
  field(:_id, :_ns)
end

.connectionObject

Return the database connection. The default value is # $db.



88
89
90
91
92
# File 'lib/mongo_record/base.rb', line 88

def connection
  conn = @@connection || $db
  raise "connection not defined" unless conn
  conn
end

.connection=(val) ⇒ Object

Set the database connection. If the connection is set to nil, then $db will be used.



96
97
98
99
# File 'lib/mongo_record/base.rb', line 96

def connection=(val)
  @@connection = val
  @@connection.pk_factory = PKFactory.new unless @@connection.pk_factory
end

.count(options = {}) ⇒ Object

Returns the number of matching records.



381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'lib/mongo_record/base.rb', line 381

def count(options={})
  criteria = criteria_from(options[:conditions],options[:criteria]).merge!(where_func(options[:where]))
  begin
    collection.find(criteria).count()
  rescue => ex
    if ex.to_s =~ /Error with count command.*ns missing/
      # Return 0 because we will graciously assume that we are being
      # called from a subclass that has been initialized properly, and
      # is therefore mentioned in the schema.
      0
    else
      raise ex
    end
  end
end

.create(values_hash) ⇒ Object

Creates, saves, and returns a new database object.



436
437
438
439
440
# File 'lib/mongo_record/base.rb', line 436

def create(values_hash)
  object = self.new(values_hash)
  object.save
  object
end

.delete(id) ⇒ Object

Deletes the record with the given id from the collection.



403
404
405
# File 'lib/mongo_record/base.rb', line 403

def delete(id)
  collection.remove({:_id => id})
end

.delete_all(conditions = nil) ⇒ Object

Deletes all records that match condition, which can be a Mongo-style hash or an ActiveRecord-like hash. Examples:

Person.destroy_all "name like '%fred%'   # SQL WHERE clause
Person.destroy_all ["name = ?", 'Fred']  # Rails condition
Person.destroy_all {:name => 'Fred'}     # Mongo hash


431
432
433
# File 'lib/mongo_record/base.rb', line 431

def delete_all(conditions=nil)
  collection.remove(criteria_from(conditions))
end

.destroy(id) ⇒ Object

Load the object with id and delete it.



409
410
411
# File 'lib/mongo_record/base.rb', line 409

def destroy(id)
  id.is_a?(Array) ? id.each { |oid| destroy(oid) } : find(id).destroy
end

.destroy_all(conditions = nil) ⇒ Object

Destroy all objects that match conditions. Warning: if conditions is nil, all records in the collection will be destroyed.



422
423
424
# File 'lib/mongo_record/base.rb', line 422

def destroy_all(conditions = nil)
  find(:all, :conditions => conditions).each { |object| object.destroy }
end

.field(*fields) ⇒ Object Also known as: fields

Creates one or more collection fields. Each field will be saved to and loaded from the database. The fields named “_id” and “_ns” are automatically saved and loaded.

The method “field” is also called “fields”; you can use either one.



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/mongo_record/base.rb', line 128

def field(*fields)
  fields.each { |field|
    field = field.to_sym
    unless @field_names.include?(field)
      ivar_name = "@" + field.to_s
      define_method(field, lambda { instance_variable_get(ivar_name) })
      define_method("#{field}=".to_sym, lambda { |val| instance_variable_set(ivar_name, val) })
      define_method("#{field}?".to_sym, lambda {
                      val = instance_variable_get(ivar_name)
                      val != nil && (!val.kind_of?(String) || val != '')
                    })
      @field_names << field
    end
  }
end

.field_namesObject

Return the field names.



146
# File 'lib/mongo_record/base.rb', line 146

def field_names; @field_names; end

.find(*args) ⇒ Object



325
326
327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/mongo_record/base.rb', line 325

def find(*args)
  options = extract_options_from_args!(args)
  options.symbolize_keys!
  case args.first
  when :first
    find_initial(options)
  when :all
    find_every(options)
  when :last
    find_last(options)
  else
    find_from_ids(args, options)
  end
end

.find_by_mql(mql) ⇒ Object Also known as: find_by_sql

Returns all records matching mql. Not yet implemented.



375
376
377
# File 'lib/mongo_record/base.rb', line 375

def find_by_mql(mql)    # :nodoc:
  raise "not implemented"
end

.find_each(*args) ⇒ Object

Yields each record that was found by the find options. The find is performed by find.

Example:

Person.find_each(:conditions => "age > 21") do |person|
  person.party_all_night!
end


349
350
351
352
353
354
355
356
# File 'lib/mongo_record/base.rb', line 349

def find_each(*args)
  options = extract_options_from_args!(args)
  options.symbolize_keys!
  find_every(options).each do |record|
    yield record
  end
  self
end

.first(*args) ⇒ Object



363
364
365
366
367
# File 'lib/mongo_record/base.rb', line 363

def first(*args)
#        args = ([:first]<<args).flatten
  options = extract_options_from_args!(args)
  find_initial(options)
end

.has_and_belongs_to_many(name, options = {}) ⇒ Object

Tells Mongo that this object has and many belongs to another object. A no-op.



235
236
# File 'lib/mongo_record/base.rb', line 235

def has_and_belongs_to_many(name, options={})
end

.has_many(name, options = {}) ⇒ Object

Tells Mongo about an array of subobjects (which can be either a MongoRecord::Subobject or MongoRecord::Base instance).

Options: :class_name - Name of the class of the subobject.



221
222
223
224
225
226
227
228
229
230
231
# File 'lib/mongo_record/base.rb', line 221

def has_many(name, options={})
  name = name.to_sym
  unless @arrays[name]
    ivar_name = "@" + name.to_s
    define_method(name, lambda { instance_variable_get(ivar_name) })
    define_method("#{name}=".to_sym, lambda { |val| instance_variable_set(ivar_name, val) })
    define_method("#{name}?".to_sym, lambda { !instance_variable_get(ivar_name).empty? })
    klass_name = options[:class_name] || field_name_to_class_name(name)
    @arrays[name] = eval(klass_name)
  end
end

.has_one(name, options = {}) ⇒ Object

Tell Mongo about a subobject (which can be either a MongoRecord::Subobject or MongoRecord::Base instance).

Options: <code>:class_name<code> - Name of the class of the subobject.



201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/mongo_record/base.rb', line 201

def has_one(name, options={})
  name = name.to_sym
  unless @subobjects[name]
    ivar_name = "@" + name.to_s
    define_method(name, lambda { instance_variable_get(ivar_name) })
    define_method("#{name}=".to_sym, lambda { |val| instance_variable_set(ivar_name, val) })
    define_method("#{name}?".to_sym, lambda {
                    val = instance_variable_get(ivar_name)
                    val != nil && (!val.kind_of?(String) || val != '')
                  })
    klass_name = options[:class_name] || field_name_to_class_name(name)
    @subobjects[name] = eval(klass_name)
  end
end

.index(fields, unique = false) ⇒ Object

Creates an index for this collection. fields should be either a single field name (:title) or an array of fields ([:title, :author, :date]) or an array of a field name and direction ([:title, :asc] or [:title, :desc]) or an array of field names and directions ([[:title, :asc], [:author, :desc]]) unique should be true or false and indicates whether this index should enforce a uniqueness constraint.



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/mongo_record/base.rb', line 155

def index(fields, unique = false)
  fields = Array(fields)

  if fields.length == 2 &&
    ( fields[1].to_s == 'asc' || fields[1].to_s == 'desc' ||
      fields[1] == Mongo::ASCENDING || fields[1] == Mongo::DESCENDING )
    fields = [fields]
  end

  fields = fields.map do |field|
    field = field.respond_to?(:[]) ? field : [field, :asc]
    field[1] = (field[1] == :desc) ? Mongo::DESCENDING : Mongo::ASCENDING
    field
  end

  collection.create_index(fields, unique)
end

.indexes(*fields) ⇒ Object

Returns list of indexes for model, unless fields are passed. In that case, creates an index.



175
176
177
178
179
180
181
# File 'lib/mongo_record/base.rb', line 175

def indexes(*fields)
  if fields.empty?
    collection.index_information
  else
    index(*fields)
  end
end

.inherited(subclass) ⇒ Object

Get ready to save information about subclass.



108
109
110
111
112
113
# File 'lib/mongo_record/base.rb', line 108

def inherited(subclass)
  subclass.instance_variable_set("@coll_name", class_name_to_field_name(subclass.name)) # default name
  subclass.instance_variable_set("@field_names", []) # array of scalars names (symbols)
  subclass.instance_variable_set("@subobjects", {}) # key = name (symbol), value = class
  subclass.instance_variable_set("@arrays", {})     # key = name (symbol), value = class
end

.instantiate(row = {}) ⇒ Object

This method only exists so that MongoRecord::Base and ActiveRecord::Base can live side by side.



103
104
105
# File 'lib/mongo_record/base.rb', line 103

def instantiate(row={})
  new(row)
end

.last(*args) ⇒ Object



369
370
371
372
# File 'lib/mongo_record/base.rb', line 369

def last(*args)
  options = extract_options_from_args!(args)
  find_last(options)
end

.method_missing(sym, *args) ⇒ Object

Handles find_* methods such as find_by_name, find_all_by_shoe_size, and find_or_create_by_name.



467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
# File 'lib/mongo_record/base.rb', line 467

def method_missing(sym, *args)
  if match = /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(sym.to_s)
    find_how_many = ($1 == 'all_by') ? :all : :first
    field_names = $2.split(/_and_/)
    super unless all_fields_exist?(field_names)
    search = search_from_names_and_values(field_names, args)
    self.find(find_how_many, {:conditions => search}, *args[field_names.length..-1])
  elsif match = /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/.match(sym.to_s)
    create = $1 == 'create'
    field_names = $2.split(/_and_/)
    super unless all_fields_exist?(field_names)
    search = search_from_names_and_values(field_names, args)
    row = self.find(:first, {:conditions => search})
    return self.new(row) if row # found
    obj = self.new(search.merge(args[field_names.length] || {})) # new object using search and remainder of args
    obj.save if create
    obj
  else
    super
  end
end

.mongo_ivar_namesObject

Return the names of all fields, subobjects, and arrays.



194
# File 'lib/mongo_record/base.rb', line 194

def mongo_ivar_names; @field_names + @subobjects.keys + @arrays.keys; end

.removeObject

Deletes the record with the given id from the collection.



406
407
408
# File 'lib/mongo_record/base.rb', line 406

def delete(id)
  collection.remove({:_id => id})
end

.subobjectsObject

Return the names of all instance variables that hold objects declared using has_one. The names do not start with ‘@’.

These are not necessarily MongoRecord::Subobject subclasses.



187
# File 'lib/mongo_record/base.rb', line 187

def subobjects; @subobjects; end

.sum(column) ⇒ Object



397
398
399
400
# File 'lib/mongo_record/base.rb', line 397

def sum(column)
  x = self.find(:all, :select=>column)
  x.map {|p1| p1[column.to_sym]}.compact.inject(0) { |s,v| s += v }
end

.update(id, attributes) ⇒ Object

Finds the record from the passed id, instantly saves it with the passed attributes (if the validation permits it), and returns it. If the save fails under validations, the unsaved object is still returned.

The arguments may also be given as arrays in which case the update method is called for each pair of id and attributes and an array of objects is returned.

>

Example of updating one record:

Person.update(15, {:user_name => 'Samuel', :group => 'expert'})

Example of updating multiple records:

people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy"} }
Person.update(people.keys, people.values)


454
455
456
457
458
459
460
461
462
463
# File 'lib/mongo_record/base.rb', line 454

def update(id, attributes)
  if id.is_a?(Array)
    i = -1
    id.collect { |id| i += 1; update(id, attributes[i]) }
  else
    object = find(id)
    object.update_attributes(attributes)
    object
  end
end

.update_all(updates, conditions = nil) ⇒ Object

Not yet implemented.



414
415
416
417
# File 'lib/mongo_record/base.rb', line 414

def update_all(updates, conditions = nil)
  # TODO
  raise "not yet implemented"
end

Instance Method Details

#==(comparison_object) ⇒ Object

Return true if the comparison_object is the same object, or is of the same type and has the same id.



781
782
783
784
785
786
# File 'lib/mongo_record/base.rb', line 781

def ==(comparison_object)
  comparison_object.equal?(self) ||
    (comparison_object.instance_of?(self.class) &&
     comparison_object.id == id &&
     !comparison_object.new_record?)
end

#[](attr_name) ⇒ Object



880
881
882
# File 'lib/mongo_record/base.rb', line 880

def [](attr_name)
  self.send(attr_name)
end

#[]=(attr_name, value) ⇒ Object



884
885
886
887
# File 'lib/mongo_record/base.rb', line 884

def []=(attr_name, value)
  self.class.field(attr_name)
  self.send(attr_name.to_s + '=', value)
end

#attributes_from_column_definitionObject

Does nothing.



931
# File 'lib/mongo_record/base.rb', line 931

def attributes_from_column_definition; end

#createObject

Save self to the database and set the id.



844
845
846
847
848
849
850
851
# File 'lib/mongo_record/base.rb', line 844

def create
  create_date = self.instance_variable_defined?("@created_at") ? self.created_at : nil
  set_create_times(create_date)
  @_ns = self.class.collection.name
  value = to_mongo_value
  @_id = self.class.collection.insert(value)
  value.merge(:_id => @_id)
end

#deleteObject Also known as: remove

Remove self from the database and set @_id to nil. If self has no @_id, does nothing.



866
867
868
869
870
871
# File 'lib/mongo_record/base.rb', line 866

def delete
  if @_id
    self.class.collection.remove({:_id => self._id})
    @_id = nil
  end
end

#destroyObject

Delete and freeze self.



875
876
877
878
# File 'lib/mongo_record/base.rb', line 875

def destroy
  delete
  freeze
end

#eql?(comparison_object) ⇒ Boolean

Delegate to ==

Returns:

  • (Boolean)


789
790
791
# File 'lib/mongo_record/base.rb', line 789

def eql?(comparison_object)
  self == (comparison_object)
end

#hashObject

Delegate to id in order to allow two records of the same type and id to work with something like:

[ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]


795
796
797
# File 'lib/mongo_record/base.rb', line 795

def hash
  id.hash
end

#idObject

Return this object’s id.



777
# File 'lib/mongo_record/base.rb', line 777

def id; @_id ? @_id.to_s : nil; end

#id=(val) ⇒ Object

Set the id of this object. Normally not called by user code.



774
# File 'lib/mongo_record/base.rb', line 774

def id=(val); @_id = (val == '' ? nil : val); end

#new_record?Boolean

Return true if this object is new—that is, does not yet have an id.

Returns:

  • (Boolean)


816
817
818
# File 'lib/mongo_record/base.rb', line 816

def new_record?
  @_id.nil? || self.class.collection.find_one("_id" => @_id).nil?
end

#saveObject

Save self and returns true if the save was successful, false if not.



805
806
807
# File 'lib/mongo_record/base.rb', line 805

def save
  create_or_update
end

#save!Object

Save self and returns true if the save was successful and raises RecordNotSaved if not.



811
812
813
# File 'lib/mongo_record/base.rb', line 811

def save!
  create_or_update || raise(RecordNotSaved)
end

#set_create_times(t = nil) ⇒ Object



935
936
937
938
939
940
941
942
943
944
# File 'lib/mongo_record/base.rb', line 935

def set_create_times(t=nil)
  t ||= Time.now
  t = Time.parse(t) if t.is_a?(String)
  self["created_at"] = t
  self["created_on"] = Time.local(t.year, t.month, t.day)
  self.class.subobjects.keys.each { |iv|
    val = instance_variable_get("@#{iv}")
    val.send(:set_create_times, t) if val
  }
end

#to_mongo_valueObject

Convert this object to a Mongo value suitable for saving to the database.



822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
# File 'lib/mongo_record/base.rb', line 822

def to_mongo_value
  h = {}
  key_names = self.instance_values.keys
  key_names.each {|key|
    value = instance_variable_get("@#{key}").to_mongo_value
    if value.instance_of? Hash and value["_ns"]
      value = Mongo::DBRef.new(value["_ns"], value["_id"])
    elsif value.instance_of? Array
      value = value.map {|v|
        if v.instance_of? Hash and v["_ns"]
          Mongo::DBRef.new(v["_ns"], v["_id"])
        else
          v
        end
      }
    end
    h[key] = value
  }
  h
end

#to_paramObject

Rails convenience method. Return this object’s id as a string.



800
801
802
# File 'lib/mongo_record/base.rb', line 800

def to_param
  @_id.to_s
end

#updateObject

Save self to the database. Return false if there was an error, self if all is well.



855
856
857
858
859
860
861
862
# File 'lib/mongo_record/base.rb', line 855

def update
  set_update_times
  self.class.collection.update({:_id => @_id}, to_mongo_value)
  if self.class.collection.db.error?
    return false
  end
  self
end

#update_attribute(name, value) ⇒ Object

Updates a single attribute and saves the record. This is especially useful for boolean flags on existing records. Note: This method is overwritten by the Validation module that’ll make sure that updates made with this method doesn’t get subjected to validation checks. Hence, attributes can be updated even if the full object isn’t valid.



909
910
911
912
# File 'lib/mongo_record/base.rb', line 909

def update_attribute(name, value)
  self[name] = value
  save
end

#update_attributes(attributes) ⇒ Object

Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will fail and false will be returned.



917
918
919
920
921
# File 'lib/mongo_record/base.rb', line 917

def update_attributes(attributes)
  attributes.each do |name, value|
    update_attribute(name, value)
  end
end

#update_attributes!(attributes) ⇒ Object

Updates an object just like Base.update_attributes but calls save! instead of save so an exception is raised if the record is invalid.



925
926
927
928
# File 'lib/mongo_record/base.rb', line 925

def update_attributes!(attributes)
  self.attributes = attributes
  save!
end