Skip to content
This repository has been archived by the owner on Apr 17, 2018. It is now read-only.

Property Improved #139

Merged
merged 12 commits into from
Sep 12, 2011
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ DO_VERSION = '~> 0.10.6'
DM_DO_ADAPTERS = %w[ sqlite postgres mysql oracle sqlserver ]

gem 'addressable', '~> 2.2.6'
gem 'virtus', '~> 0.0.8'

group :development do

Expand Down
7 changes: 4 additions & 3 deletions dm-core.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Gem::Specification.new do |s|

s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
s.authors = ["Dan Kubb"]
s.date = "2011-09-09"
s.date = "2011-09-12"
s.description = "Faster, Better, Simpler."
s.email = "dan.kubb@gmail.com"
s.extra_rdoc_files = [
Expand Down Expand Up @@ -64,8 +64,6 @@ Gem::Specification.new do |s|
"lib/dm-core/property/string.rb",
"lib/dm-core/property/text.rb",
"lib/dm-core/property/time.rb",
"lib/dm-core/property/typecast/numeric.rb",
"lib/dm-core/property/typecast/time.rb",
"lib/dm-core/property_set.rb",
"lib/dm-core/query.rb",
"lib/dm-core/query/conditions/comparison.rb",
Expand Down Expand Up @@ -282,17 +280,20 @@ Gem::Specification.new do |s|

if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
s.add_runtime_dependency(%q<addressable>, ["~> 2.2.6"])
s.add_runtime_dependency(%q<virtus>, ["~> 0.0.8"])
s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
s.add_development_dependency(%q<rake>, ["~> 0.9.2"])
s.add_development_dependency(%q<rspec>, ["~> 1.3.2"])
else
s.add_dependency(%q<addressable>, ["~> 2.2.6"])
s.add_dependency(%q<virtus>, ["~> 0.0.8"])
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
s.add_dependency(%q<rake>, ["~> 0.9.2"])
s.add_dependency(%q<rspec>, ["~> 1.3.2"])
end
else
s.add_dependency(%q<addressable>, ["~> 2.2.6"])
s.add_dependency(%q<virtus>, ["~> 0.0.8"])
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
s.add_dependency(%q<rake>, ["~> 0.9.2"])
s.add_dependency(%q<rspec>, ["~> 1.3.2"])
Expand Down
10 changes: 8 additions & 2 deletions lib/dm-core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ module DataMapper
module Undefined; end
end

require 'virtus'

class Virtus::Coercion::Object
def self.to_string(value)
value.nil? ? value : value.to_s
end
end

require 'dm-core/support/ext/blank'
require 'dm-core/support/ext/hash'
require 'dm-core/support/ext/object'
Expand Down Expand Up @@ -61,8 +69,6 @@ module Undefined; end
require 'dm-core/resource/persistence_state/dirty'

require 'dm-core/property'
require 'dm-core/property/typecast/numeric'
require 'dm-core/property/typecast/time'
require 'dm-core/property/object'
require 'dm-core/property/string'
require 'dm-core/property/binary'
Expand Down
2 changes: 1 addition & 1 deletion lib/dm-core/associations/many_to_one.rb
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ def source_key_options(target_property)
:unique => @unique
)

if target_property.primitive == Integer
if target_property.instance_of?(Property::Integer)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this purpose of this was to catch any property subclasses with Integer primitives, not necessarily only Property::Integer classes.

However, for other parts of DM to work, people would probably have to subclass Property::Integer anyway if the they needed to store an Integer, so maybe this should use #kind_of? ? What do you think @solnic?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dkubb yes in other gems (dm-validations for example) I used #kind_of? cause there we may deal with custom subclasses; notice that this code here deals with FKs which currently are always created as Integer properties. For this to fail somebody would have to manually create an fk using an integer sub-class. Maybe "just in case" we should use #kind_of? here too. I don't have a strong opinion to be honest.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to use the strongest match that works, and then fallback to something less specific or duck typing if I have a valid use case.

In this case I think we have a valid use case though, and it's probably best handled by duck typing rather than asserting the class. I would probably change this to if target_property.respond_to?(:min) && target_property.respond_to?(:max), which will pass-through the min/max if the property type supports it.

min = target_property.min
max = target_property.max

