Class: DataMapper::Property
- Inherits:
-
Object
- Object
- DataMapper::Property
- Includes:
- Extlib::Assertions
- Defined in:
- lib/dm-core/property.rb,
lib/dm-core/adapters/oracle_adapter.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 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 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
- OPTIONS =
NOTE: PLEASE update OPTIONS in DataMapper::Type when updating them here
[ :accessor, :reader, :writer, :lazy, :default, :nullable, :key, :serial, :field, :size, :length, :format, :index, :unique_index, :auto_validation, :validates, :unique, :precision, :scale, :min, :max ]
- PRIMITIVES =
[ TrueClass, String, Float, Integer, BigDecimal, DateTime, Date, Time, Object, Class, ].to_set.freeze
- VISIBILITY_OPTIONS =
Possible :visibility option values
[ :public, :protected, :private ].to_set.freeze
- DEFAULT_LENGTH =
50
- DEFAULT_PRECISION =
10
- DEFAULT_SCALE_BIGDECIMAL =
Default scale for BigDecimal type
0
- DEFAULT_SCALE_FLOAT =
Default scale for Float type
nil
- DEFAULT_NUMERIC_MIN =
0
- DEFAULT_NUMERIC_MAX =
2**31-1
Instance Attribute Summary collapse
-
#default ⇒ Object
readonly
Returns the value of attribute default.
-
#instance_variable_name ⇒ Object
readonly
Returns the value of attribute instance_variable_name.
-
#max ⇒ Object
readonly
Returns the value of attribute max.
-
#min ⇒ Object
readonly
Returns the value of attribute min.
-
#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.
-
#repository_name ⇒ Object
readonly
Returns the value of attribute repository_name.
-
#scale ⇒ Object
readonly
Returns the value of attribute scale.
-
#type ⇒ Object
readonly
Returns the value of attribute type.
-
#writer_visibility ⇒ Object
readonly
Returns the value of attribute writer_visibility.
Instance Method Summary collapse
-
#custom? ⇒ Boolean
Returns whether or not the property is custom (not provided by dm-core).
-
#default? ⇒ Boolean
Returns true if the property has a default value.
-
#default_for(resource) ⇒ Object
Returns a default value of the property for given resource.
-
#field(repository_name = nil) ⇒ String
Supplies the field in the data-store which the property corresponds to.
-
#get(resource) ⇒ Object
private
Standardized reader method for the property.
-
#get!(resource) ⇒ Object
private
Fetch the ivar value in the resource.
-
#hash ⇒ Integer
Returns the hash of the property name.
-
#index ⇒ true, ...
Returns index name if property has index.
-
#inspect ⇒ String
Returns a concise string representation of the property instance.
-
#key? ⇒ Boolean
Returns whether or not the property is a key or a part of a key.
-
#lazy? ⇒ Boolean
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.
-
#lazy_load_properties ⇒ Object
private
TODO: document.
-
#length ⇒ Integer, NilClass
Returns maximum property length (if applicable).
-
#loaded?(resource) ⇒ Boolean
private
Check if the attribute corresponding to the property is loaded.
-
#nullable? ⇒ Boolean
Returns whether or not the property can accept ‘nil’ as it’s value.
-
#primitive?(value) ⇒ Boolean
Test a value to see if it matches the primitive type.
-
#properties ⇒ Object
private
TODO: document.
-
#serial? ⇒ Boolean
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
private
Set the ivar value in the resource.
-
#set_original_value(resource, original) ⇒ Object
private
Sets original value of the property on given resource.
-
#typecast(value) ⇒ rue, ...
private
typecasts values into a primitive (Ruby class that backs DataMapper property type).
-
#unique? ⇒ Boolean
Returns true if property is unique.
-
#unique_index ⇒ true, ...
Returns true if property has unique index.
-
#valid?(value) ⇒ Boolean
Test the value to see if it is a valid value for this Property.
-
#value(value) ⇒ Object
Returns given value unchanged for core types and uses
dump
method of the property type for custom types.
Methods included from Deprecate
Methods included from Equalizer
Instance Attribute Details
#default ⇒ Object (readonly)
Returns the value of attribute default.
335 336 337 |
# File 'lib/dm-core/property.rb', line 335 def default @default end |
#instance_variable_name ⇒ Object (readonly)
Returns the value of attribute instance_variable_name.
335 336 337 |
# File 'lib/dm-core/property.rb', line 335 def instance_variable_name @instance_variable_name end |
#max ⇒ Object (readonly)
Returns the value of attribute max.
335 336 337 |
# File 'lib/dm-core/property.rb', line 335 def max @max end |
#min ⇒ Object (readonly)
Returns the value of attribute min.
335 336 337 |
# File 'lib/dm-core/property.rb', line 335 def min @min end |
#model ⇒ Object (readonly)
Returns the value of attribute model.
335 336 337 |
# File 'lib/dm-core/property.rb', line 335 def model @model end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
335 336 337 |
# File 'lib/dm-core/property.rb', line 335 def name @name end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
335 336 337 |
# File 'lib/dm-core/property.rb', line 335 def @options end |
#precision ⇒ Object (readonly)
Returns the value of attribute precision.
335 336 337 |
# File 'lib/dm-core/property.rb', line 335 def precision @precision end |
#primitive ⇒ Object (readonly)
Returns the value of attribute primitive.
335 336 337 |
# File 'lib/dm-core/property.rb', line 335 def primitive @primitive end |
#reader_visibility ⇒ Object (readonly)
Returns the value of attribute reader_visibility.
335 336 337 |
# File 'lib/dm-core/property.rb', line 335 def reader_visibility @reader_visibility end |
#repository_name ⇒ Object (readonly)
Returns the value of attribute repository_name.
335 336 337 |
# File 'lib/dm-core/property.rb', line 335 def repository_name @repository_name end |
#scale ⇒ Object (readonly)
Returns the value of attribute scale.
335 336 337 |
# File 'lib/dm-core/property.rb', line 335 def scale @scale end |
#type ⇒ Object (readonly)
Returns the value of attribute type.
335 336 337 |
# File 'lib/dm-core/property.rb', line 335 def type @type end |
#writer_visibility ⇒ Object (readonly)
Returns the value of attribute writer_visibility.
335 336 337 |
# File 'lib/dm-core/property.rb', line 335 def writer_visibility @writer_visibility end |
Instance Method Details
#custom? ⇒ Boolean
Returns whether or not the property is custom (not provided by dm-core)
471 472 473 |
# File 'lib/dm-core/property.rb', line 471 def custom? @custom end |
#default? ⇒ Boolean
Returns true if the property has a default value
684 685 686 |
# File 'lib/dm-core/property.rb', line 684 def default? @options.key?(:default) end |
#default_for(resource) ⇒ Object
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.
670 671 672 673 674 675 676 |
# File 'lib/dm-core/property.rb', line 670 def default_for(resource) if @default.respond_to?(:call) @default.call(resource, self) else @default.try_dup end end |
#field(repository_name = nil) ⇒ String
Supplies the field in the data-store which the property corresponds to
344 345 346 347 348 349 350 351 352 353 354 355 356 |
# File 'lib/dm-core/property.rb', line 344 def field(repository_name = nil) if repository_name warn "Passing in +repository_name+ to #{self.class}#field is deprecated (#{caller[0]})" if repository_name != self.repository_name raise ArgumentError, "Mismatching +repository_name+ with #{self.class}#repository_name (#{repository_name.inspect} != #{self.repository_name.inspect})" end end # defer setting the field with the adapter specific naming # conventions until after the adapter has been setup @field ||= model.field_naming_convention(self.repository_name).call(self).freeze 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.
Standardized reader method for the property
486 487 488 489 490 491 492 493 494 |
# File 'lib/dm-core/property.rb', line 486 def get(resource) lazy_load(resource) unless loaded?(resource) || resource.new? if loaded?(resource) get!(resource) else set(resource, default? ? default_for(resource) : nil) end 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.
Fetch the ivar value in the resource
505 506 507 |
# File 'lib/dm-core/property.rb', line 505 def get!(resource) resource.instance_variable_get(instance_variable_name) end |
#hash ⇒ Integer
Returns the hash of the property name
This is necessary to allow comparisons between different properties in different models, having the same base model
378 379 380 |
# File 'lib/dm-core/property.rb', line 378 def hash name.hash end |
#index ⇒ true, ...
Returns index name if property has index.
407 408 409 |
# File 'lib/dm-core/property.rb', line 407 def index @index end |
#inspect ⇒ String
Returns a concise string representation of the property instance.
726 727 728 |
# File 'lib/dm-core/property.rb', line 726 def inspect "#<#{self.class.name} @model=#{model.inspect} @name=#{name.inspect}>" end |
#key? ⇒ Boolean
Returns whether or not the property is a key or a part of a key
441 442 443 |
# File 'lib/dm-core/property.rb', line 441 def key? @key end |
#lazy? ⇒ Boolean
Returns whether or not the property is to be lazy-loaded
431 432 433 |
# File 'lib/dm-core/property.rb', line 431 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.
595 596 597 |
# File 'lib/dm-core/property.rb', line 595 def lazy_load(resource) resource.send(:lazy_load, lazy_load_properties) end |
#lazy_load_properties ⇒ 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: document
601 602 603 |
# File 'lib/dm-core/property.rb', line 601 def lazy_load_properties @lazy_load_properties ||= properties.in_context(lazy? ? [ self ] : properties.defaults) end |
#length ⇒ Integer, NilClass
Returns maximum property length (if applicable). This usually only makes sense when property is of type Range or custom type.
390 391 392 393 394 395 396 |
# File 'lib/dm-core/property.rb', line 390 def length if @length.kind_of?(Range) @length.max else @length end end |
#loaded?(resource) ⇒ Boolean
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.
Check if the attribute corresponding to the property is loaded
585 586 587 |
# File 'lib/dm-core/property.rb', line 585 def loaded?(resource) resource.instance_variable_defined?(instance_variable_name) end |
#nullable? ⇒ Boolean
Returns whether or not the property can accept ‘nil’ as it’s value
461 462 463 |
# File 'lib/dm-core/property.rb', line 461 def nullable? @nullable end |
#primitive?(value) ⇒ Boolean
Test a value to see if it matches the primitive type
739 740 741 742 743 744 745 |
# File 'lib/dm-core/property.rb', line 739 def primitive?(value) if primitive == TrueClass value == true || value == false else value.kind_of?(primitive) end end |
#properties ⇒ 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: document
607 608 609 |
# File 'lib/dm-core/property.rb', line 607 def properties @properties ||= model.properties(repository_name) end |
#serial? ⇒ Boolean
Returns whether or not the property is “serial” (auto-incrementing)
451 452 453 |
# File 'lib/dm-core/property.rb', line 451 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
547 548 549 550 551 552 553 554 555 556 557 558 559 |
# File 'lib/dm-core/property.rb', line 547 def set(resource, value) loaded = loaded?(resource) original = get!(resource) if loaded value = typecast(value) if loaded && value == original return original end set_original_value(resource, original) set!(resource, value) 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.
Set the ivar value in the resource
572 573 574 |
# File 'lib/dm-core/property.rb', line 572 def set!(resource, value) resource.instance_variable_set(instance_variable_name, value) end |
#set_original_value(resource, original) ⇒ 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.
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.
522 523 524 525 526 527 528 529 530 531 532 |
# File 'lib/dm-core/property.rb', line 522 def set_original_value(resource, original) original_attributes = resource.original_attributes original = self.value(original) if original_attributes.key?(self) # stop tracking the value if it has not changed original_attributes.delete(self) if original == original_attributes[self] && resource.saved? else original_attributes[self] = original end end |
#typecast(value) ⇒ rue, ...
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 (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.
638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 |
# File 'lib/dm-core/property.rb', line 638 def typecast(value) return type.typecast(value, self) if type.respond_to?(:typecast) return value if primitive?(value) || value.nil? if primitive == Integer then typecast_to_integer(value) elsif primitive == String then typecast_to_string(value) elsif primitive == TrueClass then typecast_to_boolean(value) elsif primitive == BigDecimal then typecast_to_bigdecimal(value) elsif primitive == Float then typecast_to_float(value) elsif primitive == DateTime then typecast_to_datetime(value) elsif primitive == Time then typecast_to_time(value) elsif primitive == Date then typecast_to_date(value) elsif primitive == Class then typecast_to_class(value) else value end end |
#unique? ⇒ Boolean
Returns true if property is unique. Serial properties and keys are unique by default.
365 366 367 |
# File 'lib/dm-core/property.rb', line 365 def unique? @unique end |
#unique_index ⇒ true, ...
Returns true if property has unique index. Serial properties and keys are unique by default.
421 422 423 |
# File 'lib/dm-core/property.rb', line 421 def unique_index @unique_index end |
#valid?(value) ⇒ Boolean
Test the value to see if it is a valid value for this Property
715 716 717 718 |
# File 'lib/dm-core/property.rb', line 715 def valid?(value) value = self.value(value) primitive?(value) || (value.nil? && nullable?) end |
#value(value) ⇒ Object
Returns given value unchanged for core types and uses dump
method of the property type for custom types.
698 699 700 701 702 703 704 |
# File 'lib/dm-core/property.rb', line 698 def value(value) if custom? type.dump(value, self) else value end end |