Class: DataMapper::Property

:include:QUICKLINKS

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

    property :publish, TrueClass, :default => 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

   property :body,   Text, :accessor => :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

   property :tags,  String, :reader => :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

   property :body,     DataMapper::Types::Text, :lazy => false

   property :comment,  String, lazy => [:detailed]

   property :author,   String, lazy => [:summary,:detailed]

 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

  Widget[1].components.first.body

  Widget[1].components.first.comment

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,        Integer, :serial => true  # 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]

  User['bill']

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

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

            :minimum => 0, :maximum => 250'

   property :title, String, :nullable => false

   property :email, String, :format => :email_address

   property :title, String, :length => 255, :nullable => false

 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.

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.

Attributes

Instance Attributes

default [RW] public

Returns the value of attribute default.

getter [RW] public

Returns the value of attribute getter.

instance_variable_name [RW] public

Returns the value of attribute instance_variable_name.

model [RW] public

Returns the value of attribute model.

name [RW] public

Returns the value of attribute name.

options [RW] public

Returns the value of attribute options.

precision [RW] public

Returns the value of attribute precision.

primitive [RW] public

Returns the value of attribute primitive.

reader_visibility [RW] public

Returns the value of attribute reader_visibility.

scale [RW] public

Returns the value of attribute scale.

type [RW] public

Returns the value of attribute type.

writer_visibility [RW] public

Returns the value of attribute writer_visibility.

Constants

DEFAULT_LENGTH
50
DEFAULT_PRECISION
0
DEFAULT_SCALE
10
PROPERTY_OPTIONS
[ :public, :protected, :private, :accessor, :reader, :writer, :lazy, :default, :nullable, :key, :serial, :field, :size, :length, :format, :index, :unique_index, :check, :ordinal, :auto_validation, :validates, :unique, :lock, :track, :scale, :precision ]
TYPES
[ TrueClass, String, DataMapper::Types::Text, Float, Integer, BigDecimal, DateTime, Date, Time, Object, Class, DataMapper::Types::Discriminator ]
VISIBILITY_OPTIONS
[ :public, :protected, :private ]

Constructor Summary

private initialize(model, name, type, options = {})
[View source]


428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
# File 'dm-core/lib/dm-core/property.rb', line 428

def initialize(model, name, type, options = {})
  if Fixnum == type



    # It was decided that Integer is a more expressively names class to
    # use instead of Fixnum.  Fixnum only represents smaller numbers,
    # so there was some confusion over whether or not it would also
    # work with Bignum too (it will).  Any Integer, which includes
    # Fixnum and Bignum, can be stored in this property.
    warn "#{type} properties are deprecated.  Please use Integer instead"
    type = Integer
  end

  raise ArgumentError, "+model+ is a #{model.class}, but is not a type of Resource"                 unless Resource > model
  raise ArgumentError, "+name+ should be a Symbol, but was #{name.class}"                           unless Symbol === name
  raise ArgumentError, "+type+ was #{type.inspect}, which is not a supported type: #{TYPES * ', '}" unless TYPES.include?(type) || (DataMapper::Type > type && TYPES.include?(type.primitive))

  if (unknown_options = options.keys - PROPERTY_OPTIONS).any?



    raise ArgumentError, "+options+ contained unknown keys: #{unknown_options * ', '}"
  end

  @model                  = model
  @name                   = name.to_s.sub(/\?$/, '').to_sym
  @type                   = type
  @custom                 = DataMapper::Type > @type
  @options                = @custom ? @type.options.merge(options) : options
  @instance_variable_name = "@#{@name}"

  # TODO: This default should move to a DataMapper::Types::Text
  # Custom-Type and out of Property.
  @primitive = @options.fetch(:primitive, @type.respond_to?(:primitive) ? @type.primitive : @type)

  @getter   = TrueClass == @primitive ? "#{@name}?".to_sym : @name
  @lock     = @options.fetch(:lock,     false)
  @serial   = @options.fetch(:serial,   false)
  @key      = @options.fetch(:key,      @serial || false)
  @default  = @options.fetch(:default,  nil)
  @nullable = @options.fetch(:nullable, @key == false && @default.nil?)
  @index    = @options.fetch(:index,    false)
  @unique_index = @options.fetch(:unique_index, false)

  @lazy     = @options.fetch(:lazy,     @type.respond_to?(:lazy) ? @type.lazy : false) && !@key

  # assign attributes per-type
  if String == @primitive || Class == @primitive



    @length = @options.fetch(:length, @options.fetch(:size, DEFAULT_LENGTH))
  elsif BigDecimal == @primitive || Float == @primitive
    @scale     = @options.fetch(:scale,     DEFAULT_SCALE)
    @precision = @options.fetch(:precision, DEFAULT_PRECISION)
  end

  determine_visibility

  create_getter
  create_setter

  @model.auto_generate_validations(self) if @model.respond_to?(:auto_generate_validations)
  @model.property_serialization_setup(self) if @model.respond_to?(:property_serialization_setup)

end

Public Visibility

Public Instance Method Summary

#custom?
#default_for(resource)
#eql?(o)
#field(*args)

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

Returns:

#get(resource)

Provides a standardized getter method for the property.

#hash
#index
#inspect
#key?

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

Returns:

#lazy?

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

