This is my first try on dry-types and I’m really enjoying it!
require 'dry-types'
module Types
include Dry::Types.module
DecimalFromCurrencyReal =
Types::Strict::Decimal.constructor do |value|
value =
value.
gsub(/[^\d,]/, ""). # Remove non-numbers and non-commas
tr(',','.') # Translate comma into dot
BigDecimal.new(value)
end
end
Types::DecimalFromCurrencyReal[" R$ 12.345.678,90 "]
# => #<BigDecimal:7fb452589770,'0.123456789E8',18(27)>
For a type that coerces input, should the name be something like that “DecimalFromCurrencyReal”?
Calling BigDecimal.new(value) at the end of the block to coerce the value, is this the right way to do that? Or is there something built-in?
How can I guarantee that the given value is a String so I can call gsub on it without any fear of raising an exeception? I mean, I would like to raise an exception before calling gsub on it.
It’s really up to you, the built-in types use separate namespaces, so maybe you could follow that convention if you have many types that fall into the same category.
You could use built-in decimal for that if you want, so Types::Coercible::Decimal[value].
If you provide a custom constructor then you must handle that yourself. So ie perform a type check first like value.is_a?(String) and raise TypeError if it’s something unexpected.
There’s a bit more info about this topic in this issue.
I’m doing some experimentation with the defined type and it’s working.
Currently I have this code:
require 'dry-types'
require 'disposable/twin/coercion'
module Disposable::Twin::Coercion::Types
include Dry::Types.module
DecimalFromCurrencyReal =
Strict::Decimal.constructor do |value|
# Coerce to String
value = String(value)
# Convert from Brazilian currency format to
# "Float as String" format
value =
value.
gsub(/[^\d,]/, ""). # Remove non-numbers and non-commas
tr(',','.') # Translate comma into dot
# Coerce to BigDecimal
BigDecimal(value)
end
end
Disposable::Twin::Coercion::Types::DecimalFromCurrencyReal[" R$ 12.345.678,90 "]
## => #<BigDecimal:7fb452589770,'0.123456789E8',18(27)>
class PricesTwin < Disposable::Twin
# Enable the syncing functionality
feature Sync
# Enable the coercion (the 'type' directive)
feature Coercion
# Don't load values from twinned object on setup
feature Setup::SkipSetter
property :name
property :value, type: Types::DecimalFromCurrencyReal
end
Prices = Struct.new(:name, :value)
p = Prices.new
ptwin = PricesTwin.new(p)
# Add a Brazilian Currency formatted string (with some bogus spaces)
ptwin.value = " R$ 12.345.678,91 "
# => " R$ 12.345.678,91 "
# And it is imediatly coerced as #constructor is called for the
# defined type
ptwin.value
# => #<BigDecimal:7fa1b74a8408,'0.1234567891E8',18(27)>
# The twinned object remains untouched
p.value
# => nil
# Calling #sync and it will copy the values to the twinned object
ptwin.sync
# => #<struct Prices name=nil, value=#<BigDecimal:7fa1b74a8408,'0.1234567891E8',18(27)>>
p.value
# => #<BigDecimal:7fa1b74a8408,'0.1234567891E8',18(27)>
p.value.to_f
# => 12345678.91