lib/dm-core/model/property.rb
# TODO: update Model#respond_to? to return true if method_method missing
# would handle the message
module DataMapper
module Model
module Property
Model.append_extensions self, DataMapper::Property::Lookup
def self.extended(model)
model.instance_variable_set(:@properties, {})
model.instance_variable_set(:@field_naming_conventions, {})
super
end
def inherited(model)
model.instance_variable_set(:@properties, {})
model.instance_variable_set(:@field_naming_conventions, @field_naming_conventions.dup)
@properties.each do |repository_name, properties|
model_properties = model.properties(repository_name)
properties.each { |property| model_properties << property }
end
super
end
# Defines a Property on the Resource
#
# @param [Symbol] name
# the name for which to call this property
# @param [Class] type
# the ruby type to define this property as
# @param [Hash(Symbol => String)] options
# a hash of available options
#
# @return [Property]
# the created Property
#
# @see Property
#
# @api public
def property(name, type, options = {})
# if the type can be found within Property then
# use that class rather than the primitive
klass = DataMapper::Property.determine_class(type)
unless klass
raise ArgumentError, "+type+ was #{type.inspect}, which is not a supported type"
end
property = klass.new(self, name, options)
repository_name = self.repository_name
properties = properties(repository_name)
properties << property
# Add property to the other mappings as well if this is for the default
# repository.
if repository_name == default_repository_name
other_repository_properties = DataMapper::Ext::Hash.except(@properties, default_repository_name)
other_repository_properties.each do |other_repository_name, properties|
next if properties.named?(name)
# make sure the property is created within the correct repository scope
DataMapper.repository(other_repository_name) do
properties << klass.new(self, name, options)
end
end
end
# Add the property to the lazy_loads set for this resources repository
# only.
# TODO Is this right or should we add the lazy contexts to all
# repositories?
if property.lazy?
context = options.fetch(:lazy, :default)
context = :default if context == true
Array(context).each do |context|
properties.lazy_context(context) << property
end
end
# add the property to the child classes only if the property was
# added after the child classes' properties have been copied from
# the parent
descendants.each do |descendant|
descendant.properties(repository_name) << property
end
create_reader_for(property)
create_writer_for(property)
# FIXME: explicit return needed for YARD to parse this properly
return property
end
# Gets a list of all properties that have been defined on this Model in
# the requested repository
#
# @param [Symbol, String] repository_name
# The name of the repository to use. Uses the default Repository
# if none is specified.
#
# @return [PropertySet]
# A list of Properties defined on this Model in the given Repository
#
# @api public
def properties(repository_name = default_repository_name)
# TODO: create PropertySet#copy that will copy the properties, but assign the
# new Relationship objects to a supplied repository and model. dup does not really
# do what is needed
repository_name = repository_name.to_sym
default_repository_name = self.default_repository_name
@properties[repository_name] ||= if repository_name == default_repository_name
PropertySet.new
else
properties(default_repository_name).dup
end
end
# Gets the list of key fields for this Model in +repository_name+
#
# @param [String] repository_name
# The name of the Repository for which the key is to be reported
#
# @return [Array]
# The list of key fields for this Model in +repository_name+
#
# @api public
def key(repository_name = default_repository_name)
properties(repository_name).key
end
# @api public
def serial(repository_name = default_repository_name)
key(repository_name).detect { |property| property.serial? }
end
# Gets the field naming conventions for this resource in the given Repository
#
# @param [String, Symbol] repository_name
# the name of the Repository for which the field naming convention
# will be retrieved
#
# @return [#call]
# The naming convention for the given Repository
#
# @api public
def field_naming_convention(repository_name = default_storage_name)
@field_naming_conventions[repository_name] ||= repository(repository_name).adapter.field_naming_convention
end
# @api private
def properties_with_subclasses(repository_name = default_repository_name)
properties = properties(repository_name).dup
descendants.each do |model|
model.properties(repository_name).each do |property|
properties << property
end
end
properties
end
# @api private
def key_conditions(repository, key)
Hash[self.key(repository.name).zip(Array(key))]
end
private
# Defines the anonymous module that is used to add properties.
# Using a single module here prevents having a very large number
# of anonymous modules, where each property has their own module.
# @api private
def property_module
@property_module ||= begin
mod = Module.new
class_eval do
include mod
end
mod
end
end
# defines the reader method for the property
#
# @api private
def create_reader_for(property)
name = property.name.to_s
reader_visibility = property.reader_visibility
instance_variable_name = property.instance_variable_name
property_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
#{reader_visibility}
def #{name}
return #{instance_variable_name} if defined?(#{instance_variable_name})
property = properties[#{name.inspect}]
#{instance_variable_name} = property ? persistence_state.get(property) : nil
end
RUBY
boolean_reader_name = "#{name}?"
if property.kind_of?(DataMapper::Property::Boolean)
property_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
#{reader_visibility}
def #{boolean_reader_name}
#{name}
end
RUBY
end
end
# defines the setter for the property
#
# @api private
def create_writer_for(property)
name = property.name
writer_visibility = property.writer_visibility
writer_name = "#{name}="
property_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
#{writer_visibility}
def #{writer_name}(value)
property = properties[#{name.inspect}]
value = property.typecast(value)
self.persistence_state = persistence_state.set(property, value)
persistence_state.get(property)
end
RUBY
end
# @api public
def method_missing(method, *args, &block)
if property = properties(repository_name)[method]
return property
end
super
end
end # module Property
end # module Model
end # module DataMapper