Class DataMapper::Property
In: lib/data_mapper/property.rb
lib/data_mapper/property.rb
Parent: Object

Quick Links

Properties

A model‘s properties are not derived from database structure. Instead, properties are declared inside it‘s model‘s class definition, which map to (or generate) fields in a database.

Defining properties explicitly in a model has several advantages. It centralizes information about the model in a single location, rather than having to dig out migrations, xml, or other config files. It also provides the ability to use Ruby‘s access control functions. Finally, since Datamapper only cares about properties explicitly defined in your models, Datamappers 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 < DataMapper::Base
    property :title,   :string, :nullable => false # Cannot be null
    property :publish, :boolen, :default  => false # Default value for new records
                                                     is false
  end

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 < DataMapper::Base
   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 getters, setters, and accessors, and can be declared using :reader and :writer, in addition to :accessor.

 class Post < DataMapper::Base
   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 < DataMapper::Base
   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, text fields are lazy loading by default, although you can over-ride this behavior if you wish:

Example:

 class Post < DataMapper::Base
   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 true or false with the :lazy option.

 class Post < DataMapper::Base
   property :title,  :string               # Loads normally
   property :body,   :text, :lazy => false # The default is now over-ridden
 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.

Keys

Properties can be declared as primary or natural keys on a table. By default, Datamapper will assume :id and create it if you don‘t have it. You can, however, declare a property as the primary key of the table:

 property :legacy_pk, :string, :key => true

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

Inferred Validations

When properties are declared with specific column restrictions, Datamapper will infer a few validation rules for values assigned to that property.

 property :title, :string, :length => 250
 # => infers 'validates_length_of :title, :minimum => 0, :maximum => 250'

 property :title, :string, :nullable => false
 # => infers 'validates_presence_of :title

 property :email, :string, :format => :email_address
 # => infers 'validates_format_of :email, :with => :email_address

 property :title, :string, :length => 255, :nullable => false
 # => infers both 'validates_length_of' as well as 'validates_presence_of'
 #    better: property :title, :string, :length => 1..255

For more information about validations, visit the Validatable documentation.

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 inadvertantly truncate data.
  • You may declare a Property with the data-type of :class. see SingleTableInheritance for more on how to use :class columns.

Methods

Constants