Expand Down
68 changes: 38 additions & 30 deletions lib/dm-core/property.rb
Original file line number Diff line number Diff line change
Expand Up @@ -300,24 +300,6 @@ module DataMapper
# * You may declare a Property with the data-type of <tt>Class</tt>.
# see SingleTableInheritance for more on how to use <tt>Class</tt> columns.
class Property
module PassThroughLoadDump
# @api semipublic
def load(value)
typecast(value) unless value.nil?
end

# Stub instance method for dumping
#
# @param value [Object, nil] value to dump
#
# @return [Object] Dumped object
#
# @api semipublic
def dump(value)
value
end
end

include DataMapper::Assertions
include Subject
extend Equalizer
Expand All @@ -337,6 +319,7 @@ def dump(value)
].to_set.freeze

OPTIONS = [
:load_as, :dump_as, :coercion_method,
:accessor, :reader, :writer,
:lazy, :default, :key, :field,
:index, :unique_index,
Expand All @@ -352,10 +335,14 @@ def dump(value)
Query::OPTIONS.to_a
).map { |name| name.to_s }

attr_reader :primitive, :model, :name, :instance_variable_name,
attr_reader :load_as, :dump_as, :coercion_method,
:model, :name, :instance_variable_name,
:reader_visibility, :writer_visibility, :options,
:default, :repository_name, :allow_nil, :allow_blank, :required

alias_method :load_class, :load_as
alias_method :dump_class, :dump_as

class << self
extend Deprecate

Expand Down Expand Up @@ -460,9 +447,15 @@ def options
end
options
end

# @api deprecated
def primitive(*args)
warn "DataMapper::Property.primitive is deprecated, use .load_as instead (#{caller.first})"
load_as(*args)
end
end

accept_options :primitive, *Property::OPTIONS
accept_options *Property::OPTIONS

# A hook to allow properties to extend or modify the model it's bound to.
# Implementations are not supposed to modify the state of the property
Expand Down Expand Up @@ -680,11 +673,7 @@ def properties

# @api semipublic
def typecast(value)
if value.nil? || primitive?(value)
value
elsif respond_to?(:typecast_to_primitive)
typecast_to_primitive(value)
end
Virtus::Coercion[value.class].send(coercion_method, value)
end

# Test the value to see if it is a valid value for this Property
Expand All @@ -702,7 +691,7 @@ def valid?(value, negated = false)
if required? && dumped_value.nil?
negated || false
else
primitive?(dumped_value) || (dumped_value.nil? && (allow_nil? || negated))
value_dumped?(dumped_value) || (dumped_value.nil? && (allow_nil? || negated))
end
end

Expand All @@ -726,7 +715,23 @@ def inspect
#
# @api semipublic
def primitive?(value)
value.kind_of?(primitive)
warn "#primitive? is deprecated, use #value_dumped? instead (#{caller.first})"
value_dumped?(value)
end

def primitive
warn "#primitive is deprecated, use #dump_as instead (#{caller.first})"
dump_as
end

# @api semipublic
def value_dumped?(value)
value.kind_of?(dump_as)
end

# @api semipublic
def value_loaded?(value)
value.kind_of?(load_as)
end

protected
Expand All @@ -749,10 +754,13 @@ def initialize(model, name, options = {})
@name = name.to_s.chomp('?').to_sym
@options = predefined_options.merge(options).freeze
@instance_variable_name = "@#{@name}".freeze
@coercion_method = @options.fetch(:coercion_method)

@load_as = self.class.load_as
@dump_as = self.class.dump_as

@primitive = self.class.primitive
@field = @options[:field].freeze unless @options[:field].nil?
@default = @options[:default]
@field = @options[:field].freeze unless @options[:field].nil?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize the original code did this, but this seems to be freezing the object that was provided to the method, even if it is a hash value.

In general we try not to mutate any objects passed into methods, unless that is the method's primary purpose (which is rare).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The safest approach for freezing objects can be seen in veritas: https://github.com/dkubb/veritas/blob/master/lib/veritas/support/immutable.rb#L81-116

@default = @options[:default]

@serial = @options.fetch(:serial, false)
@key = @options.fetch(:key, @serial)
Expand Down
2 changes: 1 addition & 1 deletion lib/dm-core/property/binary.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module DataMapper
class Property
class Binary < String
include PassThroughLoadDump

