Module: DataMapper::Model

Extended by:
Chainable
Includes:
Is, Scope, Enumerable
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

Defined Under Namespace

Modules: Hook, Is, Property, Relationship, Scope

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, #scope_stack, #with_exclusive_scope, #with_scope

Methods included from Is

#is

Instance Attribute Details

#base_modelObject (readonly)



638
639
640
# File 'lib/dm-core/model.rb', line 638

def base_model
  @base_model
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



178
179
180
181
182
183
184
185
186
187
# File 'lib/dm-core/model.rb', line 178

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

  # Add the extension to existing descendants
  descendants.each do |model|
    extensions.each { |extension| model.extend(extension) }
  end

  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



147
148
149
150
151
152
153
154
155
156
# File 'lib/dm-core/model.rb', line 147

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

  # Add the inclusion to existing descendants
  descendants.each do |model|
    inclusions.each { |inclusion| model.send :include, inclusion }
  end

  true
end

.descendantsDescendantSet

Return all models that extend the Model module

class Foo
  include DataMapper::Resource
end

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

Returns:



50
51
52
# File 'lib/dm-core/model.rb', line 50

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

.extended(descendant) ⇒ 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.



199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/dm-core/model.rb', line 199

def self.extended(descendant)
  descendants << descendant

  descendant.instance_variable_set(:@valid,         false)
  descendant.instance_variable_set(:@base_model,    descendant)
  descendant.instance_variable_set(:@storage_names, {})
  descendant.instance_variable_set(:@default_order, {})

  descendant.extend(Chainable)

  extra_extensions.each { |mod| descendant.extend(mod)         }
  extra_inclusions.each { |mod| descendant.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)


194
195
196
# File 'lib/dm-core/model.rb', line 194

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)


163
164
165
# File 'lib/dm-core/model.rb', line 163

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

.new(name = nil, namespace = Object, &block) ⇒ Model

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.

Creates a new Model class with its constant already set

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

Parameters:

  • name (#to_s) (defaults to: nil)

    the name of the new model

  • namespace (Object) (defaults to: Object)

    the namespace that will hold the new model

  • block (Proc)

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

Returns:

  • (Model)

    the newly created Model class



27
28
29
30
31
32
33
34
35
36
# File 'lib/dm-core/model.rb', line 27

def self.new(name = nil, namespace = Object, &block)
  model = name ? namespace.const_set(name, Class.new) : Class.new

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

  model.instance_eval(&block) if block
  model
end

.raise_on_save_failureBoolean

Return if Resource#save should raise an exception on save failures (globally)

This is false by default.

DataMapper::Model.raise_on_save_failure  # => false

Returns:

  • (Boolean)

    true if a failure in Resource#save should raise an exception



83
84
85
86
87
88
89
# File 'lib/dm-core/model.rb', line 83

def self.raise_on_save_failure
  if defined?(@raise_on_save_failure)
    @raise_on_save_failure
  else
    false
  end
end

.raise_on_save_failure=(raise_on_save_failure) ⇒ Boolean

Specify if Resource#save should raise an exception on save failures (globally)

Parameters:

  • a (Boolean)

    boolean that if true will cause Resource#save to raise an exception

Returns:

  • (Boolean)

    true if a failure in Resource#save should raise an exception



100
101
102
# File 'lib/dm-core/model.rb', line 100

def self.raise_on_save_failure=(raise_on_save_failure)
  @raise_on_save_failure = raise_on_save_failure
end

Instance Method Details

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



287
288
289
# File 'lib/dm-core/model.rb', line 287

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

#all(query = Undefined) ⇒ 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: Undefined)

    A hash describing the conditions and order for the query

Returns:

  • (Collection)

    A set of records found matching the conditions in query

See Also:



329
330
331
332
333
334
335
336
# File 'lib/dm-core/model.rb', line 329

def all(query = Undefined)
  if query.equal?(Undefined) || (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



293
294
295
# File 'lib/dm-core/model.rb', line 293

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

#copy(source_repository_name, target_repository_name, query = {}) ⇒ Collection

Copy a set of records from one repository to another.

Parameters:

  • source_repository_name (String)

    The name of the Repository the resources should be copied from

  • target_repository_name (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



530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
# File 'lib/dm-core/model.rb', line 530

def copy(source_repository_name, target_repository_name, query = {})
  target_properties = properties(target_repository_name)

  query[:fields] ||= properties(source_repository_name).select do |property|
    target_properties.include?(property)
  end

  repository(target_repository_name) do |repository|
    resources = []

    all(query.merge(:repository => source_repository_name)).each do |resource|
      new_resource = new
      query[:fields].each { |property| new_resource.__send__("#{property.name}=", property.get(resource)) }
      resources << new_resource if new_resource.save
    end

    all(Query.target_query(repository, self, resources))
  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



449
450
451
# File 'lib/dm-core/model.rb', line 449

def create(attributes = {})
  _create(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



462
463
464
# File 'lib/dm-core/model.rb', line 462

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

#default_order(repository_name = default_repository_name) ⇒ Object



646
647
648
# File 'lib/dm-core/model.rb', line 646

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



641
642
643
# File 'lib/dm-core/model.rb', line 641

def default_repository_name
  Repository.default_name
end

#descendantsSet

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



69
70
71
# File 'lib/dm-core/model.rb', line 69

def descendants
  @descendants ||= DescendantSet.new
end

#destroyBoolean

Remove all Resources from the repository

Returns:

  • (Boolean)

    true if the resources were successfully destroyed



502
503
504
# File 'lib/dm-core/model.rb', line 502

def destroy
  all.destroy
end

#destroy!Boolean

Remove all Resources from the repository, bypassing validation

Returns:

  • (Boolean)

    true if the resources were successfully destroyed



512
513
514
# File 'lib/dm-core/model.rb', line 512

def destroy!
  all.destroy!
end

#each(&block) ⇒ Object



309
310
311
312
# File 'lib/dm-core/model.rb', line 309

def each(&block)
  all.each(&block)
  self
end

#fetch(*args, &block) ⇒ Object



297
298
299
# File 'lib/dm-core/model.rb', line 297

def fetch(*args, &block)
  all.fetch(*args, &block)
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



355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/dm-core/model.rb', line 355

def first(*args)
  first_arg = args.first
  last_arg  = args.last

  limit_specified = first_arg.kind_of?(Integer)
  with_query      = (last_arg.kind_of?(Hash) && !last_arg.empty?) || last_arg.kind_of?(Query)

  limit = limit_specified ? first_arg : 1
  query = with_query      ? last_arg  : {}

  query = self.query.slice(0, limit).update(query)

  if limit_specified
    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



436
437
438
# File 'lib/dm-core/model.rb', line 436

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



421
422
423
# File 'lib/dm-core/model.rb', line 421

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

#get(*key) ⇒ Resource?

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, nil)

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



263
264
265
266
267
268
269
270
# File 'lib/dm-core/model.rb', line 263

def get(*key)
  assert_valid_key_size(key)

  repository = self.repository
  key        = self.key(repository.name).typecast(key)

  repository.identity_map(self)[key] || first(key_conditions(repository, key).update(:order => nil))
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:



283
284
285
# File 'lib/dm-core/model.rb', line 283

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



391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
# File 'lib/dm-core/model.rb', line 391

def last(*args)
  first_arg = args.first
  last_arg  = args.last

  limit_specified = first_arg.kind_of?(Integer)
  with_query      = (last_arg.kind_of?(Hash) && !last_arg.empty?) || last_arg.kind_of?(Query)

  limit = limit_specified ? first_arg : 1
  query = with_query      ? last_arg  : {}

  query = self.query.slice(0, limit).update(query).reverse!

  if limit_specified
    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



560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
# File 'lib/dm-core/model.rb', line 560

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 ] }
  field_map = DataMapper::Ext::Array.to_hash(field_map)

  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 && discriminator.load(record[discriminator]) || self
        model_key = model.key(repository_name)

        resource = if model_key.valid?(key_values = record.values_at(*model_key))
          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
          value = property.load(value)

          property.set!(resource, value)
        end

      when Resource
        model     = record.model
        model_key = model.key(repository_name)

        resource = if model_key.valid?(key_values = record.key)
          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)

    if identity_map
      resource.persisted_state = Resource::State::Clean.new(resource) unless resource.persisted_state?

      # defer setting the IdentityMap so second level caches can
      # record the state of the resource after loaded
      identity_map[key_values] = resource
    else
      resource.persisted_state = Resource::State::Immutable.new(resource)
    end

    resource
  end
