Module: DataMapper::Model

Extended by:
Chainable
Includes:
Is, Scope
Defined in:
lib/dm-core/model.rb,
lib/dm-core/model/is.rb,
lib/dm-core/model/hook.rb,
lib/dm-core/model/scope.rb,
lib/dm-core/model/property.rb,
lib/dm-core/model/relationship.rb,
lib/dm-core/model/descendant_set.rb

Defined Under Namespace

Modules: Hook, Is, Property, Relationship, Scope Classes: DescendantSet

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Chainable

chainable, extendable

Methods included from Scope

#current_scope, #default_scope, #query

Methods included from Is

#is

Instance Attribute Details

#base_modelObject (readonly)

TODO: document



533
534
535
# File 'lib/dm-core/model.rb', line 533

def base_model
  @base_model
end

#descendantsSet (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Return all models that inherit from a Model

class Foo
  include DataMapper::Resource
end

class Bar < Foo
end

Foo.descendants.first   #=> Bar

Returns:

  • (Set)

    Set containing the descendant classes



73
74
75
# File 'lib/dm-core/model.rb', line 73

def descendants
  @descendants
end

Class Method Details

.append_extensions(*extensions) ⇒ Boolean

Extends the model with this module after Resource has been included.

This is a useful way to extend Model while still retaining a self.extended method.

Parameters:

  • extensions (Module)

    List of modules that will extend the model after it is extended by Model

Returns:

  • (Boolean)

    whether or not the inclusions have been successfully appended to the list



112
113
114
115
# File 'lib/dm-core/model.rb', line 112

def self.append_extensions(*extensions)
  extra_extensions.concat extensions
  true
end

.append_inclusions(*inclusions) ⇒ Boolean

Appends a module for inclusion into the model class after Resource.

This is a useful way to extend Resource while still retaining a self.included method.

Parameters:

  • inclusions (Module)

    the module that is to be appended to the module after Resource

Returns:

  • (Boolean)

    true if the inclusions have been successfully appended to the list



87
88
89
90
# File 'lib/dm-core/model.rb', line 87

def self.append_inclusions(*inclusions)
  extra_inclusions.concat inclusions
  true
end

.descendantsDescendantSet

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Return all models that extend the Model module

class Foo
  include DataMapper::Resource
end

DataMapper::Model.descendants.first   #=> Foo

Returns:



54
55
56
# File 'lib/dm-core/model.rb', line 54

def self.descendants
  @descendants ||= DescendantSet.new
end

.extended(model) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

TODO: document



128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/dm-core/model.rb', line 128

def self.extended(model)
  descendants << model

  model.instance_variable_set(:@valid,         false)
  model.instance_variable_set(:@base_model,    model)
  model.instance_variable_set(:@storage_names, {})
  model.instance_variable_set(:@default_order, {})
  model.instance_variable_set(:@descendants,   descendants.class.new(model, descendants))

  extra_extensions.each { |mod| model.extend(mod)         }
  extra_inclusions.each { |mod| model.send(:include, mod) }
end

.extra_extensionsSet

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The current registered extra extensions

Returns:

  • (Set)


122
123
124
# File 'lib/dm-core/model.rb', line 122

def self.extra_extensions
  @extra_extensions ||= []
end

.extra_inclusionsSet

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The current registered extra inclusions

Returns:

  • (Set)


97
98
99
# File 'lib/dm-core/model.rb', line 97

def self.extra_inclusions
  @extra_inclusions ||= []
end

.new(storage_name = nil, &block) ⇒ Model

Creates a new Model class with default_storage_name storage_name

If a block is passed, it will be eval’d in the context of the new Model

Parameters:

  • block (Proc)

    a block that will be eval’d in the context of the new Model class

Returns:

  • (Model)

    the newly created Model class



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/dm-core/model.rb', line 18

def self.new(storage_name = nil, &block)
  model = Class.new

  model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
    include DataMapper::Resource

    def self.name
      to_s
    end
  RUBY

  if storage_name
    warn "Passing in +storage_name+ to #{name}.new is deprecated (#{caller[0]})"
    model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
      def self.default_storage_name
        #{Extlib::Inflection.classify(storage_name).inspect}.freeze
      end
    RUBY
  end

  model.instance_eval(&block) if block
  model
end

Instance Method Details

#[](*args) ⇒ Object Also known as: slice



222
223
224
# File 'lib/dm-core/model.rb', line 222

