Class: DataMapper::Property

Class | DataMapper::Property
In: | lib/dm-core/adapters/oracle_adapter.rb lib/dm-core/property.rb
Parent: | Object

# Quick Links

- Setup and Configuration - DataMapper
- Finders and CRUD -
- Properties - DataMapper::Property
- FAQ
- Contact Us
  - Website - www.datamapper.org
  - Bug Reports - datamapper.lighthouseapp.com
  - IRC Channel - `#datamapper` on irc.freenode.net
  - Mailing List - groups.google.com/group/datamapper/

# Properties

Properties for a model are not derived from a database structure, but instead explicitly declared inside your model class definitions. These properties then map (or, if using automigrate, generate) fields in your repository/database.

If you are coming to DataMapper from another ORM framework, such as ActiveRecord, this may be a fundamental difference in thinking to you. However, there are several advantages to defining your properties in your models:

- information about your model is centralized in one place: rather than having to dig out migrations, xml or other configuration files.
- use of mixins can be applied to model properties: better code reuse
- having information centralized in your models, encourages you and the developers on your team to take a model-centric view of development.
- it provides the ability to use Ruby‘s access control functions.
- and, because DataMapper only cares about properties explicitly defined

in

      your models, DataMapper plays well with legacy databases, and shares
      databases easily with other applications.

## Declaring Properties

Inside your class, you call the property method for each property you want to add. The only two required arguments are the name and type, everything else is optional.

      class Post
        include DataMapper::Resource

        property :title,   String,  :nullable => false  # Cannot be null
        property :publish, Boolean, :default => false   # Default value for new records is false
      end

By default, DataMapper supports the following primitive (Ruby) types also called core types:

- Boolean
- String (default length is 50)
- Text (limit of 65k characters by default)
- Float
- Integer
- BigDecimal
- DateTime
- Date
- Time
- Object (marshalled out during serialization)
- Class (datastore primitive is the same as String. Used for Inheritance)

Other types are known as custom types.

For more information about available Types, see DataMapper::Type

## Limiting Access

Property access control is uses the same terminology Ruby does. Properties are public by default, but can also be declared private or protected as needed (via the :accessor option).

     class Post
      include DataMapper::Resource

       property :title, String, :accessor => :private    # Both reader and writer are private
       property :body,  Text,   :accessor => :protected  # Both reader and writer are protected
     end

Access control is also analogous to Ruby attribute readers and writers, and can be declared using :reader and :writer, in addition to :accessor.

     class Post
       include DataMapper::Resource

       property :title, String, :writer => :private    # Only writer is private
       property :tags,  String, :reader => :protected  # Only reader is protected
     end

## Overriding Accessors

The reader/writer for any property can be overridden in the same manner that Ruby attr readers/writers can be. After the property is defined, just add your custom reader or writer:

     class Post
       include DataMapper::Resource

       property :title, String

       def title=(new_title)
         raise ArgumentError if new_title != 'Luke is Awesome'
         @title = new_title
       end
     end

## Lazy Loading

By default, some properties are not loaded when an object is fetched in DataMapper. These lazily loaded properties are fetched on demand when their accessor is called for the first time (as it is often unnecessary to instantiate -every- property -every- time an object is loaded). For instance, DataMapper::Types::Text fields are lazy loading by default, although you can over-ride this behavior if you wish:

Example:

     class Post
       include DataMapper::Resource

       property :title, String  # Loads normally
       property :body,  Text    # Is lazily loaded by default
     end

If you want to over-ride the lazy loading on any field you can set it to a context or false to disable it with the :lazy option. Contexts allow multipule lazy properties to be loaded at one time. If you set :lazy to true, it is placed in the :default context

     class Post
       include DataMapper::Resource

       property :title,   String                                    # Loads normally
       property :body,    Text,   :lazy => false                    # The default is now over-ridden
       property :comment, String, :lazy => [ :detailed ]            # Loads in the :detailed context
       property :author,  String, :lazy => [ :summary, :detailed ]  # Loads in :summary & :detailed context
     end

