Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Property Improved #139

Merged
merged 12 commits into from

3 participants

@solnic
Owner

Hey!

This is still a WIP but I'm opening a pull request early so we can be discussing changes while I'm finishing this stuff.

This branch makes two relatively small but significant changes. First it adds dump_as/load_as property options as described here: https://gist.github.com/784350; it also removes typecast logic from dm-core and replaces it with Virtus Coercion system.

Let me know what you think :)

@dkubb dkubb commented on the diff
lib/dm-core/property.rb
((8 lines not shown))
- @primitive = self.class.primitive
- @field = @options[:field].freeze unless @options[:field].nil?
- @default = @options[:default]
+ @field = @options[:field].freeze unless @options[:field].nil?
@dkubb Owner
dkubb added a note

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).

@dkubb Owner
dkubb added a note

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/dm-core/property/class.rb
((7 lines not shown))
- 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)
+ # @api semipublic
+ def typecast(value)
+ return unless value
@dkubb Owner
dkubb added a note

This should probably check value.nil? explicitly here instead. If false was passed in we just want to pass it through, not silently transform it into nil.

@dkubb Owner
dkubb added a note

Come to think of it,. are there other places in dm-core or dm-types where we just do return unless value rather than return unless value.nil? and thus potentially coerce false to nil when we should be passing it through?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@dkubb dkubb commented on the diff
lib/dm-core/property/numeric.rb
((16 lines not shown))
def initialize(model, name, options = {})
super
- if @primitive == BigDecimal || @primitive == ::Float
+ if kind_of?(Decimal) || kind_of?(Float)
@dkubb Owner
dkubb added a note

Later on we should consider extracting this block into a module that is mixed into Decimal and Float properties.

I don't like the idea of a class having any direct knowledge of their subclases.

@solnic Owner
solnic added a note

Yes, I never liked it. Those weird bits of code are 'left-overs' from the DM::Type era. To be honest with the DM 2.0 on the horizon my motivation to clean this up is getting smaller and smaller each day.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/dm-core/property/object.rb
((7 lines not shown))
# @api semipublic
def dump(value)
+ if self.class == Object
@dkubb Owner
dkubb added a note

I would generally recommend using Object.equal?(self.class). Not only is it more efficient, it's also almost guaranteed that Object#equal? won't be overridden -- of course it's highly unlikely that self.class#== won't be overridden too, but not quite as much.

@solnic Owner
solnic added a note

Actually I ended up using instance_of?(Object) - what do you think?

@dkubb Owner
dkubb added a note

Oh, that's even better than what I was suggesting. For some reason I was thinking you had a class that you were comparing to Object, but since you have an instance #instance_of? is the most intention revealing approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@dkubb dkubb commented on the diff
spec/public/property/binary_spec.rb
@@ -4,7 +4,7 @@ describe DataMapper::Property::Binary do
before :all do
@name = :title
@type = described_class
- @primitive = String
+ @load_as = String
@dkubb Owner
dkubb added a note

It looks like there are small formatting fixes needed in the specs for what (I assume) was changed via find/replace.

@solnic Owner
solnic added a note

blame sed for this awful mistake ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@dkubb
Owner

@solnic this is a pretty amazing reduction in code. Do the dm-types specs pass with this change?

Most of the comments I've left are really minor in nature, I really think this is a pretty awesome change. I love removing code from DM and replacing it with library code that's better tested. It's also a nice way to get virtus used more now.

@solnic solnic merged commit a42c0ff into from
@dkubb dkubb commented on the diff
lib/dm-core/associations/many_to_one.rb
@@ -266,7 +266,7 @@ module DataMapper
:unique => @unique
)
- if target_property.primitive == Integer
+ if target_property.instance_of?(Property::Integer)
@dkubb Owner
dkubb added a note

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?

@solnic Owner
solnic added a note

@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.

@dkubb Owner
dkubb added a note

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@emmanuel
Owner
@solnic
Owner