def [](*args)
  all[*args]
end

#all(query = nil) ⇒ Collection

Find a set of records matching an optional set of conditions. Additionally, specify the order that the records are return.

Zoo.all                                   # all zoos
Zoo.all(:open => true)                    # all zoos that are open
Zoo.all(:opened_on => start..end)         # all zoos that opened on a date in the date-range
Zoo.all(:order => [ :tiger_count.desc ])  # Ordered by tiger_count

Parameters:

  • query (Hash) (defaults to: nil)

    A hash describing the conditions and order for the query

Returns:

  • (Collection)

    A set of records found matching the conditions in query

See Also:



258
259
260
261
262
263
264
265
# File 'lib/dm-core/model.rb', line 258

def all(query = nil)
  if query.nil? || (query.kind_of?(Hash) && query.empty?)
    # TODO: after adding Enumerable methods to Model, try to return self here
    new_collection(self.query.dup)
  else
    new_collection(scoped_query(query))
  end
end

#at(*args) ⇒ Object



228
229
230
# File 'lib/dm-core/model.rb', line 228

def at(*args)
  all.at(*args)
end

#copy(source, destination, query = {}) ⇒ Collection

Copy a set of records from one repository to another.

Parameters:

  • source (String)

    The name of the Repository the resources should be copied from

  • destination (String)

    The name of the Repository the resources should be copied to

  • query (Hash) (defaults to: {})

    The conditions with which to find the records to copy. These conditions are merged with Model.query

Returns:

  • (Collection)

    A Collection of the Resource instances created in the operation



433
434
435
436
437
438
439
440
441
442
443
444
# File 'lib/dm-core/model.rb', line 433

def copy(source, destination, query = {})

  # get the list of properties that exist in the source and destination
  destination_properties = properties(destination)
  fields = query[:fields] ||= properties(source).select { |property| destination_properties.include?(property) }

  repository(destination) do
    all(query.merge(:repository => source)).map do |resource|
      create(fields.map { |property| [ property.name, property.get(resource) ] }.to_hash)
    end
  end
end

#create(attributes = {}) ⇒ Resource

Create a Resource

Parameters:

  • attributes (Hash(Symbol => Object)) (defaults to: {})

    attributes to set

Returns:

  • (Resource)

    the newly created Resource instance



402
403
404
# File 'lib/dm-core/model.rb', line 402

def create(attributes = {})
  _create(true, attributes)
end

#create!(attributes = {}) ⇒ Resource

Create a Resource, bypassing hooks

Parameters:

  • attributes (Hash(Symbol => Object)) (defaults to: {})

    attributes to set

Returns:

  • (Resource)

    the newly created Resource instance



415
416
417
# File 'lib/dm-core/model.rb', line 415

def create!(attributes = {})
  _create(false, attributes)
end

#default_order(repository_name = default_repository_name) ⇒ Object

TODO: document



543
544
545
# File 'lib/dm-core/model.rb', line 543

def default_order(repository_name = default_repository_name)
  @default_order[repository_name] ||= key(repository_name).map { |property| Query::Direction.new(property) }.freeze
end

#default_repository_nameObject

TODO: document



537
538
539
# File 'lib/dm-core/model.rb', line 537

def default_repository_name
  Repository.default_name
end

#entriesObject Also known as: to_a

TODO: spec this



237
238
239
# File 'lib/dm-core/model.rb', line 237

def entries
  all.entries
end

#first(*args) ⇒ Resource, Collection

Return the first Resource or the first N Resources for the Model with an optional query

When there are no arguments, return the first Resource in the Model. When the first argument is an Integer, return a Collection containing the first N Resources. When the last (optional) argument is a Hash scope the results to the query.

Parameters:

  • limit (Integer)

    (optional) limit the returned Collection to a specific number of entries

  • query (Hash)

    (optional) scope the returned Resource or Collection to the supplied query

Returns:

  • (Resource, Collection)

    The first resource in the entries of this collection, or a new collection whose query has been merged



284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/dm-core/model.rb', line 284

def first(*args)
  last_arg = args.last

  limit      = args.first if args.first.kind_of?(Integer)
  with_query = last_arg.respond_to?(:merge) && !last_arg.blank?

  query = with_query ? last_arg : {}

  query = if query.kind_of?(Query)
    query.slice(0, limit || 1)
  else
    offset = query.fetch(:offset, 0)
    query  = query.except(:offset)
    scoped_query(query).slice(offset, limit || 1)
  end

  if limit
    all(query)
  else
    query.repository.read(query).first
  end