Delaying the request for lazy-loaded attributes even applies to objects accessed through associations. In a sense, DataMapper anticipates that you will likely be iterating over objects in associations and rolls all of the load commands for lazy-loaded properties into one request from the database.

Example:

      Widget.get(1).components
        # loads when the post object is pulled from database, by default

      Widget.get(1).components.first.body
        # loads the values for the body property on all objects in the
        # association, rather than just this one.

      Widget.get(1).components.first.comment
        # loads both comment and author for all objects in the association
        # since they are both in the :detailed context

## Keys

Properties can be declared as primary or natural keys on a table. You should a property as the primary key of the table:

Examples:

     property :id,        Serial                # auto-incrementing key
     property :legacy_pk, String, :key => true  # 'natural' key

This is roughly equivalent to ActiveRecord‘s `set_primary_key`, though non-integer data types may be used, thus DataMapper supports natural keys. When a property is declared as a natural key, accessing the object using the indexer syntax `Class[key]` remains valid.

      User.get(1)
         # when :id is the primary key on the users table
      User.get('bill')
         # when :name is the primary (natural) key on the users table

## Indices

You can add indices for your properties by using the `:index` option. If you use `true` as the option value, the index will be automatically named. If you want to name the index yourself, use a symbol as the value.

      property :last_name,  String, :index => true
      property :first_name, String, :index => :name

You can create multi-column composite indices by using the same symbol in all the columns belonging to the index. The columns will appear in the index in the order they are declared.

      property :last_name,  String, :index => :name
      property :first_name, String, :index => :name
         # => index on (last_name, first_name)

If you want to make the indices unique, use `:unique_index` instead of `:index`

## Inferred Validations

If you require the dm-validations plugin, auto-validations will automatically be mixed-in in to your model classes: validation rules that are inferred when properties are declared with specific column restrictions.

     class Post
       include DataMapper::Resource

       property :title, String, :length => 250
         # => infers 'validates_length :title,
                :minimum => 0, :maximum => 250'

       property :title, String, :nullable => false
         # => infers 'validates_present :title

       property :email, String, :format => :email_address
         # => infers 'validates_format :email, :with => :email_address

       property :title, String, :length => 255, :nullable => false
         # => infers both 'validates_length' as well as
         #    'validates_present'
         #    better: property :title, String, :length => 1..255

     end

This functionality is available with the dm-validations gem, part of the dm-more bundle. For more information about validations, check the documentation for dm-validations.

## Default Values

To set a default for a property, use the `:default` key. The property will be set to the value associated with that key the first time it is accessed, or when the resource is saved if it hasn‘t been set with another value already. This value can be a static value, such as ‘hello’ but it can also be a proc that will be evaluated when the property is read before its value has been set. The property is set to the return of the proc. The proc is passed two values, the resource the property is being set for and the property itself.

      property :display_name, String, :default => { |resource, property| resource.login }

Word of warning. Don‘t try to read the value of the property you‘re setting the default for in the proc. An infinite loop will ensue.

## Embedded Values

As an alternative to extraneous has_one relationships, consider using an EmbeddedValue.

## Property options reference

     :accessor            if false, neither reader nor writer methods are
                          created for this property

     :reader              if false, reader method is not created for this property

     :writer              if false, writer method is not created for this property

     :lazy                if true, property value is only loaded when on first read
                          if false, property value is always loaded
                          if a symbol, property value is loaded with other properties
                          in the same group

     :default             default value of this property

     :nullable            if true, property may have a nil value on save

     :key                 name of the key associated with this property.

     :serial              if true, field value is auto incrementing

     :field               field in the data-store which the property corresponds to

     :length              string field length

     :format              format for autovalidation. Use with dm-validations plugin.

     :index               if true, index is created for the property. If a Symbol, index
                          is named after Symbol value instead of being based on property name.

     :unique_index        true specifies that index on this property should be unique

     :auto_validation     if true, automatic validation is performed on the property

     :validates           validation context. Use together with dm-validations.

     :unique              if true, property column is unique. Properties of type Serial
                          are unique by default.

     :precision           Indicates the number of significant digits. Usually only makes sense
                          for float type properties. Must be >= scale option value. Default is 10.

     :scale               The number of significant digits to the right of the decimal point.
                          Only makes sense for float type properties. Must be > 0.
                          Default is nil for Float type and 10 for BigDecimal type.

     All other keys you pass to +property+ method are stored and available
     as options[:extra_keys].