end

#raise_on_save_failureBoolean

Return if Resource#save should raise an exception on save failures (per-model)

This delegates to DataMapper::Model.raise_on_save_failure by default.

User.raise_on_save_failure  # => false

Returns:

  • (Boolean)

    true if a failure in Resource#save should raise an exception



114
115
116
117
118
119
120
# File 'lib/dm-core/model.rb', line 114

def raise_on_save_failure
  if defined?(@raise_on_save_failure)
    @raise_on_save_failure
  else
    DataMapper::Model.raise_on_save_failure
  end
end

#raise_on_save_failure=(raise_on_save_failure) ⇒ Boolean

Specify if Resource#save should raise an exception on save failures (per-model)

Parameters:

  • a (Boolean)

    boolean that if true will cause Resource#save to raise an exception

Returns:

  • (Boolean)

    true if a failure in Resource#save should raise an exception



131
132
133
# File 'lib/dm-core/model.rb', line 131

def raise_on_save_failure=(raise_on_save_failure)
  @raise_on_save_failure = raise_on_save_failure
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)



695
696
697
# File 'lib/dm-core/model.rb', line 695

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

#repository(name = nil, &block) ⇒ 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.



663
664
665
666
667
668
669
670
671
# File 'lib/dm-core/model.rb', line 663

def repository(name = nil, &block)
  #
  # 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).
  #

  DataMapper.repository(name || repository_name, &block)
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



682
683
684
685
# File 'lib/dm-core/model.rb', line 682

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

#reverseObject



305
306
307
# File 'lib/dm-core/model.rb', line 305

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



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

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 receptacles



243
244
245
# File 'lib/dm-core/model.rb', line 243

def storage_names
  @storage_names
end

#update(attributes) ⇒ Boolean

Update every Resource

Person.update(:allow_beer => true)

Parameters:

  • attributes (Hash)

    attributes to update with

Returns:

  • (Boolean)

    true if the resources were successfully updated



477
478
479
# File 'lib/dm-core/model.rb', line 477

def update(attributes)
  all.update(attributes)
end

#update!(attributes) ⇒ Boolean

Update every Resource, bypassing validations

Person.update!(:allow_beer => true)

Parameters:

  • attributes (Hash)

    attributes to update with

Returns:

  • (Boolean)

    true if the resources were successfully updated



492
493
494
# File 'lib/dm-core/model.rb', line 492

def update!(attributes)
  all.update!(attributes)
end

#values_at(*args) ⇒ Object



301
302
303
# File 'lib/dm-core/model.rb', line 301

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