end # class Binary
end # class Property
end # module DataMapper
29 changes: 9 additions & 20 deletions lib/dm-core/property/boolean.rb
Original file line number Diff line number Diff line change
@@ -1,31 +1,20 @@
module DataMapper
class Property
class Boolean < Object
include PassThroughLoadDump
load_as ::TrueClass
dump_as ::TrueClass
coercion_method :to_boolean

primitive ::TrueClass

TRUE_VALUES = [ 1, '1', 't', 'T', 'true', 'TRUE' ].freeze
FALSE_VALUES = [ 0, '0', 'f', 'F', 'false', 'FALSE' ].freeze
BOOLEAN_MAP = Hash[
TRUE_VALUES.product([ true ]) + FALSE_VALUES.product([ false ]) ].freeze
# @api semipublic
def value_dumped?(value)
value_loaded?(value)
end

def primitive?(value)
# @api semipublic
def value_loaded?(value)
value == true || value == false
end

# Typecast a value to a true or false
#
# @param [Integer, #to_str] value
# value to typecast
#
# @return [Boolean]
# true or false constructed from value
#
# @api private
def typecast_to_primitive(value)
BOOLEAN_MAP.fetch(value, value)
end
end # class Boolean
end # class Property
end # module DataMapper
21 changes: 7 additions & 14 deletions lib/dm-core/property/class.rb
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
module DataMapper
class Property
class Class < Object
include PassThroughLoadDump
load_as ::Class
dump_as ::Class
coercion_method :to_constant

primitive ::Class

# Typecast a value to a Class
#
# @param [#to_s] value
# value to typecast
#
# @return [Class]
# Class constructed from value
#
# @api private
def typecast_to_primitive(value)
DataMapper::Ext::Module.find_const(model, value.to_s)
# @api semipublic
def typecast(value)
DataMapper::Ext::Module.find_const(model, value.to_s) unless value.nil?
rescue NameError
value
end

end # class Class
end # class Property
end # module DataMapper
41 changes: 3 additions & 38 deletions lib/dm-core/property/date.rb
Original file line number Diff line number Diff line change
@@ -1,45 +1,10 @@
module DataMapper
class Property
class Date < Object
include PassThroughLoadDump
include Typecast::Time
load_as ::Date
dump_as ::Date
coercion_method :to_date

primitive ::Date

# Typecasts an arbitrary value to a Date
# Handles both Hashes and Date instances.
#
# @param [Hash, #to_mash, #to_s] value
# value to be typecast
#
# @return [Date]
# Date constructed from value
#
# @api private
def typecast_to_primitive(value)
if value.respond_to?(:to_date)
value.to_date
elsif value.is_a?(::Hash) || value.respond_to?(:to_mash)
typecast_hash_to_date(value)
else
::Date.parse(value.to_s)
end
rescue ArgumentError
value
end

# Creates a Date instance from a Hash with keys :year, :month, :day
#
# @param [Hash, #to_mash] value
# value to be typecast
#
# @return [Date]
# Date constructed from hash
#
# @api private
def typecast_hash_to_date(value)
::Date.new(*extract_time(value)[0, 3])
end
end # class Date
end # class Property
end # module DataMapper
40 changes: 3 additions & 37 deletions lib/dm-core/property/date_time.rb
Original file line number Diff line number Diff line change
@@ -1,44 +1,10 @@
module DataMapper
class Property
class DateTime < Object
include PassThroughLoadDump
include Typecast::Time
load_as ::DateTime
dump_as ::DateTime
coercion_method :to_datetime

primitive ::DateTime

# Typecasts an arbitrary value to a DateTime.
# Handles both Hashes and DateTime instances.
#
# @param [Hash, #to_mash, #to_s] value
# value to be typecast
#
# @return [DateTime]
# DateTime constructed from value
#
# @api private
def typecast_to_primitive(value)
if value.is_a?(::Hash) || value.respond_to?(:to_mash)
typecast_hash_to_datetime(value)
else
::DateTime.parse(value.to_s)
end
rescue ArgumentError
value
end

# Creates a DateTime instance from a Hash with keys :year, :month, :day,
# :hour, :min, :sec
#
# @param [Hash, #to_mash] value
# value to be typecast
#
# @return [DateTime]
# DateTime constructed from hash
#
# @api private
def typecast_hash_to_datetime(value)
::DateTime.new(*extract_time(value))
end
end # class DateTime
end # class Property
end # module DataMapper
Loading