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

Quick Links

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 is a fundamental difference in thinking. 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.
  • 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, TrueClass, :default => false
       # Default value for new records is false
  end

By default, DataMapper supports the following primitive types:

  • TrueClass, Boolean
  • String
  • 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)

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 accessors and mutators, 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 accessor for any property can be overridden in the same manner that Ruby class accessors can be. After the property is defined, just add your custom accessor:

 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,   DataMapper::Types::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,     DataMapper::Types::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[1].components
    # loads when the post object is pulled from database, by default

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

  Widget[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[1]
     # when :id is the primary key on the users table
  User['bill']
     # when :name is the primary (natural) key on the users table

Indeces

You can add indeces 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 indeces 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 indeces 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 => { |r, p| r.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.

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

_dump   _load   custom?   default_for   eql?   field   get   get!   hash   index   inspect   key?   lazy?   lazy_load   length   new   nullable?   serial?   set   set!   set_original_value   size   typecast   unique   unique_index   value  

Included Modules

Assertions

Constants

PROPERTY_OPTIONS = [ :accessor, :reader, :writer, :lazy, :default, :nullable, :key, :serial, :field, :size, :length, :format, :index, :unique_index, :check, :ordinal, :auto_validation, :validates, :unique, :track, :precision, :scale   NOTE: check is only for psql, so maybe the postgres adapter should define its own property options. currently it will produce a warning tho since PROPERTY_OPTIONS is a constant

NOTE: PLEASE update PROPERTY_OPTIONS in DataMapper::Type when updating them here

TYPES = [ TrueClass, String, DataMapper::Types::Text, Float, Integer, BigDecimal, DateTime, Date, Time, Object, Class, DataMapper::Types::Discriminator, DataMapper::Types::Serial   FIXME: can we pull the keys from DataMapper::Adapters::DataObjectsAdapter::TYPES for this?
IMMUTABLE_TYPES = [ TrueClass, Float, Integer, BigDecimal]
VISIBILITY_OPTIONS = [ :public, :protected, :private ]
DEFAULT_LENGTH = 50
DEFAULT_PRECISION = 10
DEFAULT_SCALE_BIGDECIMAL = 0
DEFAULT_SCALE_FLOAT = nil

Attributes

default  [R] 
extra_options  [R] 
getter  [R] 
instance_variable_name  [R] 
model  [R] 
name  [R] 
options  [R] 
precision  [R] 
primitive  [R] 
reader_visibility  [R] 
scale  [R] 
track  [R] 
type  [R] 
writer_visibility  [R] 

Public Class methods

TODO: add docs @api private

Public Instance methods

TODO: add docs @api private

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

@return <String> name of field in data-store - @api semi-public

 Provides a standardized getter method for the property

 @raise <ArgumentError> "+resource+ should be a DataMapper::Resource, but was ...."

-

 @api private
 Returns whether or not the property is a key or a part of a key

 @return <TrueClass, FalseClass> whether the property is a key or a part of
   a key

-

 @api public

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

@return <TrueClass, FalseClass> whether or not the property is to be

  lazy-loaded

- @api public

 Loads lazy columns when get or set is called.

-

 @api private
 Returns whether or not the property can accept 'nil' as it's value

 @return <TrueClass, FalseClass> whether or not the property can accept 'nil'

-

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

 @return <TrueClass, FalseClass> whether or not the property is "serial"

-

 @api public
 Provides a standardized setter method for the property

 @raise <ArgumentError> "+resource+ should be a DataMapper::Resource, but was ...."

-

 @api private
size()

Alias for length

 typecasts values into a primitive

 @return <TrueClass, String, Float, Integer, BigDecimal, DateTime, Date, Time
   Class> the primitive data-type, defaults to TrueClass

-

 @api private

[Validate]