Usando number_to_currency em modelos no Rails


Leia em 1 minuto

No PHP, uma função muito usada é number_format, mas no Ruby não temos nada assim nativamente. O Rails implementa sua própria versão com o helper number_to_currency, que satisfaz totalmente minhas necessidades. O problema é que ele só está disponível na view e, sendo assim, você não consegue utilizá-lo nem no modelo, nem no controller.

Para resolver isto, estendi algumas classes de modo que isso esteja disponível para qualquer instância de números (Fixnum, Bignum ou Float). Adicionalmente, um método to_number foi adicionado aos objetos da classe String, convertendo valores como 150.00 ou 150,00 para números do tipo Float.

module Currency
  BRL = {:delimiter => ".", :separator => ",", :unit => "R$", :precision => 2, :position => "before"}
  USD = {:delimiter => ',', :separator => ".", :unit => "US$", :precision => 2, :position => "before"}
  DEFAULT = USD.merge(:unit => "$")

  module String
    def to_number(options={})
      return self.gsub(/,/, '.').to_f if self.numeric?
      nil
    end

    def numeric?
      self =~ /^(|-)?[0-9]+((\.|,)[0-9]+)?$/ ? true : false
    end
  end

  module Number
    def to_currency(options = {})
      number = self
      default   = Currency::DEFAULT.stringify_keys
      options   = default.merge(options.stringify_keys)
      precision = options["precision"] || default["precision"]
      unit      = options["unit"] || default["unit"]
      position  = options["position"] || default["position"]
      separator = precision > 0 ? options["separator"] || default["separator"] : ""
      delimiter = options["delimiter"] || default["delimiter"]

      begin
        parts = number.with_precision(precision).split('.')
        number = parts[0].to_i.with_delimiter(delimiter) + separator + parts[1].to_s
        position == "before" ? unit + number : number + unit
      rescue
        number
      end
    end

    def with_delimiter(delimiter=",", separator=".")
      number = self
      begin
        parts = number.to_s.split(separator)
        parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\1#{delimiter}")
        parts.join separator
      rescue
        self
      end
    end

    def with_precision(precision=3)
      number = self
      "%01.#{precision}f" % number
    end
  end
end

class Fixnum; include Currency::Number; end
class Bignum; include Currency::Number; end
class Float; include Currency::Number; end
class String; include Currency::String; end

Para ter estes novos métodos funcionando basta adicionar o código acima ao arquivo "environment.rb". Veja alguns exemplos de uso:

number = 9999.99

# using default settings
puts number.to_currency # $9,999.99

# using Brazilian Real
puts number.to_currency(Currency::BRL) # R$9.999,99

# using US Dollar
puts number.to_currency(Currency::USD) # US$9,999.99

# setting default to Brazilian Real
silence_warnings do
  Currency::DEFAULT = Currency::BRL
end

# using default settings, now
# Brazilian Real
number.to_currency # R$9.999,99

puts '199.99'.to_number # 199.99
puts '199,99'.to_number # 199.99
puts '+199,99'.to_number # 199.99
puts '-199,99'.to_number # -199.99