@emmanuel thanks man, it was fun :)

I'm not sure how to tackle the virtus transition to be honest. I still need to think about possible solutions. @dkubb suggested that I could turn Property into a sub-class of Virtus::Attribute to ease the transition. That seems like a good idea.

What do you mean by 'has-a Virtus::Attribute'? Effectively we want to rip out Property and have it replaced by Virtus.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
Showing with 181 additions and 409 deletions.
  1. +1 −0  Gemfile
  2. +4 −3 dm-core.gemspec
  3. +8 −2 lib/dm-core.rb
  4. +1 −1  lib/dm-core/associations/many_to_one.rb
  5. +38 −30 lib/dm-core/property.rb
  6. +1 −1  lib/dm-core/property/binary.rb
  7. +9 −20 lib/dm-core/property/boolean.rb
  8. +7 −14 lib/dm-core/property/class.rb
  9. +3 −38 lib/dm-core/property/date.rb
  10. +3 −37 lib/dm-core/property/date_time.rb
  11. +4 −18 lib/dm-core/property/decimal.rb
  12. +1 −3 lib/dm-core/property/discriminator.rb
  13. +3 −15 lib/dm-core/property/float.rb
  14. +4 −14 lib/dm-core/property/integer.rb
  15. +5 −4 lib/dm-core/property/lookup.rb
  16. +3 −5 lib/dm-core/property/numeric.rb
  17. +15 −9 lib/dm-core/property/object.rb
  18. +2 −2 lib/dm-core/property/serial.rb
  19. +4 −16 lib/dm-core/property/string.rb
  20. +2 −5 lib/dm-core/property/text.rb
  21. +3 −39 lib/dm-core/property/time.rb
  22. +0 −32 lib/dm-core/property/typecast/numeric.rb
  23. +0 −33 lib/dm-core/property/typecast/time.rb
  24. +7 −7 lib/dm-core/spec/shared/public/property_spec.rb
  25. +4 −12 lib/dm-core/spec/shared/semipublic/property_spec.rb
  26. +2 −2 spec/public/property/binary_spec.rb
  27. +2 −2 spec/public/property/boolean_spec.rb
  28. +2 −2 spec/public/property/class_spec.rb
  29. +2 −2 spec/public/property/date_spec.rb
  30. +2 −2 spec/public/property/date_time_spec.rb
  31. +2 −2 spec/public/property/decimal_spec.rb
  32. +1 −1  spec/public/property/discriminator_spec.rb
  33. +2 −2 spec/public/property/float_spec.rb
  34. +2 −2 spec/public/property/integer_spec.rb
  35. +1 −1  spec/public/property/object_spec.rb
  36. +2 −2 spec/public/property/serial_spec.rb
  37. +2 −2 spec/public/property/string_spec.rb
  38. +2 −2 spec/public/property/text_spec.rb
  39. +2 −2 spec/public/property/time_spec.rb
  40. +1 −1  spec/semipublic/property/boolean_spec.rb
  41. +1 −1  spec/semipublic/property/class_spec.rb
  42. +4 −4 spec/semipublic/property/date_spec.rb
  43. +7 −7 spec/semipublic/property/date_time_spec.rb
  44. +1 −1  spec/semipublic/property/decimal_spec.rb
  45. +1 −1  spec/semipublic/property/float_spec.rb
  46. +1 −1  spec/semipublic/property/integer_spec.rb
  47. +7 −7 spec/semipublic/property/time_spec.rb
View
1  Gemfile
@@ -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
View
7 dm-core.gemspec
@@ -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 = [
@@ -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",
@@ -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"])
View
10 lib/dm-core.rb
@@ -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'
@@ -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'
View
2  lib/dm-core/associations/many_to_one.rb
@@ -266,7 +266,7 @@ def source_key_options(target_property)
:unique => @unique
)
- if target_property.primitive == Integer
+ if target_property.instance_of?(Property::Integer)
@dkubb Owner
dkubb added a note

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?