PROPERTY_OPTIONS = [ :public, :protected, :private, :accessor, :reader, :writer, :lazy, :default, :nullable, :key, :serial, :column, :size, :length, :format, :index, :check, :ordinal, :auto_validation   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
VISIBILITY_OPTIONS = [:public, :protected, :private]
AUTO_VALIDATIONS = { :nullable => lambda { |k,v| "validates_presence_of :#{k}" if v == false }, :size => lambda { |k,v| "validates_length_of :#{k}, " + (v.is_a?(Range) ? ":within => #{v}" : ":maximum => #{v}") }, :format => lambda { |k, v| "validates_format_of :#{k}, :with => #{v.inspect}" }   NOTE: :length may also be used in place of :size
PROPERTY_OPTIONS = [ :public, :protected, :private, :accessor, :reader, :writer, :lazy, :default, :nullable, :key, :serial, :column, :size, :length, :format, :index, :check, :ordinal, :auto_validation   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
VISIBILITY_OPTIONS = [:public, :protected, :private]
AUTO_VALIDATIONS = { :nullable => lambda { |k,v| "validates_presence_of :#{k}" if v == false }, :size => lambda { |k,v| "validates_length_of :#{k}, " + (v.is_a?(Range) ? ":within => #{v}" : ":maximum => #{v}") }, :format => lambda { |k, v| "validates_format_of :#{k}, :with => #{v.inspect}" }   NOTE: :length may also be used in place of :size

Public Class methods

[Source]

     # File lib/data_mapper/property.rb, line 153
153:     def initialize(klass, name, type, options)
154:       
155:       @klass, @name, @type, @options = klass, name.to_sym, type, options
156:       @symbolized_name = name.to_s.sub(/\?$/, '').to_sym
157:       
158:       validate_type!
159:       validate_options!
160:       determine_visibility!
161:       
162:       database.schema[klass].add_column(@symbolized_name, @type, @options)
163:       klass::ATTRIBUTES << @symbolized_name
164:       
165:       create_getter!
166:       create_setter!
167:       auto_validations! unless @options[:auto_validation] == false
168:       
169:     end

[Source]

     # File lib/data_mapper/property.rb, line 153
153:     def initialize(klass, name, type, options)
154:       
155:       @klass, @name, @type, @options = klass, name.to_sym, type, options
156:       @symbolized_name = name.to_s.sub(/\?$/, '').to_sym
157:       
158:       validate_type!
159:       validate_options!
160:       determine_visibility!
161:       
162:       database.schema[klass].add_column(@symbolized_name, @type, @options)
163:       klass::ATTRIBUTES << @symbolized_name
164:       
165:       create_getter!
166:       create_setter!
167:       auto_validations! unless @options[:auto_validation] == false
168:       
169:     end

Public Instance methods

defines the inferred validations given a property definition.

[Source]

     # File lib/data_mapper/property.rb, line 262
262:     def auto_validations!
263:       AUTO_VALIDATIONS.each do |key, value|
264:         next unless options.has_key?(key)
265:         validation = value.call(name, options[key])
266:         next if validation.nil? or validation.empty?
267:         klass.class_eval "begin\n\#{validation}\nrescue ArgumentError => e\nthrow e unless e.message =~ /specify a unique key/\nend\n"
268:       end
269:     end

defines the inferred validations given a property definition.

[Source]

     # File lib/data_mapper/property.rb, line 262
262:     def auto_validations!
263:       AUTO_VALIDATIONS.each do |key, value|
264:         next unless options.has_key?(key)
265:         validation = value.call(name, options[key])
266:         next if validation.nil? or validation.empty?
267:         klass.class_eval "begin\n\#{validation}\nrescue ArgumentError => e\nthrow e unless e.message =~ /specify a unique key/\nend\n"
268:       end
269:     end

[Source]

     # File lib/data_mapper/property.rb, line 282
282:     def column
283:       column = database.table(klass)[@name]
284:       raise StandardError.new("#{@name.inspect} is not a valid column name") unless column
285:       return column
286:     end

[Source]

     # File lib/data_mapper/property.rb, line 282
282:     def column
283:       column = database.table(klass)[@name]
284:       raise StandardError.new("#{@name.inspect} is not a valid column name") unless column
285:       return column
286:     end

defines the getter for the property

[Source]

     # File lib/data_mapper/property.rb, line 191
191:     def create_getter!
192:       if lazy?
193:         klass.class_eval "\#{reader_visibility.to_s}\ndef \#{name}\nlazy_load!(\#{name.inspect})\nclass << self;\nattr_accessor \#{name.inspect}\nend\n@\#{name}\nend\n"
194:       else
195:         klass.class_eval "\#{reader_visibility.to_s}\ndef \#{name}\n\#{instance_variable_name}\nend\n"
196:       end
197:       if type == :boolean
198:         klass.class_eval "\#{reader_visibility.to_s}\ndef \#{name.to_s.ensure_ends_with('?')}\n\#{instance_variable_name}\nend\n"
199:       end
200:     rescue SyntaxError
201:       raise SyntaxError.new(column)
202:     end

defines the getter for the property

[Source]

     # File lib/data_mapper/property.rb, line 191
191:     def create_getter!
192:       if lazy?
193:         klass.class_eval "\#{reader_visibility.to_s}\ndef \#{name}\nlazy_load!(\#{name.inspect})\nclass << self;\nattr_accessor \#{name.inspect}\nend\n@\#{name}\nend\n"
194:       else
195:         klass.class_eval "\#{reader_visibility.to_s}\ndef \#{name}\n\#{instance_variable_name}\nend\n"
196:       end
197:       if type == :boolean
198:         klass.class_eval "\#{reader_visibility.to_s}\ndef \#{name.to_s.ensure_ends_with('?')}\n\#{instance_variable_name}\nend\n"
199:       end
200:     rescue SyntaxError
201:       raise SyntaxError.new(column)
202:     end

defines the setter for the property

[Source]

     # File lib/data_mapper/property.rb, line 227
227:     def create_setter!
228:       if lazy?
229:         klass.class_eval "\#{writer_visibility.to_s}\ndef \#{name}=(value)\nclass << self;\nattr_accessor \#{name.inspect}\nend\n@\#{name} = value\nend\n"
230:       else
231:         klass.class_eval "\#{writer_visibility.to_s}\ndef \#{name}=(value)\n\#{instance_variable_name} = value\nend\n"
232:       end
233:     rescue SyntaxError
234:       raise SyntaxError.new(column)
235:     end

defines the setter for the property

[Source]

     # File lib/data_mapper/property.rb, line 227
227:     def create_setter!
228:       if lazy?
229:         klass.class_eval "\#{writer_visibility.to_s}\ndef \#{name}=(value)\nclass << self;\nattr_accessor \#{name.inspect}\nend\n@\#{name} = value\nend\n"
230:       else
231:         klass.class_eval "\#{writer_visibility.to_s}\ndef \#{name}=(value)\n\#{instance_variable_name} = value\nend\n"
232:       end
233:     rescue SyntaxError
234:       raise SyntaxError.new(column)
235:     end

[Source]

     # File lib/data_mapper/property.rb, line 278
278:     def klass
279:       @klass
280:     end

[Source]

     # File lib/data_mapper/property.rb, line 278
278:     def klass
279:       @klass
280:     end

[Source]

     # File lib/data_mapper/property.rb, line 312
312:     def lazy?
313:       column.lazy?
314:     end

[Source]

     # File lib/data_mapper/property.rb, line 312
312:     def lazy?
313:       column.lazy?
314:     end

[Source]

     # File lib/data_mapper/property.rb, line 288
288:     def name
289:       @name
290:     end

[Source]

     # File lib/data_mapper/property.rb, line 288
288:     def name
289:       @name
290:     end

[Source]

     # File lib/data_mapper/property.rb, line 300
300:     def options
301:       column.options
302:     end

[Source]

     # File lib/data_mapper/property.rb, line 300
300:     def options
301:       column.options
302:     end

[Source]

     # File lib/data_mapper/property.rb, line 296
296:     def type
297:       column.type
298:     end

[Source]

     # File lib/data_mapper/property.rb, line 296
296:     def type
297:       column.type
298:     end

[Validate]