Returns:

#length
#lock?
#nullable?

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

Returns:

#repository(*args)
#serial?

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

Returns:

#set(resource, value)

Provides a standardized setter method for the property.

#typecast(value)

typecasts values into a primitive.

Returns:

#unique
#unique_index

Public Instance Methods Inherited from Object

validatable?

Public Instance Method Details

custom?

public custom?
[View source]


373
374
375
376
377
# File 'dm-core/lib/dm-core/property.rb', line 373

def custom?


  @custom
end

default_for

public default_for(resource)
[View source]


418
419
420
# File 'dm-core/lib/dm-core/property.rb', line 418

def default_for(resource)
  @default.respond_to?(:call) ? @default.call(resource, self) : @default
end

eql?

public eql?(o)
[View source]


309
310
311
312
313
314
315
316
317
318
# File 'dm-core/lib/dm-core/property.rb', line 309

def eql?(o)
  if o.is_a?(Property)



    return o.model == @model && o.name == @name
  else
    return false
  end
end

field

public field(*args)

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

Meta Tags

Returns:

<String> name of field in data-store

[View source]


288
289
290
# File 'dm-core/lib/dm-core/property.rb', line 288

def field(*args)
  @options.fetch(:field, repository(*args).adapter.field_naming_convention.call(name))
end

get

public get(resource)

Provides a standardized getter method for the property

Meta Tags

Raises:

<ArgumentError>

"resource should be a DataMapper::Resource, but was .…"

[View source]


382
383
384
385
# File 'dm-core/lib/dm-core/property.rb', line 382

def get(resource)
  raise ArgumentError, "+resource+ should be a DataMapper::Resource, but was #{resource.class}" unless Resource === resource
  resource.attribute_get(@name)
end

hash

public hash
[View source]


300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'dm-core/lib/dm-core/property.rb', line 300

def hash


  if @custom && !@bound



    @type.bind(self)
    @bound = true
  end

  return @model.hash + @name.hash
end

index

public index
[View source]


322
323
324
325
326
# File 'dm-core/lib/dm-core/property.rb', line 322

def index


  @index
end

inspect

public inspect
[View source]


422
423
424
425
426
# File 'dm-core/lib/dm-core/property.rb', line 422

def inspect


  "#<Property:#{@model}:#{@name}>"
end

key?

public key?

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

Meta Tags

Returns:

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

[View source]


347
348
349
350
351
# File 'dm-core/lib/dm-core/property.rb', line 347

def key?


  @key
end

lazy?

public lazy?

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

Meta Tags

Returns:

<TrueClass, FalseClass> whether or not the property is to be lazy-loaded

[View source]


336
337
338
339
340
# File 'dm-core/lib/dm-core/property.rb', line 336

def lazy?


  @lazy
end

length

public length
[View source]


317
318
319
320
321
# File 'dm-core/lib/dm-core/property.rb', line 317

def length


  @length.is_a?(Range) ? @length.max : @length
end

lock?

public lock?
[View source]


369
370
371
372
373
# File 'dm-core/lib/dm-core/property.rb', line 369

def lock?


  @lock
end

nullable?

public nullable?

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

Meta Tags

Returns:

<TrueClass, FalseClass> whether or not the property can accept ‘nil’

[View source]


365
366
367
368
369
# File 'dm-core/lib/dm-core/property.rb', line 365

def nullable?


  @nullable
end

repository

public repository(*args)
[View source]


296
297
298
# File 'dm-core/lib/dm-core/property.rb', line 296

def repository(*args)
  @model.repository(*args)
end

serial?

public serial?

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

Meta Tags

Returns:

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

[View source]


356
357
358
359
360
# File 'dm-core/lib/dm-core/property.rb', line 356

def serial?


  @serial
end

set

public set(resource, value)

Provides a standardized setter method for the property

Meta Tags

Raises:

<ArgumentError>

"resource should be a DataMapper::Resource, but was .…"

[View source]


392
393
394
395
# File 'dm-core/lib/dm-core/property.rb', line 392

def set(resource, value)
  raise ArgumentError, "+resource+ should be a DataMapper::Resource, but was #{resource.class}" unless Resource === resource
  resource.attribute_set(@name, value)
end

typecast

public typecast(value)

typecasts values into a primitive

Meta Tags

Returns:

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

[View source]


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

def typecast(value)
  return value if type === value || (value.nil? && type != TrueClass)

  if    type == TrueClass  then %w[ true 1 t ].include?(value.to_s.downcase)



  elsif type == String     then value.to_s
  elsif type == Float      then value.to_f
  elsif type == Integer    then value.to_i
  elsif type == BigDecimal then BigDecimal(value.to_s)
  elsif type == DateTime   then DateTime.parse(value.to_s)
  elsif type == Date       then Date.parse(value.to_s)
  elsif type == Time       then Time.parse(value.to_s)
  elsif type == Class      then find_const(value)
  end
end

unique

public unique
[View source]


292
293
294
295
296
# File 'dm-core/lib/dm-core/property.rb', line 292

def unique


  @unique ||= @options.fetch(:unique, @serial || @key || false)
end

unique_index

public unique_index
[View source]


326
327
328
329
330
# File 'dm-core/lib/dm-core/property.rb', line 326

def unique_index


  @unique_index
end