@solnic Owner
solnic added a note

@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.

@dkubb Owner
dkubb added a note

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
min = target_property.min
max = target_property.max
View
68 lib/dm-core/property.rb
@@ -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
@@ -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,
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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?
@dkubb Owner
dkubb added a note

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).

@dkubb Owner
dkubb added a note

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ @default = @options[:default]
@serial = @options.fetch(:serial, false)
@key = @options.fetch(:key, @serial)
View
2  lib/dm-core/property/binary.rb
@@ -1,7 +1,7 @@
module DataMapper
class Property
class Binary < String
- include PassThroughLoadDump
+
end # class Binary
end # class Property
end # module DataMapper
View
29 lib/dm-core/property/boolean.rb
@@ -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
View
21 lib/dm-core/property/class.rb
@@ -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
View
41 lib/dm-core/property/date.rb
@@ -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
View
40 lib/dm-core/property/date_time.rb
@@ -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
View
22 lib/dm-core/property/decimal.rb
@@ -1,7 +1,9 @@
module DataMapper
class Property
class Decimal < Numeric
- primitive BigDecimal
+ load_as BigDecimal
+ dump_as BigDecimal
+ coercion_method :to_decimal
DEFAULT_PRECISION = 10
DEFAULT_SCALE = 0
@@ -9,7 +11,7 @@ class Decimal < Numeric
precision(DEFAULT_PRECISION)
scale(DEFAULT_SCALE)
- protected
+ protected
def initialize(model, name, options = {})
super
@@ -29,22 +31,6 @@ def initialize(model, name, options = {})
end
end
- # Typecast a value to a BigDecimal
- #
- # @param [#to_str, #to_d, Integer] value
- # value to typecast
- #
- # @return [BigDecimal]
- # BigDecimal constructed from value
- #
- # @api private
- def typecast_to_primitive(value)
- if value.kind_of?(::Integer)
- value.to_s.to_d
- else
- typecast_to_numeric(value, :to_d)
- end
- end
end # class Decimal
end # class Property
end # module DataMapper
View
4 lib/dm-core/property/discriminator.rb
@@ -1,8 +1,6 @@
module DataMapper
class Property
class Discriminator < Class
- include PassThroughLoadDump
-
default lambda { |resource, property| resource.model }
required true
@@ -22,7 +20,7 @@ def new(*args, &block)
discriminator = properties(repository_name).discriminator
if discriminator_value = args.first[discriminator.name]
- model = discriminator.typecast_to_primitive(discriminator_value)
+ model = discriminator.typecast(discriminator_value)
if model.kind_of?(Model) && !model.equal?(self)
return model.new(*args, &block)
View
18 lib/dm-core/property/float.rb
@@ -1,7 +1,9 @@
module DataMapper
class Property
class Float < Numeric
- primitive ::Float
+ load_as ::Float
+ dump_as ::Float
+ coercion_method :to_float
DEFAULT_PRECISION = 10
DEFAULT_SCALE = nil
@@ -9,20 +11,6 @@ class Float < Numeric
precision(DEFAULT_PRECISION)
scale(DEFAULT_SCALE)
- protected
-
- # Typecast a value to a Float
- #
- # @param [#to_str, #to_f] value
- # value to typecast
- #
- # @return [Float]
- # Float constructed from value
- #
- # @api private
- def typecast_to_primitive(value)
- typecast_to_numeric(value, :to_f)
- end
end # class Float
end # class Property
end # module DataMapper
View
18 lib/dm-core/property/integer.rb
@@ -1,11 +1,13 @@
module DataMapper
class Property
class Integer < Numeric
- primitive ::Integer
+ load_as ::Integer
+ dump_as ::Integer
+ coercion_method :to_integer
accept_options :serial
- protected
+ protected
# @api semipublic
def initialize(model, name, options = {})
@@ -15,18 +17,6 @@ def initialize(model, name, options = {})
super
end
- # Typecast a value to an Integer
- #
- # @param [#to_str, #to_i] value
- # value to typecast
- #
- # @return [Integer]
- # Integer constructed from value
- #
- # @api private
- def typecast_to_primitive(value)
- typecast_to_numeric(value, :to_i)
- end
end # class Integer
end # class Property
end # module DataMapper
View
9 lib/dm-core/property/lookup.rb
@@ -2,7 +2,7 @@ module DataMapper
class Property
module Lookup
- protected
+ protected
#
# Provides transparent access to the Properties defined in
@@ -24,6 +24,7 @@ module Lookup
def const_missing(name)
Property.find_class(name.to_s) || super
end
- end
- end
-end
+
+ end # module Lookup
+ end # class Property
+end # module DataMapper
View
8 lib/dm-core/property/numeric.rb
@@ -1,21 +1,19 @@
module DataMapper
class Property
class Numeric < Object
- include PassThroughLoadDump
- include Typecast::Numeric
-
accept_options :precision, :scale, :min, :max
+
attr_reader :precision, :scale, :min, :max
DEFAULT_NUMERIC_MIN = 0
DEFAULT_NUMERIC_MAX = 2**31-1
- protected
+ protected
def initialize(model, name, options = {})
super
- if @primitive == BigDecimal || @primitive == ::Float
+ if kind_of?(Decimal) || kind_of?(Float)
@dkubb Owner
dkubb added a note