## Misc. Notes

- Properties declared as strings will default to a length of 50, rather than 255 (typical max varchar column size). To overload the default, pass `:length => 255` or `:length => 0..255`. Since DataMapper does not introspect for properties, this means that legacy database tables may need their `String` columns defined with a `:length` so that DM does not apply an un-needed length validation, or allow overflow.
- You may declare a Property with the data-type of `Class`. see SingleTableInheritance for more on how to use `Class` columns.

### Methods

custom? default? default_for field get get! hash index inspect key? lazy? lazy_load lazy_load_properties length loaded? new nullable? primitive? properties serial? set set! set_original_value typecast unique? unique_index valid? value

### Included Modules

Extlib::Assertions

### Constants

OPTIONS | = | [ :accessor, :reader, :writer, :lazy, :default, :nullable, :key, :serial, :field, :size, :length, :format, :index, :unique_index, :auto_validation, :validates, :unique, :precision, :scale, :min, :max |  | NOTE: PLEASE update OPTIONS in DataMapper::Type when updating them here
PRIMITIVES | = | [ TrueClass, String, Float, Integer, BigDecimal, DateTime, Date, Time, Object, Class, ].to_set.freeze
VISIBILITY_OPTIONS | = | [ :public, :protected, :private ].to_set.freeze |  | Possible :visibility option values
DEFAULT_LENGTH | = | 50
DEFAULT_PRECISION | = | 10
DEFAULT_SCALE_BIGDECIMAL | = | 0
DEFAULT_SCALE_FLOAT | = | nil
DEFAULT_NUMERIC_MIN | = | 0
DEFAULT_NUMERIC_MAX | = | 2**31-1

### Attributes

default | [R] |
instance_variable_name | [R] |
max | [R] |
min | [R] |
model | [R] |
name | [R] |
options | [R] |
precision | [R] |
primitive | [R] |
reader_visibility | [R] |
repository_name | [R] |
scale | [R] |
type | [R] |
writer_visibility | [R] |

### Public Class methods

new(model, name, type, options = {})

TODO: document @api semipublic

### Public Instance methods

custom?()

Returns whether or not the property is custom (not provided by dm-core)

@return [Boolean]

      whether or not the property is custom

@api public

default?()

Returns true if the property has a default value

@return [Boolean]

      true if the property has a default value

@api semipublic

default_for(resource)

Returns a default value of the property for given resource.

When default value is a callable object, it is called with resource and property passed as arguments.

@param [Resource] resource

      the model instance for which the default is to be set

@return [Object]

      the default value of this property for +resource+

@api semipublic

field(repository_name = nil)

Supplies the field in the data-store which the property corresponds to

@return [String] name of field in data-store

@api semipublic

get(resource)

Standardized reader method for the property

@param [Resource] resource

      model instance for which this property is to be loaded

@return [Object]

      the value of this property for the provided instance

@raise [ArgumentError] "`resource` should be a Resource, but was .…"

@api private

get!(resource)

Fetch the ivar value in the resource

@param [Resource] resource

      model instance for which this property is to be unsafely loaded

@return [Object]

      current @ivar value of this property in +resource+

@api private

hash()

Returns the hash of the property name

This is necessary to allow comparisons between different properties in different models, having the same base model

@return [Integer]

      the property name hash

@api semipublic

index()

Returns index name if property has index.

@return [true, Symbol, Array, nil]

      returns true if property is indexed by itself
      returns a Symbol if the property is indexed with other properties
      returns an Array if the property belongs to multiple indexes
      returns nil if the property does not belong to any indexes

@api public

inspect()

Returns a concise string representation of the property instance.

@return [String]

      Concise string representation of the property instance.

@api public

key?()

Returns whether or not the property is a key or a part of a key

@return [Boolean]

      true if the property is a key or a part of a key

@api public

lazy?()

Returns whether or not the property is to be lazy-loaded

@return [Boolean]

      true if the property is to be lazy-loaded

@api public

lazy_load(resource)

Loads lazy columns when get or set is called.

@param [Resource] resource

      model instance for which lazy loaded attribute are loaded

@api private

lazy_load_properties()

TODO: document @api private

length()

Returns maximum property length (if applicable). This usually only makes sense when property is of type Range or custom type.

@return [Integer, NilClass]

      the maximum length of this property

@api semipublic

loaded?(resource)

Check if the attribute corresponding to the property is loaded

@param [Resource] resource

      model instance for which the attribute is to be tested

@return [Boolean]

      true if the attribute is loaded in the resource

@api private

nullable?()

Returns whether or not the property can accept ‘nil’ as it‘s value

@return [Boolean]

      whether or not the property can accept 'nil'

@api public

primitive?(value)

Test a value to see if it matches the primitive type

@param [Object] value

      value to test

@return [Boolean]

      true if the value is the correct type

@api semipublic

properties()

TODO: document @api private

serial?()

Returns whether or not the property is "serial" (auto-incrementing)

@return [Boolean]

      whether or not the property is "serial"

@api public

set(resource, value)

Provides a standardized setter method for the property

@param [Resource] resource

      the resource to get the value from

@param [Object] value

      the value to set in the resource

@return [Object]

      +value+ after being typecasted according to this property's primitive

@raise [ArgumentError] "`resource` should be a Resource, but was .…"

@api private

set!(resource, value)

Set the ivar value in the resource

@param [Resource] resource

      the resource to set

@param [Object] value

      the value to set in the resource

@return [Object]

      the value set in the resource

@api private

set_original_value(resource, original)

Sets original value of the property on given resource. When property is set on DataMapper resource instance, original value is preserved. This makes possible to track dirty attributes and save only those really changed, and avoid extra queries to the data source in certain situations.

@param [Resource] resource

      model instance for which to set the original value

@param [Object] original

      value to set as original value for this property in +resource+

@api private

typecast(value)

typecasts values into a primitive (Ruby class that backs DataMapper property type). If property type can handle typecasting, it is delegated. How typecasting is perfomed, depends on the primitive of the type.

If type‘s primitive is a TrueClass, values of 1, t and true are casted to true.

For String primitive, `to_s` is called on value.

For Float primitive, `to_f` is called on value but only if value is a number otherwise value is returned.

For Integer primitive, `to_i` is called on value but only if value is a number, otherwise value is returned.

For BigDecimal primitive, `to_d` is called on value but only if value is a number, otherwise value is returned.

Casting to DateTime, Time and Date can handle both hashes with keys like :day or :hour and strings in format methods like Time.parse can handle.

@param [to_s, to_f, to_i, to_d, Hash] value

      the value to typecast

@return [rue, String, Float, Integer, BigDecimal, DateTime, Date, Time, Class]

      The typecasted +value+

@api semipublic

unique?()

Returns true if property is unique. Serial properties and keys are unique by default.

@return [Boolean]

      true if property has uniq index defined, false otherwise

@api public

unique_index()

Returns true if property has unique index. Serial properties and keys are unique by default.

@return [true, Symbol, Array, nil]

      returns true if property is indexed by itself
      returns a Symbol if the property is indexed with other properties
      returns an Array if the property belongs to multiple indexes
      returns nil if the property does not belong to any indexes

@api public

valid?(value)

Test the value to see if it is a valid value for this Property

@param [Object] value

      the value to be tested

@return [Boolean]

      true if the value is valid

@api semipulic

value(value)

Returns given value unchanged for core types and uses `dump` method of the property type for custom types.

@param [Object] value

      the value to be converted into a storeable (ie., primitive) value

@return [Object]

      the primitive value to be stored in the repository for +val+

@api semipublic

[Validate]