end

#first_or_create(conditions = {}, attributes = {}) ⇒ Resource

Finds the first Resource by conditions, or creates a new Resource with the attributes if none found

Parameters:

  • conditions (Hash) (defaults to: {})

    The conditions to be used to search

  • attributes (Hash) (defaults to: {})

    The attributes to be used to create the record of none is found.

Returns:

  • (Resource)

    The instance found by query, or created with attributes if none found



373
374
375
# File 'lib/dm-core/model.rb', line 373

def first_or_create(conditions = {}, attributes = {})
  first(conditions) || create(conditions.merge(attributes))
end

#first_or_new(conditions = {}, attributes = {}) ⇒ Resource

Finds the first Resource by conditions, or initializes a new Resource with the attributes if none found

Parameters:

  • conditions (Hash) (defaults to: {})

    The conditions to be used to search

  • attributes (Hash) (defaults to: {})

    The attributes to be used to create the record of none is found.

Returns:

  • (Resource)

    The instance found by query, or created with attributes if none found



358
359
360
# File 'lib/dm-core/model.rb', line 358

def first_or_new(conditions = {}, attributes = {})
  first(conditions) || new(conditions.merge(attributes))
end

#get(*key) ⇒ Resource, NilClass

Grab a single record by its key. Supports natural and composite key lookups as well.

Zoo.get(1)                # get the zoo with primary key of 1.
Zoo.get!(1)               # Or get! if you want an ObjectNotFoundError on failure
Zoo.get('DFW')            # wow, support for natural primary keys
Zoo.get('Metro', 'DFW')   # more wow, composite key look-up

Parameters:

  • *key (Object)

    The primary key or keys to use for lookup

Returns:

  • (Resource, NilClass)

    A single model that was found If no instance was found matching key



200
201
202
203
204
205
# File 'lib/dm-core/model.rb', line 200

def get(*key)
  repository = self.repository
  key        = self.key(repository.name).typecast(key)

  repository.identity_map(self)[key] || first(key_conditions(repository, key))
end

#get!(*key) ⇒ Resource

Grab a single record just like #get, but raise an ObjectNotFoundError if the record doesn’t exist.

Parameters:

  • *key (Object)

    The primary key or keys to use for lookup

Returns:

  • (Resource)

    A single model that was found

Raises:



218
219
220
# File 'lib/dm-core/model.rb', line 218

def get!(*key)
  get(*key) || raise(ObjectNotFoundError, "Could not find #{self.name} with key #{key.inspect}")
end

#last(*args) ⇒ Resource, Collection

Return the last Resource or the last N Resources for the Model with an optional query

When there are no arguments, return the last Resource for the Model. When the first argument is an Integer, return a Collection containing the last N Resources. When the last (optional) argument is a Hash scope the results to the query.

Parameters:

  • limit (Integer)

    (optional) limit the returned Collection to a specific number of entries

  • query (Hash)

    (optional) scope the returned Resource or Collection to the supplied query

Returns:

  • (Resource, Collection)

    The last resource in the entries of this collection, or a new collection whose query has been merged



324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/dm-core/model.rb', line 324

def last(*args)
  last_arg = args.last

  limit      = args.first if args.first.kind_of?(Integer)
  with_query = last_arg.respond_to?(:merge) && !last_arg.blank?

  query = with_query ? last_arg : {}

  query = if query.kind_of?(Query)
    query.slice(0, limit || 1).reverse!
  else
    offset = query.fetch(:offset, 0)
    query  = query.except(:offset)
    scoped_query(query).slice(offset, limit || 1).reverse!
  end

  if limit
    all(query)
  else
    query.repository.read(query).last
  end
end

#load(records, query) ⇒ Resource

Loads an instance of this Model, taking into account IdentityMap lookup, inheritance columns(s) and Property typecasting.

Parameters:

  • records (Enumerable(Object))

    an Array of Resource or Hashes to load a Resource with

Returns:

  • (Resource)

    the loaded Resource instance



456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
# File 'lib/dm-core/model.rb', line 456