Later on we should consider extracting this block into a module that is mixed into Decimal and Float properties.

I don't like the idea of a class having any direct knowledge of their subclases.

@solnic Owner
solnic added a note

Yes, I never liked it. Those weird bits of code are 'left-overs' from the DM::Type era. To be honest with the DM 2.0 on the horizon my motivation to clean this up is getting smaller and smaller each day.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@precision = @options.fetch(:precision)
@scale = @options.fetch(:scale)
View
24 lib/dm-core/property/object.rb
@@ -1,22 +1,28 @@
module DataMapper
class Property
class Object < Property
- primitive ::Object
+ load_as ::Object
+ dump_as ::Object
+ coercion_method :to_object
# @api semipublic
def dump(value)
- return if value.nil?
- [ Marshal.dump(value) ].pack('m')
+ instance_of?(Object) ? marshal(value) : value
end
# @api semipublic
def load(value)
- case value
- when ::String
- Marshal.load(value.unpack('m').first)
- when ::Object
- value
- end
+ typecast(instance_of?(Object) ? unmarshal(value) : value)
+ end
+
+ # @api semipublic
+ def marshal(value)
+ [ Marshal.dump(value) ].pack('m') unless value.nil?
+ end
+
+ # @api semipublic
+ def unmarshal(value)
+ Marshal.load(value.unpack('m').first) unless value.nil?
end
# @api private
View
4 lib/dm-core/property/serial.rb
@@ -1,8 +1,8 @@
module DataMapper
class Property
class Serial < Integer
- serial true
- min 1
+ serial true
+ min 1
# @api private
def to_child_key
View
20 lib/dm-core/property/string.rb
@@ -1,9 +1,9 @@
module DataMapper
class Property
class String < Object
- include PassThroughLoadDump
-
- primitive ::String
+ load_as ::String
+ dump_as ::String
+ coercion_method :to_string
accept_options :length
@@ -26,25 +26,13 @@ def length
end
end
- protected
+ protected
def initialize(model, name, options = {})
super
@length = @options.fetch(:length)
end
- # Typecast a value to a String
- #
- # @param [#to_s] value
- # value to typecast
- #
- # @return [String]
- # String constructed from value
- #
- # @api private
- def typecast_to_primitive(value)
- value.to_s
- end
end # class String
end # class Property
end # module DataMapper
View
7 lib/dm-core/property/text.rb
@@ -1,12 +1,9 @@
module DataMapper
class Property
class Text < String
- length 65535
- lazy true
+ length 65535
+ lazy true
- def primitive?(value)
- value.kind_of?(::String)
- end
end # class Text
end # class Property
end # module DataMapper
View
42 lib/dm-core/property/time.rb
@@ -1,46 +1,10 @@
module DataMapper
class Property
class Time < Object
- include PassThroughLoadDump
- include Typecast::Time
+ load_as ::Time
+ dump_as ::Time
+ coercion_method :to_time
- primitive ::Time
-
- # Typecasts an arbitrary value to a Time
- # Handles both Hashes and Time instances.
- #
- # @param [Hash, #to_mash, #to_s] value
- # value to be typecast
- #
- # @return [Time]
- # Time constructed from value
- #
- # @api private
- def typecast_to_primitive(value)
- if value.respond_to?(:to_time)
- value.to_time
- elsif value.is_a?(::Hash) || value.respond_to?(:to_mash)
- typecast_hash_to_time(value)
- else
- ::Time.parse(value.to_s)
- end
- rescue ArgumentError
- value
- end
-
- # Creates a Time instance from a Hash with keys :year, :month, :day,
- # :hour, :min, :sec
- #
- # @param [Hash, #to_mash] value
- # value to be typecast
- #
- # @return [Time]
- # Time constructed from hash
- #
- # @api private
- def typecast_hash_to_time(value)
- ::Time.local(*extract_time(value))
- end
end # class Time
end # class Property
end # module DataMapper
View
32 lib/dm-core/property/typecast/numeric.rb
@@ -1,32 +0,0 @@
-module DataMapper
- class Property
- module Typecast
- module Numeric
- # Match numeric string
- #
- # @param [#to_str, Numeric] value
- # value to typecast
- # @param [Symbol] method
- # method to typecast with
- #
- # @return [Numeric]
- # number if matched, value if no match
- #
- # @api private
- def typecast_to_numeric(value, method)
- if value.respond_to?(:to_str)
- if value.to_str =~ /\A(-?(?:0|[1-9]\d*)(?:\.\d+)?|(?:\.\d+))\z/
- $1.send(method)
- else
- value
- end
- elsif value.respond_to?(method)
- value.send(method)
- else
- value
- end
- end
- end # Numeric
- end # Typecast
- end # Property
-end # DataMapper
View
33 lib/dm-core/property/typecast/time.rb
@@ -1,33 +0,0 @@
-module DataMapper
- class Property
- module Typecast
- module Time
- include Numeric
-
- # Extracts the given args from the hash. If a value does not exist, it
- # uses the value of Time.now.
- #
- # @param [Hash, #to_mash] value
- # value to extract time args from
- #
- # @return [Array]
- # Extracted values
- #
- # @api private
- def extract_time(value)
- mash = if value.respond_to?(:to_mash)
- value.to_mash
- else
- DataMapper::Ext::Hash.to_mash(value)
- end
-
- now = ::Time.now
-
- [ :year, :month, :day, :hour, :min, :sec ].map do |segment|
- typecast_to_numeric(mash.fetch(segment, now.send(segment)), :to_i)
- end
- end
- end # Time
- end # Typecast
- end # Property
-end # DataMapper
View
14 lib/dm-core/spec/shared/public/property_spec.rb
@@ -1,6 +1,6 @@
share_examples_for 'A public Property' do
before :all do
- %w[ @type @primitive @name @value @other_value ].each do |ivar|
+ %w[ @type @load_as @name @value @other_value ].each do |ivar|
raise "+#{ivar}+ should be defined in before block" unless instance_variable_defined?(ivar)
end
@@ -101,16 +101,16 @@ class ::ChildSubType < @subtype
end
end
- describe ".primitive" do
- it "should return the primitive class" do
+ describe ".load_as" do
+ it "should return the load_as" do
[@type, @subtype].each do |type|
- type.primitive.should be(@primitive)
+ type.load_as.should be(@load_as)
end
end
- it "should change the primitive class" do
- @subtype.primitive Object
- @subtype.primitive.should be(Object)
+ it "should change the load_as class" do
+ @subtype.load_as Object
+ @subtype.load_as.should be(Object)
end
end
end
View
16 lib/dm-core/spec/shared/semipublic/property_spec.rb
@@ -22,8 +22,8 @@ class Article
@property.should be_kind_of(@type)
end
- it 'should set the primitive' do
- @property.primitive.should be(@type.primitive)
+ it 'should set the load_as' do
+ @property.load_as.should be(@type.load_as)
end
it 'should set the model' do
@@ -50,8 +50,8 @@ class Article
@property.model.should equal(@model)
end
- it 'should set the primitive' do
- @property.primitive.should be(@type.primitive)
+ it 'should set the load_as' do
+ @property.load_as.should be(@type.load_as)
end
it "should set the options to #{options.inspect}" do
@@ -89,14 +89,6 @@ class Article
end
describe "#typecast" do
- describe "when is able to do typecasting on it's own" do
- it 'delegates all the work to the type' do
- return_value = mock(@other_value)
- @property.should_receive(:typecast_to_primitive).with(@invalid_value).and_return(return_value)
- @property.typecast(@invalid_value)
- end
- end
-
describe 'when value is nil' do
it 'returns value unchanged' do
@property.typecast(nil).should be(nil)
View
4 spec/public/property/binary_spec.rb
@@ -4,7 +4,7 @@
before :all do
@name = :title
@type = described_class
- @primitive = String
+ @load_as = String
@dkubb Owner
dkubb added a note

