Module: DataMapper::Model

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

Constant Summary collapse

WRITER_METHOD_REGEXP =
/=\z/.freeze
INVALID_WRITER_METHODS =
%w[ == != === []= taguri= attributes= collection= persistence_state= raise_on_save_failure= ].to_set.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Scope

#current_scope, #default_scope, #query, #scope_stack, #with_exclusive_scope, #with_scope

Methods included from Is

#is

Instance Attribute Details

#allowed_writer_methodsSet (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.

The list of writer methods that can be mass-assigned to in #attributes=



656
657
658
# File 'lib/dm-core/model.rb', line 656

def allowed_writer_methods
  @allowed_writer_methods
end

#base_modelObject (readonly)



649
650
651
# File 'lib/dm-core/model.rb', line 649

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.



188
189
190
191
192
193
194
195
196
197
# File 'lib/dm-core/model.rb', line 188

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.



157
158
159
160
161
162
163
164
165
166
# File 'lib/dm-core/model.rb', line 157

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


46
47
48
# File 'lib/dm-core/model.rb', line 46

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.



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/dm-core/model.rb', line 209

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) }

  super
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



204
205
206
# File 'lib/dm-core/model.rb', line 204

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



173
174
175
# File 'lib/dm-core/model.rb', line 173

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



23
24
25
26
27
28
29
30
31
32
# File 'lib/dm-core/model.rb', line 23

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


79
80
81
82
83
84
85
# File 'lib/dm-core/model.rb', line 79

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)



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

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



299
300
301
# File 'lib/dm-core/model.rb', line 299

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

See Also:



341
342
343
344
345
346
347
348
# File 'lib/dm-core/model.rb', line 341

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



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

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

#const_missing(name) ⇒ 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.



718
719
720
721
722
723
724
725
726
# File 'lib/dm-core/model.rb', line 718

def const_missing(name)
  if name == :DM
    raise "#{name} prefix deprecated and no longer necessary (#{caller.first})"
  elsif name == :Resource
    Resource
  else
    super
  end
end

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

Copy a set of records from one repository to another.



542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
# File 'lib/dm-core/model.rb', line 542

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



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

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

#create!(attributes = {}) ⇒ Resource

Create a Resource, bypassing hooks



474
475
476
# File 'lib/dm-core/model.rb', line 474

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

#default_order(repository_name = default_repository_name) ⇒ Object



664
665
666
# File 'lib/dm-core/model.rb', line 664

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



659
660
661
# File 'lib/dm-core/model.rb', line 659

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


65
66
67
# File 'lib/dm-core/model.rb', line 65

def descendants
  @descendants ||= DescendantSet.new
end

#destroyBoolean

Remove all Resources from the repository



514
515
516
# File 'lib/dm-core/model.rb', line 514

def destroy
  all.destroy
end

#destroy!Boolean

Remove all Resources from the repository, bypassing validation



524
525
526
# File 'lib/dm-core/model.rb', line 524

def destroy!
  all.destroy!
end

#each(&block) ⇒ Object



321
322
323
324
# File 'lib/dm-core/model.rb', line 321

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

#fetch(*args, &block) ⇒ Object



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

def fetch(*args, &block)
  all.fetch(*args, &block)
end

#finalizeself

Finish model setup and verify it is valid



136
137
138
139
140
141
142
143
# File 'lib/dm-core/model.rb', line 136

def finalize
  finalize_relationships
  finalize_allowed_writer_methods
  assert_valid_name
  assert_valid_properties
  assert_valid_key
  self
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.



367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/dm-core/model.rb', line 367

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



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

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



433
434
435
# File 'lib/dm-core/model.rb', line 433

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


275
276
277
278
279
280
281
282
# File 'lib/dm-core/model.rb', line 275

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.

Raises:



295
296
297
# File 'lib/dm-core/model.rb', line 295

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

#inherited(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.



226
227
228
229
230
231
232
233
234
235
# File 'lib/dm-core/model.rb', line 226

def inherited(descendant)
  descendants << descendant

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

  super
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.



403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
# File 'lib/dm-core/model.rb', line 403

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.



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
636
637
638
639
640
641
642
643
644
645
646
# File 'lib/dm-core/model.rb', line 572

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 = Hash[ fields.map { |property| [ property, property.field ] } ]

  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.persistence_state = Resource::PersistenceState::Clean.new(resource) unless resource.persistence_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.persistence_state = Resource::PersistenceState::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


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

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)



127
128
129
# File 'lib/dm-core/model.rb', line 127

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)



713
714
715
# File 'lib/dm-core/model.rb', line 713

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.



681
682
683
684
685
686
687
688
689
# File 'lib/dm-core/model.rb', line 681

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



700
701
702
703
# File 'lib/dm-core/model.rb', line 700

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

#reverseObject



317
318
319
# File 'lib/dm-core/model.rb', line 317

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).



245
246
247
# File 'lib/dm-core/model.rb', line 245

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



255
256
257
# File 'lib/dm-core/model.rb', line 255

def storage_names
  @storage_names
end

#update(attributes) ⇒ Boolean

Update every Resource

Person.update(:allow_beer => true)


489
490
491
# File 'lib/dm-core/model.rb', line 489

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

#update!(attributes) ⇒ Boolean

Update every Resource, bypassing validations

Person.update!(:allow_beer => true)


504
505
506
# File 'lib/dm-core/model.rb', line 504

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

#values_at(*args) ⇒ Object



313
314
315
# File 'lib/dm-core/model.rb', line 313

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