module DataMapper::Model
Constants
- INVALID_WRITER_METHODS
- WRITER_METHOD_REGEXP
Attributes
The list of writer methods that can be mass-assigned to in attributes=
@return [Set]
@api private
@api semipublic
Public Class Methods
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.
@param [Module] extensions
List of modules that will extend the model after it is extended by Model
@return [Boolean]
whether or not the inclusions have been successfully appended to the list
@api semipublic
# File lib/dm-core/model.rb, line 187 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
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.
@param [Module] inclusions
the module that is to be appended to the module after Resource
@return [Boolean]
true if the inclusions have been successfully appended to the list
@api semipublic
# File lib/dm-core/model.rb, line 156 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
Return all models that extend the Model module
class Foo include DataMapper::Resource end DataMapper::Model.descendants.first #=> Foo
@return [DescendantSet]
Set containing the descendant models
@api semipublic
# File lib/dm-core/model.rb, line 46 def self.descendants @descendants ||= DescendantSet.new end
@api private
# File lib/dm-core/model.rb, line 208 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
The current registered extra extensions
@return [Set]
@api private
# File lib/dm-core/model.rb, line 203 def self.extra_extensions @extra_extensions ||= [] end
The current registered extra inclusions
@return [Set]
@api private
# File lib/dm-core/model.rb, line 172 def self.extra_inclusions @extra_inclusions ||= [] end
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
@param [#to_s] name
the name of the new model
@param [Object] namespace
the namespace that will hold the new model
@param [Proc] block
a block that will be eval'd in the context of the new Model class
@return [Model]
the newly created Model class
@api private
# 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 " include DataMapper::Resource ", __FILE__, __LINE__ + 1 model.instance_eval(&block) if block model end
Return if DataMapper::Resource#save should raise an exception on save failures (globally)
This is false by default.
DataMapper::Model.raise_on_save_failure # => false
@return [Boolean]
true if a failure in Resource#save should raise an exception
@api public
# 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
Specify if DataMapper::Resource#save should raise an exception on save failures (globally)
@param [Boolean]
a boolean that if true will cause Resource#save to raise an exception
@return [Boolean]
true if a failure in Resource#save should raise an exception
@api public
# 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
Public Instance Methods
# File lib/dm-core/model.rb, line 294 def [](*args) all[*args] end
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
@param [Hash] query
A hash describing the conditions and order for the query
@return [Collection]
A set of records found matching the conditions in +query+
@see Collection
@api public
# File lib/dm-core/model.rb, line 337 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
# File lib/dm-core/model.rb, line 300 def at(*args) all.at(*args) end
Copy a set of records from one repository to another.
@param [String] source_repository_name
The name of the Repository the resources should be copied _from_
@param [String] target_repository_name
The name of the Repository the resources should be copied _to_
@param [Hash] query
The conditions with which to find the records to copy. These conditions are merged with Model.query
@return [Collection]
A Collection of the Resource instances created in the operation
@api public
# File lib/dm-core/model.rb, line 538 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
@api semipublic
# File lib/dm-core/model.rb, line 660 def default_order(repository_name = default_repository_name) @default_order[repository_name] ||= key(repository_name).map { |property| Query::Direction.new(property) }.freeze end
@api semipublic
# File lib/dm-core/model.rb, line 655 def default_repository_name Repository.default_name end
Return all models that inherit from a Model
class Foo include DataMapper::Resource end class Bar < Foo end Foo.descendants.first #=> Bar
@return [Set]
Set containing the descendant classes
@api semipublic
# File lib/dm-core/model.rb, line 65 def descendants @descendants ||= DescendantSet.new end
Remove all Resources from the repository
@return [Boolean]
true if the resources were successfully destroyed
@api public
# File lib/dm-core/model.rb, line 510 def destroy all.destroy end
Remove all Resources from the repository, bypassing validation
@return [Boolean]
true if the resources were successfully destroyed
@api public
# File lib/dm-core/model.rb, line 520 def destroy! all.destroy! end
# File lib/dm-core/model.rb, line 316 def each(&block) return to_enum unless block_given? all.each(&block) self end
# File lib/dm-core/model.rb, line 304 def fetch(*args, &block) all.fetch(*args, &block) end
Finish model setup and verify it is valid
@return [undefined]
@api public
# 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 end
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.
@param [Integer] limit (optional)
limit the returned Collection to a specific number of entries
@param [Hash] query (optional)
scope the returned Resource or Collection to the supplied query
@return [Resource, Collection]
The first resource in the entries of this collection, or a new collection whose query has been merged
@api public
# File lib/dm-core/model.rb, line 363 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
Finds the first Resource by conditions, or creates a new Resource with the attributes if none found
@param [Hash] conditions
The conditions to be used to search
@param [Hash] attributes
The attributes to be used to create the record of none is found.
@return [Resource]
The instance found by +query+, or created with +attributes+ if none found
@api public
# File lib/dm-core/model.rb, line 444 def first_or_create(conditions = {}, attributes = {}) first(conditions) || create(conditions.merge(attributes)) end
Finds the first Resource by conditions, or initializes a new Resource with the attributes if none found
@param [Hash] conditions
The conditions to be used to search
@param [Hash] attributes
The attributes to be used to create the record of none is found.
@return [Resource]
The instance found by +query+, or created with +attributes+ if none found
@api public
# File lib/dm-core/model.rb, line 429 def first_or_new(conditions = {}, attributes = {}) first(conditions) || new(conditions.merge(attributes)) end
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
@param [Object] *key
The primary key or keys to use for lookup
@return [Resource, nil]
A single model that was found If no instance was found matching +key+
@api public
# File lib/dm-core/model.rb, line 270 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
Grab a single record just like get, but raise an ObjectNotFoundError if the record doesn't exist.
@param [Object] *key
The primary key or keys to use for lookup
@return [Resource]
A single model that was found
@raise [ObjectNotFoundError]
The record was not found
@api public
# File lib/dm-core/model.rb, line 290 def get!(*key) get(*key) || raise(ObjectNotFoundError, "Could not find #{self.name} with key #{key.inspect}") end
@api private
# File lib/dm-core/model.rb, line 223 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) end
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.
@param [Integer] limit (optional)
limit the returned Collection to a specific number of entries
@param [Hash] query (optional)
scope the returned Resource or Collection to the supplied query
@return [Resource, Collection]
The last resource in the entries of this collection, or a new collection whose query has been merged
@api public
# File lib/dm-core/model.rb, line 399 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
Loads an instance of this Model, taking into account IdentityMap lookup, inheritance columns(s) and Property typecasting.
@param [Enumerable(Object)] records
an Array of Resource or Hashes to load a Resource with
@return [Resource]
the loaded Resource instance
@api semipublic
# File lib/dm-core/model.rb, line 568 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
Return if DataMapper::Resource#save should raise an exception on save failures (per-model)
This delegates to ::raise_on_save_failure by default.
User.raise_on_save_failure # => false
@return [Boolean]
true if a failure in Resource#save should raise an exception
@api public
# 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
Specify if DataMapper::Resource#save should raise an exception on save failures (per-model)
@param [Boolean]
a boolean that if true will cause Resource#save to raise an exception
@return [Boolean]
true if a failure in Resource#save should raise an exception
@api public
# 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
Gets the current Set of repositories for which this Model has been defined (beyond default)
@return [Set]
The Set of repositories for which this Model has been defined (beyond default)
@api private
# File lib/dm-core/model.rb, line 709 def repositories [ repository ].to_set + @properties.keys.map { |repository_name| DataMapper.repository(repository_name) } end
Get the repository with a given name, or the default one for the current context, or the default one for this class.
@param [Symbol] name
the name of the repository wanted
@param [Block] block
block to execute with the fetched repository as parameter
@return [Object, Respository]
whatever the block returns, if given a block, otherwise the requested repository.
@api private
# File lib/dm-core/model.rb, line 677 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
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
@return [String]
the current repository name to use for this Model
@api private
# File lib/dm-core/model.rb, line 696 def repository_name context = Repository.context context.any? ? context.last.name : default_repository_name end
# File lib/dm-core/model.rb, line 312 def reverse all.reverse end
Gets the name of the storage receptacle for this resource in the given Repository (ie., table name, for database stores).
@return [String]
the storage name (ie., table name, for database stores) associated with this resource in the given repository
@api public
# File lib/dm-core/model.rb, line 240 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
the names of the storage receptacles for this resource across all repositories
@return [Hash(Symbol => String)]
All available names of storage receptacles
@api public
# File lib/dm-core/model.rb, line 250 def storage_names @storage_names end
Update every Resource
Person.update(:allow_beer => true)
@param [Hash] attributes
attributes to update with
@return [Boolean]
true if the resources were successfully updated
@api public
# File lib/dm-core/model.rb, line 485 def update(attributes) all.update(attributes) end
Update every Resource, bypassing validations
Person.update!(:allow_beer => true)
@param [Hash] attributes
attributes to update with
@return [Boolean]
true if the resources were successfully updated
@api public
# File lib/dm-core/model.rb, line 500 def update!(attributes) all.update!(attributes) end
# File lib/dm-core/model.rb, line 308 def values_at(*args) all.values_at(*args) end
Private Instance Methods
@api private
# File lib/dm-core/model.rb, line 716 def _create(attributes, execute_hooks = true) resource = new(attributes) resource.__send__(execute_hooks ? :save : :save!) resource end
Test if the model has a valid key
@return [undefined]
@raise [IncompleteModelError]
raised if the model does not have a valid key
@api private
# File lib/dm-core/model.rb, line 863 def assert_valid_key if key(repository_name).empty? raise IncompleteModelError, "#{name} must have a key to be valid" end end
Raises an exception if get receives the wrong number of arguments
@param [Array] key
the key value
@return [undefined]
@raise [UpdateConflictError]
raise if the resource is dirty
@api private
# File lib/dm-core/model.rb, line 816 def assert_valid_key_size(key) expected_key_size = self.key(repository_name).size actual_key_size = key.size if actual_key_size != expected_key_size raise ArgumentError, "The number of arguments for the key is invalid, expected #{expected_key_size} but was #{actual_key_size}" end end
Test if the model name is valid
@return [undefined]
@api private
# File lib/dm-core/model.rb, line 830 def assert_valid_name if name.to_s.strip.empty? raise IncompleteModelError, "#{inspect} must have a name" end end
Test if the model has properties
A model may also be valid if it has at least one m:1 relationships which will add inferred foreign key properties.
@return [undefined]
@raise [IncompleteModelError]
raised if the model has no properties
@api private
# File lib/dm-core/model.rb, line 847 def assert_valid_properties repository_name = self.repository_name if properties(repository_name).empty? && !relationships(repository_name).any? { |relationship| relationship.kind_of?(Associations::ManyToOne::Relationship) } raise IncompleteModelError, "#{name} must have at least one property or many to one relationship to be valid" end end
@api private
# File lib/dm-core/model.rb, line 723 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
@api private
# File lib/dm-core/model.rb, line 734 def default_storage_name base_model.name end
Initialize the list of allowed writer methods
@return [undefined]
@api private
# File lib/dm-core/model.rb, line 791 def finalize_allowed_writer_methods @allowed_writer_methods = public_instance_methods.map { |method| method.to_s }.grep(WRITER_METHOD_REGEXP).to_set @allowed_writer_methods -= INVALID_WRITER_METHODS @allowed_writer_methods.freeze end
Initialize all foreign key properties established by relationships
@return [undefined]
@api private
# File lib/dm-core/model.rb, line 782 def finalize_relationships relationships(repository_name).each { |relationship| relationship.finalize } end
Initializes a new Collection
@return [Collection]
A new Collection object
@api private
# File lib/dm-core/model.rb, line 744 def new_collection(query, resources = nil, &block) Collection.new(query, resources, &block) end
@api private TODO: move the logic to create relative query into Query
# File lib/dm-core/model.rb, line 750 def scoped_query(query) if query.kind_of?(Query) query.dup else repository = if query.key?(:repository) query = query.dup repository = query.delete(:repository) if repository.kind_of?(Symbol) DataMapper.repository(repository) else repository end else self.repository end query = self.query.merge(query) if self.query.repository == repository query else repository.new_query(self, query.options) end end end