def load(records, query)
  repository      = query.repository
  repository_name = repository.name
  fields          = query.fields
  discriminator   = properties(repository_name).discriminator
  no_reload       = !query.reload?

  field_map = fields.map { |property| [ property, property.field ] }.to_hash

  records.map do |record|
    identity_map = nil
    key_values   = nil
    resource     = nil

    case record
      when Hash
        # remap fields to use the Property object
        record = record.dup
        field_map.each { |property, field| record[property] = record.delete(field) if record.key?(field) }

        model = discriminator && record[discriminator] || self

        resource = if (key_values = record.values_at(*model.key(repository_name))).all?
          identity_map = repository.identity_map(model)
          identity_map[key_values]
        end

        resource ||= model.allocate

        fields.each do |property|
          next if no_reload && property.loaded?(resource)

          value = record[property]

          # TODO: typecasting should happen inside the Adapter
          # and all values should come back as expected objects
          if property.custom?
            value = property.type.load(value, property)
          end

          property.set!(resource, value)
        end

      when Resource
        model = record.model

        resource = if (key_values = record.key).all?
          identity_map = repository.identity_map(model)
          identity_map[key_values]
        end

        resource ||= model.allocate

        fields.each do |property|
          next if no_reload && property.loaded?(resource)

          property.set!(resource, property.get!(record))
        end
    end

    resource.instance_variable_set(:@repository, repository)
    resource.instance_variable_set(:@saved,      true)

    if identity_map
      # defer setting the IdentityMap so second level caches can
      # record the state of the resource after loaded
      identity_map[key_values] = resource
    else
      resource.freeze
    end

    resource
  end
end

#model_method_defined?(method) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

TODO: document

Returns:

  • (Boolean)


600
601
602
# File 'lib/dm-core/model.rb', line 600

def model_method_defined?(method)
  model_methods.include?(method.to_s)
end

#repositoriesSet

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Gets the current Set of repositories for which this Model has been defined (beyond default)

Returns:

  • (Set)

    The Set of repositories for which this Model has been defined (beyond default)



594
595
596
# File 'lib/dm-core/model.rb', line 594

def repositories
  [ repository ].to_set + @properties.keys.map { |repository_name| DataMapper.repository(repository_name) }
end

#repository(name = nil) ⇒ Object, Respository

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Get the repository with a given name, or the default one for the current context, or the default one for this class.

Parameters:

  • name (Symbol) (defaults to: nil)

    the name of the repository wanted

  • block (Block)

    block to execute with the fetched repository as parameter

Returns:

  • (Object, Respository)

    whatever the block returns, if given a block, otherwise the requested repository.



560
561
562
563
564
565
566
567
568
569
570
571
# File 'lib/dm-core/model.rb', line 560

def repository(name = nil)
  #
  # There has been a couple of different strategies here, but me (zond) and dkubb are at least
  # united in the concept of explicitness over implicitness. That is - the explicit wish of the
  # caller (+name+) should be given more priority than the implicit wish of the caller (Repository.context.last).
  #
  if block_given?
    DataMapper.repository(name || repository_name) { |*block_args| yield(*block_args) }
  else
    DataMapper.repository(name || repository_name)
  end
end

#repository_nameString

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Get the current repository_name for this Model.

If there are any Repository contexts, the name of the last one will be returned, else the default_repository_name of this model will be

Returns:

  • (String)

    the current repository name to use for this Model



582
583
584
# File 'lib/dm-core/model.rb', line 582

def repository_name
  Repository.context.any? ? Repository.context.last.name : default_repository_name
end

#resource_method_defined?(method) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

TODO: document

Returns:

  • (Boolean)


606
607
608
# File 'lib/dm-core/model.rb', line 606

def resource_method_defined?(method)
  resource_methods.include?(method.to_s)
end

#reverseObject



232
233
234
# File 'lib/dm-core/model.rb', line 232

def reverse
  all.reverse
end

#storage_name(repository_name = default_repository_name) ⇒ String

Gets the name of the storage receptacle for this resource in the given Repository (ie., table name, for database stores).

Returns:

  • (String)

    the storage name (ie., table name, for database stores) associated with this resource in the given repository



170
171
172
# File 'lib/dm-core/model.rb', line 170

def storage_name(repository_name = default_repository_name)
  storage_names[repository_name] ||= repository(repository_name).adapter.resource_naming_convention.call(default_storage_name).freeze
end

#storage_namesHash(Symbol => String)

the names of the storage receptacles for this resource across all repositories

Returns:

  • (Hash(Symbol => String))

    All available names of storage recepticles



180
181
182
# File 'lib/dm-core/model.rb', line 180

def storage_names
  @storage_names
end