Class: DataMapper::Property
- Object
- 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
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
Public Instance Method Details
custom?
373 374 375 376 377
# File 'dm-core/lib/dm-core/property.rb', line 373 def custom? @custom end
default_for
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?
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
Supplies the field in the data-store which the property corresponds to
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
Provides a standardized getter method for the property
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
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
322 323 324 325 326
# File 'dm-core/lib/dm-core/property.rb', line 322 def index @index end
inspect
422 423 424 425 426
# File 'dm-core/lib/dm-core/property.rb', line 422 def inspect "#<Property:#{@model}:#{@name}>" end
key?
Returns whether or not the property is a key or a part of a key
347 348 349 350 351
# File 'dm-core/lib/dm-core/property.rb', line 347 def key? @key end
lazy?
Returns whether or not the property is to be lazy-loaded
336 337 338 339 340
# File 'dm-core/lib/dm-core/property.rb', line 336 def lazy? @lazy end
length
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?
369 370 371 372 373
# File 'dm-core/lib/dm-core/property.rb', line 369 def lock? @lock end
nullable?
Returns whether or not the property can accept ‘nil’ as it’s value
365 366 367 368 369
# File 'dm-core/lib/dm-core/property.rb', line 365 def nullable? @nullable end
repository
296 297 298
# File 'dm-core/lib/dm-core/property.rb', line 296 def repository(*args) @model.repository(*args) end
serial?
Returns whether or not the property is "serial" (auto-incrementing)
356 357 358 359 360
# File 'dm-core/lib/dm-core/property.rb', line 356 def serial? @serial end
set
Provides a standardized setter method for the property
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
typecasts values into a primitive
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
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
326 327 328 329 330
# File 'dm-core/lib/dm-core/property.rb', line 326 def unique_index @unique_index end