Usando number_to_currency em modelos no Rails

16/06/07

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

Comentários #


#1 Rafael disse:
17 Jun 07, 11:38PM

Achei interessante este método para uso no modelo e no controller, mas já trabalhei bastante com valores no BielBid (http://bielbid.com.br) e não senti necessidade.

Temos que ter atenção para não confundir as coisas, como se trata de uma formatação de valores, este método deve ser utilizado basicamente no view mesmo.

O mais correto é trabalhar com o valor cru (raw data) no modelo.

Não é qualquer um que vai pegar a sua extensão e vai utilizá-la respeitando os conceitos de MVC!

Abraços

#2 Thomaz Leite disse:
18 Jun 07, 01:24AM

Concordo 100% com o Rafael (como já tinha me expressado no fórum em http://forum.rubyonbr.org/forums/1/topics/1680#posts-9535).

#3 Nando Vieira disse:
18 Jun 07, 04:41AM

No Spesa, deixo o usuário escolher se ele quer enviar os dados como '100.00' ou '100,00'. Fiz isso usando um attr_accessor de nome formatted_amount. No before_validation, uso o método to_number. E tem o caso contrário que é converter o atributo formatted_amount com amount.to_currency no after_find.

Como vocês fariam essa lógica? A de exibição, acho mais simples, pois já existe um método nativo. Mas e a de conversão de uma string '100,00' para número?

Sou adepto de modelos gordos[1]. Então, *para mim*, faz todo o sentido.

[1] http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model

#4 Clovis disse:
18 Jun 07, 09:42AM

perfeito cara, eu custumo fazer isso com algumas gambiarras, hehehhehee, sempre tive medo de tentar um override da classe, por nao ter tempo de estudar corretamnete como elas funcionam.. mas pelo visto, voce ja esta craque nisto.

parabens.

#5 Leonardo Faria Coelho disse:
05 Jul 07, 07:17PM

Muito bom Nando. veio numa hora quase certa

#6 Thiago disse:
05 Maio 08, 04:13PM

Opa, Seu post salvou uma vida heheh! :-)

Muito bom…

Abraços

#7 Thiago Antonius disse:
03 Ago 08, 03:57PM

Olá Nando, estou utilizando esse modulo e está sendo muito útil.
Pra mim eu uso nos controllers quando vou gerar relatórios.

Gostaria de saber se você tem ou conhece algum script para converter um valor real para extenso.

Obrigado.

#8 Nando Vieira disse:
04 Ago 08, 10:05AM

Se não estou enganado, o Brazilian Rails tem uma gem para isso. http://github.com/tapajos/brazilian-rails/tree/master

Deixe um comentário




Este blog usa o Gravatar.


Não é aceito código HTML:
adicione-o no pastie.caboo.se ou paste.milk-it.net e poste apenas o link.

Se este é seu primeiro comentário, ele terá que ser aprovado antes de ser exibido.