Brazilian currency (Real) to Decimal coercion type


#1

Hi all,

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.

Any other suggestions?

Best regards,
Abinoam Jr.


#2

Hey!

I’m glad you like it :slight_smile:

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.


#3

Thanks for answering!

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