It looks like there are small formatting fixes needed in the specs for what (I assume) was changed via find/replace.

@solnic Owner
solnic added a note

blame sed for this awful mistake ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@value = 'value'
@other_value = 'return value'
@invalid_value = 1
@@ -17,6 +17,6 @@
it { should be_kind_of(Hash) }
- it { should eql(:primitive => @primitive, :length => 50) }
+ it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_string, :length => 50) }
end
end
View
4 spec/public/property/boolean_spec.rb
@@ -4,7 +4,7 @@
before :all do
@name = :active
@type = described_class
- @primitive = TrueClass
+ @load_as = TrueClass
@value = true
@other_value = false
@invalid_value = 1
@@ -17,6 +17,6 @@
it { should be_kind_of(Hash) }
- it { should eql(:primitive => @primitive) }
+ it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_boolean) }
end
end
View
4 spec/public/property/class_spec.rb
@@ -10,7 +10,7 @@ class ::Bar; end
@name = :type
@type = described_class
- @primitive = Class
+ @load_as = Class
@value = Foo
@other_value = Bar
@invalid_value = 1
@@ -23,6 +23,6 @@ class ::Bar; end
it { should be_kind_of(Hash) }
- it { should eql(:primitive => @primitive) }
+ it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_constant) }
end
end
View
4 spec/public/property/date_spec.rb
@@ -4,7 +4,7 @@
before :all do
@name = :created_on
@type = described_class
- @primitive = Date
+ @load_as = Date
@value = Date.today
@other_value = Date.today + 1
@invalid_value = 1
@@ -17,6 +17,6 @@
it { should be_kind_of(Hash) }
- it { should eql(:primitive => @primitive) }
+ it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_date) }
end
end
View
4 spec/public/property/date_time_spec.rb
@@ -4,7 +4,7 @@
before :all do
@name = :created_at
@type = described_class
- @primitive = DateTime
+ @load_as = DateTime
@value = DateTime.now
@other_value = DateTime.now + 15
@invalid_value = 1
@@ -17,6 +17,6 @@
it { should be_kind_of(Hash) }
- it { should eql(:primitive => @primitive) }
+ it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_datetime) }
end
end
View
4 spec/public/property/decimal_spec.rb
@@ -5,7 +5,7 @@
@name = :rate
@type = described_class
@options = { :precision => 5, :scale => 2 }
- @primitive = BigDecimal
+ @load_as = BigDecimal
@value = BigDecimal('1.0')
@other_value = BigDecimal('2.0')
@invalid_value = true
@@ -18,6 +18,6 @@
it { should be_kind_of(Hash) }
- it { should eql(:primitive => @primitive, :precision => 10, :scale => 0) }
+ it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_decimal, :precision => 10, :scale => 0) }
end
end
View
2  spec/public/property/discriminator_spec.rb
@@ -28,7 +28,7 @@ class Release < Announcement; end
it { should be_kind_of(Hash) }
- it { should include(:primitive => Class, :required => true) }
+ it { should include(:load_as => Class, :required => true) }
end
it 'should typecast to a Model' do
View
4 spec/public/property/float_spec.rb
@@ -4,7 +4,7 @@
before :all do
@name = :rating
@type = described_class
- @primitive = Float
+ @load_as = Float
@value = 0.1
@other_value = 0.2
@invalid_value = '1'
@@ -17,6 +17,6 @@
it { should be_kind_of(Hash) }
- it { should eql(:primitive => @primitive, :precision => 10, :scale => nil) }
+ it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_float, :precision => 10, :scale => nil) }
end
end
View
4 spec/public/property/integer_spec.rb
@@ -4,7 +4,7 @@
before :all do
@name = :age
@type = described_class
- @primitive = Integer
+ @load_as = Integer
@value = 1
@other_value = 2
@invalid_value = '1'
@@ -17,6 +17,6 @@
it { should be_kind_of(Hash) }
- it { should eql(:primitive => @primitive) }
+ it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_integer) }
end
end
View
2  spec/public/property/object_spec.rb
@@ -62,7 +62,7 @@ class Article
it { should respond_to(:valid?) }
describe '#valid?' do
- describe 'with a valid primitive' do
+ describe 'with a valid load_as' do
subject { @property.valid?('lang' => 'en_CA') }
it { should be(true) }
View
4 spec/public/property/serial_spec.rb
@@ -4,7 +4,7 @@
before :all do
@name = :id
@type = described_class
- @primitive = Integer
+ @load_as = Integer
@value = 1
@other_value = 2
@invalid_value = 'foo'
@@ -17,6 +17,6 @@
it { should be_kind_of(Hash) }
- it { should eql(:primitive => @primitive, :min => 1, :serial => true) }
+ it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_integer, :min => 1, :serial => true) }
end
end
View
4 spec/public/property/string_spec.rb
@@ -4,7 +4,7 @@
before :all do
@name = :name
@type = described_class
- @primitive = String
+ @load_as = String
@value = 'value'
@other_value = 'return value'
@invalid_value = 1
@@ -17,6 +17,6 @@
it { should be_kind_of(Hash) }
- it { should eql(:primitive => @primitive, :length => 50) }
+ it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_string, :length => 50) }
end
end
View
4 spec/public/property/text_spec.rb
@@ -4,7 +4,7 @@
before :all do
@name = :title
@type = described_class
- @primitive = String
+ @load_as = String
@value = 'value'
@other_value = 'return value'
@invalid_value = 1
@@ -17,7 +17,7 @@
it { should be_kind_of(Hash) }
- it { should eql(:primitive => @primitive, :length => 65535, :lazy => true) }
+ it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_string, :length => 65535, :lazy => true) }
end
describe 'migration with an index' do
View
4 spec/public/property/time_spec.rb
@@ -4,7 +4,7 @@
before :all do
@name = :deleted_at
@type = described_class
- @primitive = Time
+ @load_as = Time
@value = Time.now
@other_value = Time.now + 15
@invalid_value = 1
@@ -17,6 +17,6 @@
it { should be_kind_of(Hash) }
- it { should eql(:primitive => @primitive) }
+ it { should eql(:load_as => @load_as, :dump_as => @load_as, :coercion_method => :to_time) }
end
end
View
2  spec/semipublic/property/boolean_spec.rb
@@ -43,7 +43,7 @@
end
end
- describe '#typecast_to_primitive' do
+ describe '#typecast' do
[ true, 'true', 'TRUE', '1', 1, 't', 'T' ].each do |value|
it "returns true when value is #{value.inspect}" do
@property.typecast(value).should be(true)
View
2  spec/semipublic/property/class_spec.rb
@@ -17,7 +17,7 @@ class ::Bar; end
it_should_behave_like 'A semipublic Property'
- describe '#typecast_to_primitive' do
+ describe '#typecast' do
it 'returns same value if a class' do
@property.typecast(@model).should equal(@model)
end
View
8 spec/semipublic/property/date_spec.rb
@@ -11,13 +11,13 @@
it_should_behave_like 'A semipublic Property'
- describe '#typecast_to_primitive' do
+ describe '#typecast' do
describe 'and value given as a hash with keys like :year, :month, etc' do
it 'builds a Date instance from hash values' do
result = @property.typecast(
- 'year' => '2007',
- 'month' => '3',
- 'day' => '25'
+ :year => '2007',
+ :month => '3',
+ :day => '25'
)
result.should be_kind_of(Date)
View
14 spec/semipublic/property/date_time_spec.rb
@@ -11,16 +11,16 @@
it_should_behave_like 'A semipublic Property'
- describe '#typecast_to_primitive' do
+ describe '#typecast' do
describe 'and value given as a hash with keys like :year, :month, etc' do
it 'builds a DateTime instance from hash values' do
result = @property.typecast(
- 'year' => '2006',
- 'month' => '11',
- 'day' => '23',
- 'hour' => '12',
- 'min' => '0',
- 'sec' => '0'
+ :year => '2006',
+ :month => '11',
+ :day => '23',
+ :hour => '12',
+ :min => '0',
+ :sec => '0'
)
result.should be_kind_of(DateTime)
View
2  spec/semipublic/property/decimal_spec.rb
@@ -12,7 +12,7 @@
it_should_behave_like 'A semipublic Property'
- describe '#typecast_to_primitive' do
+ describe '#typecast' do
it 'returns same value if a decimal' do
@value = BigDecimal('24.0')
@property.typecast(@value).should equal(@value)
View
2  spec/semipublic/property/float_spec.rb
@@ -11,7 +11,7 @@
it_should_behave_like 'A semipublic Property'
- describe '#typecast_to_primitive' do
+ describe '#typecast' do
it 'returns same value if a float' do
@value = 24.0
@property.typecast(@value).should equal(@value)
View
2  spec/semipublic/property/integer_spec.rb
@@ -11,7 +11,7 @@
it_should_behave_like 'A semipublic Property'
- describe '#typecast_to_primitive' do
+ describe '#typecast' do
it 'returns same value if an integer' do
@value = 24
@property.typecast(@value).should equal(@value)
View
14 spec/semipublic/property/time_spec.rb
@@ -11,16 +11,16 @@
it_should_behave_like 'A semipublic Property'
- describe '#typecast_to_primitive' do
+ describe '#typecast' do
describe 'and value given as a hash with keys like :year, :month, etc' do
it 'builds a Time instance from hash values' do
result = @property.typecast(
- 'year' => '2006',
- 'month' => '11',
- 'day' => '23',
- 'hour' => '12',
- 'min' => '0',
- 'sec' => '0'
+ :year => '2006',
+ :month => '11',
+ :day => '23',
+ :hour => '12',
+ :min => '0',
+ :sec => '0'
)
result.should be_kind_of(Time)
Something went wrong with that request. Please try again.