Class: DataMapper::Property
- Inherits:
-
Object
- Object
- DataMapper::Property
- Includes:
- Assertions
- Defined in:
- lib/dm-core/property.rb
Overview
: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
# 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 theirString
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 useClass
columns.
Constant Summary collapse
- PROPERTY_OPTIONS =
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
[ :accessor, :reader, :writer, :lazy, :default, :nullable, :key, :serial, :field, :size, :length, :format, :index, :unique_index, :check, :ordinal, :auto_validation, :validates, :unique, :track, :precision, :scale ]
- TYPES =
FIXME: can we pull the keys from DataMapper::Adapters::DataObjectsAdapter::TYPES for this?
[ TrueClass, String, DataMapper::Types::Text, Float, Integer, BigDecimal, DateTime, Date, Time, Object, Class, DataMapper::Types::Discriminator, DataMapper::Types::Serial ]
- 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
Instance Attribute Summary collapse
-
#default ⇒ Object
readonly
Returns the value of attribute default.
-
#extra_options ⇒ Object
readonly
Returns the value of attribute extra_options.
-
#getter ⇒ Object
readonly
Returns the value of attribute getter.
-
#instance_variable_name ⇒ Object
readonly
Returns the value of attribute instance_variable_name.
-
#model ⇒ Object
readonly
Returns the value of attribute model.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
-
#precision ⇒ Object
readonly
Returns the value of attribute precision.
-
#primitive ⇒ Object
readonly
Returns the value of attribute primitive.
-
#reader_visibility ⇒ Object
readonly
Returns the value of attribute reader_visibility.
-
#scale ⇒ Object
readonly
Returns the value of attribute scale.
-
#track ⇒ Object
readonly
Returns the value of attribute track.
-
#type ⇒ Object
readonly
Returns the value of attribute type.
-
#writer_visibility ⇒ Object
readonly
Returns the value of attribute writer_visibility.
Class Method Summary collapse
-
._load(marshalled) ⇒ Object
private
TODO: add docs.
Instance Method Summary collapse
-
#_dump ⇒ Object
private
TODO: add docs.
- #custom? ⇒ Boolean
- #default_for(resource) ⇒ Object
- #eql?(o) ⇒ Boolean
-
#field(repository_name = nil) ⇒ String
Supplies the field in the data-store which the property corresponds to.
-
#get(resource) ⇒ Object
private
Provides a standardized getter method for the property.
- #get!(resource) ⇒ Object
- #hash ⇒ Object
- #index ⇒ Object
- #inspect ⇒ Object
-
#key? ⇒ TrueClass, FalseClass
Returns whether or not the property is a key or a part of a key.
-
#lazy? ⇒ TrueClass, FalseClass
Returns whether or not the property is to be lazy-loaded.
-
#lazy_load(resource) ⇒ Object
private
Loads lazy columns when get or set is called.
- #length ⇒ Object (also: #size)
-
#nullable? ⇒ TrueClass, FalseClass
Returns whether or not the property can accept ‘nil’ as it’s value.
-
#serial? ⇒ TrueClass, FalseClass
Returns whether or not the property is “serial” (auto-incrementing).
-
#set(resource, value) ⇒ Object
private
Provides a standardized setter method for the property.
- #set!(resource, value) ⇒ Object
- #set_original_value(resource, val) ⇒ Object
-
#typecast(value) ⇒ TrueClass, ...
private
typecasts values into a primitive.
- #unique ⇒ Object
- #unique_index ⇒ Object
- #value(val) ⇒ Object
Methods included from Assertions
Instance Attribute Details
#default ⇒ Object (readonly)
Returns the value of attribute default.
295 296 297 |
# File 'lib/dm-core/property.rb', line 295 def default @default end |
#extra_options ⇒ Object (readonly)
Returns the value of attribute extra_options.
295 296 297 |
# File 'lib/dm-core/property.rb', line 295 def @extra_options end |
#getter ⇒ Object (readonly)
Returns the value of attribute getter.
295 296 297 |
# File 'lib/dm-core/property.rb', line 295 def getter @getter end |
#instance_variable_name ⇒ Object (readonly)
Returns the value of attribute instance_variable_name.
295 296 297 |
# File 'lib/dm-core/property.rb', line 295 def instance_variable_name @instance_variable_name end |
#model ⇒ Object (readonly)
Returns the value of attribute model.
295 296 297 |
# File 'lib/dm-core/property.rb', line 295 def model @model end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
295 296 297 |
# File 'lib/dm-core/property.rb', line 295 def name @name end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
295 296 297 |
# File 'lib/dm-core/property.rb', line 295 def @options end |
#precision ⇒ Object (readonly)
Returns the value of attribute precision.
295 296 297 |
# File 'lib/dm-core/property.rb', line 295 def precision @precision end |
#primitive ⇒ Object (readonly)
Returns the value of attribute primitive.
295 296 297 |
# File 'lib/dm-core/property.rb', line 295 def primitive @primitive end |
#reader_visibility ⇒ Object (readonly)
Returns the value of attribute reader_visibility.
295 296 297 |
# File 'lib/dm-core/property.rb', line 295 def reader_visibility @reader_visibility end |
#scale ⇒ Object (readonly)
Returns the value of attribute scale.
295 296 297 |
# File 'lib/dm-core/property.rb', line 295 def scale @scale end |
#track ⇒ Object (readonly)
Returns the value of attribute track.
295 296 297 |
# File 'lib/dm-core/property.rb', line 295 def track @track end |
#type ⇒ Object (readonly)
Returns the value of attribute type.
295 296 297 |
# File 'lib/dm-core/property.rb', line 295 def type @type end |
#writer_visibility ⇒ Object (readonly)
Returns the value of attribute writer_visibility.
295 296 297 |
# File 'lib/dm-core/property.rb', line 295 def writer_visibility @writer_visibility end |
Class Method Details
._load(marshalled) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
TODO: add docs
523 524 525 526 |
# File 'lib/dm-core/property.rb', line 523 def self._load(marshalled) repository, model, name = Marshal.load(marshalled) model.properties(repository.name)[name] end |
Instance Method Details
#_dump ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
TODO: add docs
517 518 519 |
# File 'lib/dm-core/property.rb', line 517 def _dump(*) Marshal.dump([ repository, model, name ]) end |
#custom? ⇒ Boolean
380 381 382 |
# File 'lib/dm-core/property.rb', line 380 def custom? @custom end |
#default_for(resource) ⇒ Object
503 504 505 |
# File 'lib/dm-core/property.rb', line 503 def default_for(resource) @default.respond_to?(:call) ? @default.call(resource, self) : @default end |
#eql?(o) ⇒ Boolean
321 322 323 324 325 326 327 |
# File 'lib/dm-core/property.rb', line 321 def eql?(o) if o.is_a?(Property) return o.model == @model && o.name == @name else return false end end |
#field(repository_name = nil) ⇒ String
Supplies the field in the data-store which the property corresponds to
-
304 305 306 |
# File 'lib/dm-core/property.rb', line 304 def field(repository_name = nil) @field || @fields[repository_name] ||= self.model.field_naming_convention(repository_name).call(self) end |
#get(resource) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Provides a standardized getter method for the property
-
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 |
# File 'lib/dm-core/property.rb', line 389 def get(resource) lazy_load(resource) value = get!(resource) set_original_value(resource, value) # [YK] Why did we previously care whether options[:default] is nil. # The default value of nil will be applied either way if value.nil? && resource.new_record? && !resource.attribute_loaded?(name) value = default_for(resource) set(resource, value) end value end |
#get!(resource) ⇒ Object
406 407 408 |
# File 'lib/dm-core/property.rb', line 406 def get!(resource) resource.instance_variable_get(instance_variable_name) end |
#hash ⇒ Object
312 313 314 315 316 317 318 319 |
# File 'lib/dm-core/property.rb', line 312 def hash if @custom && !@bound @type.bind(self) @bound = true end return @model.hash + @name.hash end |
#index ⇒ Object
334 335 336 |
# File 'lib/dm-core/property.rb', line 334 def index @index end |
#inspect ⇒ Object
511 512 513 |
# File 'lib/dm-core/property.rb', line 511 def inspect "#<Property:#{@model}:#{@name}>" end |
#key? ⇒ TrueClass, FalseClass
Returns whether or not the property is a key or a part of a key
-
358 359 360 |
# File 'lib/dm-core/property.rb', line 358 def key? @key end |
#lazy? ⇒ TrueClass, FalseClass
Returns whether or not the property is to be lazy-loaded
-
348 349 350 |
# File 'lib/dm-core/property.rb', line 348 def lazy? @lazy end |
#lazy_load(resource) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Loads lazy columns when get or set is called. -
447 448 449 450 451 452 453 454 455 456 |
# File 'lib/dm-core/property.rb', line 447 def lazy_load(resource) # It is faster to bail out at at a new_record? rather than to process # which properties would be loaded and then not load them. return if resource.new_record? || resource.attribute_loaded?(name) # If we're trying to load a lazy property, load it. Otherwise, lazy-load # any properties that should be eager-loaded but were not included # in the original :fields list contexts = lazy? ? name : model.eager_properties(resource.repository.name) resource.send(:lazy_load, contexts) end |
#length ⇒ Object Also known as: size
329 330 331 |
# File 'lib/dm-core/property.rb', line 329 def length @length.is_a?(Range) ? @length.max : @length end |
#nullable? ⇒ TrueClass, FalseClass
Returns whether or not the property can accept ‘nil’ as it’s value
-
376 377 378 |
# File 'lib/dm-core/property.rb', line 376 def nullable? @nullable end |
#serial? ⇒ TrueClass, FalseClass
Returns whether or not the property is “serial” (auto-incrementing)
-
367 368 369 |
# File 'lib/dm-core/property.rb', line 367 def serial? @serial end |
#set(resource, value) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Provides a standardized setter method for the property
-
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 |
# File 'lib/dm-core/property.rb', line 423 def set(resource, value) # [YK] We previously checked for new_record? here, but lazy loading # is blocked anyway if we're in a new record by by # Resource#reload_attributes. This may eventually be useful for # optimizing, but let's (a) benchmark it first, and (b) do # whatever refactoring is necessary, which will benefit from the # centralize checking lazy_load(resource) new_value = typecast(value) old_value = get!(resource) set_original_value(resource, old_value) set!(resource, new_value) end |
#set!(resource, value) ⇒ Object
440 441 442 |
# File 'lib/dm-core/property.rb', line 440 def set!(resource, value) resource.instance_variable_set(instance_variable_name, value) end |
#set_original_value(resource, val) ⇒ Object
410 411 412 413 414 415 416 |
# File 'lib/dm-core/property.rb', line 410 def set_original_value(resource, val) unless resource.original_values.key?(name) val = val.try_dup val = val.hash if track == :hash resource.original_values[name] = val end end |
#typecast(value) ⇒ TrueClass, ...
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
typecasts values into a primitive
-
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 495 496 497 498 499 500 501 |
# File 'lib/dm-core/property.rb', line 464 def typecast(value) return type.typecast(value, self) if type.respond_to?(:typecast) return value if value.kind_of?(primitive) || value.nil? begin if primitive == TrueClass then %w[ true 1 t ].include?(value.to_s.downcase) elsif primitive == String then value.to_s elsif primitive == Float then value.to_f elsif primitive == Integer # The simplest possible implementation, i.e. value.to_i, is not # desirable because "junk".to_i gives "0". We want nil instead, # because this makes it clear that the typecast failed. # # After benchmarking, we preferred the current implementation over # these two alternatives: # * Integer(value) rescue nil # * Integer(value_to_s =~ /(\d+)/ ? $1 : value_to_s) rescue nil # # [YK] The previous implementation used a rescue. Why use a rescue # when the list of cases where a valid string other than "0" could # produce 0 is known? value_to_i = value.to_i if value_to_i == 0 value.to_s =~ /^(0x|0b)?0+/ ? 0 : nil else value_to_i end elsif primitive == BigDecimal then BigDecimal(value.to_s) elsif primitive == DateTime then typecast_to_datetime(value) elsif primitive == Date then typecast_to_date(value) elsif primitive == Time then typecast_to_time(value) elsif primitive == Class then self.class.find_const(value) else value end rescue value end end |
#unique ⇒ Object
308 309 310 |
# File 'lib/dm-core/property.rb', line 308 def unique @unique ||= @options.fetch(:unique, @serial || @key || false) end |
#unique_index ⇒ Object
338 339 340 |
# File 'lib/dm-core/property.rb', line 338 def unique_index @unique_index end |
#value(val) ⇒ Object
507 508 509 |
# File 'lib/dm-core/property.rb', line 507 def value(val) custom? ? self.type.dump(val, self) : val end |