Carlos Brando

Nome do Jogo

O novo sistema de interpolação de Strings do Ruby 1.9

Embora o Ruby 1.9 já tenha sido liberado há algum tempo, infelizmente ele ainda é inacessível para muitos de nós, já que muitos projetos foram iniciados em versões mais antigas do Rails, quando ele ainda era incompatível com esta nova versão do Ruby. E mesmo aqueles que estão utilizando as versões mais recentes do Rails, também precisam tomar certos cuidados já que nem todos os plugins e gems já foram preparados e testados para serem executados nesta versão do Ruby.

Já faz um tempo que o core team do Rails vêm aceitando patches que implementam certos recursos presentes apenas no Ruby 1.9, permitindo que mesmo projetos rodando sob as versões 1.8.x também possam se beneficiar destas novas funcionalidades. Uma dessas novidades que recentemente foi incluída na versão de desenvolvimento do Rails (Edge Rails) e que estará presente no próximo release, foi o novo sistema de interpolação de Strings do Ruby 1.9.

Atualmente utilizamos muito o conhecido #{} para realizar esta atividade, como demonstrado no exemplo abaixo.

1
2
3
4
5
firstname  = "Carlos"
familyname = "Brando"

puts "#{firstname} #{familyname}"
# => "Carlos Brando"

No Ruby 1.9, além da forma exibida acima, também podemos utilizar %{} para informar os valores a serem interpolados através de um objeto do tipo Hash. Seguindo a seguinte sintaxe:

1
2
3
4
person = {:firstname => "Carlos", :familyname => "Brando"}

puts "%{firstname} %{familyname}" % person
# => "Carlos Brando"

Se você gosta de viver no limite e utiliza a versão de desenvolvimento do Rails em seus projetos, então já pode começar a utilizar este novo recurso, mesmo que ainda nem tenha o Ruby 1.9 instalado em seu computador. Mas, se você é um pouco mais conservador e não gosta de arriscar (o que pode evitar alguns problemas com bugs), você tem duas opções: esperar pelo Rails 2.3.3 (ou quem sabe o 3.0, não sei qual será o próximo) ou implantar você mesmo esta funcionalidade no seu projeto Rails atual.

Se você optou pela segunda opção, basta criar um arquivo qualquer no diretório lib do seu projeto e incluir o seguinte código nele:

string.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
if RUBY_VERSION < '1.9'

=begin
  string.rb - Extension for String.

  Copyright (C) 2005-2009 Masao Mutoh

  You may redistribute it and/or modify it under the same
  license terms as Ruby.
=end

# This feature is included in Ruby 1.9 or later but not occur TypeError.
#
# String#% method which accepts named arguments. Particularly useful if the
# string is to be used by a translator because named arguments mean more
# than %s/%d style.
class String

  unless instance_methods.find {|m| m.to_s == 'bytesize'}
    # For older ruby (such as ruby-1.8.5)
    alias :bytesize :size
  end

  alias :_old_format_m :% # :nodoc:

  PERCENT_MATCH_RE = Regexp.union(
      /%%/,
      /%\{(\w+)\}/,
      /%<(\w+)>(.*?\d*\.?\d*[bBdiouxXeEfgGcps])/
  )

  # call-seq:
  #   %(arg)
  #   %(hash)
  #
  # Format - Uses str as a format specification, and returns the result of applying it to arg.
  # If the format specification contains more than one substitution, then arg must be
  # an Array containing the values to be substituted. See Kernel::sprintf for details of the
  # format string. This is the default behavior of the String class.
  #   * arg: an Array or other class except Hash.
  #   * Returns: formatted String
  # Example:
  #    "%s, %s" % ["Masao", "Mutoh"]
  #
  # Also you can use a Hash as the "named argument". This is recommended way so translators
  # can understand the meanings of the msgids easily.
  #   * hash: {:key1 => value1, :key2 => value2, ... }
  #   * Returns: formatted String
  # Example:
  #   For strings.
  #   "%{firstname}, %{familyname}" % {:firstname => "Masao", :familyname => "Mutoh"}
  #
  #   With field type to specify format such as d(decimal), f(float),...
  #   "%<age>d, %<weight>.1f" % {:age => 10, :weight => 43.4}
  def %(args)
    if args.kind_of?(Hash)
      ret = dup
      ret.gsub!(PERCENT_MATCH_RE) {|match|
        if match == '%%'
          '%'
        elsif $1
          key = $1.to_sym
          args.has_key?(key) ? args[key] : match
        elsif $2
          key = $2.to_sym
          args.has_key?(key) ? sprintf("%#{$3}", args[key]) : match
        end
      }
      ret
    else
      ret = gsub(/%([{<])/, '%%\1')
      begin
        ret._old_format_m(args)
      rescue ArgumentError => e
        if $DEBUG
          $stderr.puts "  The string:#{ret}"
          $stderr.puts "  args:#{args.inspect}"
          puts e.backtrace
        else
          raise ArgumentError, e.message
        end
      end
    end
  end
end

end

Não esqueça de carregar o arquivo utilizando o método require antes de utiliza-lo. É recomendado que você faça isto em algum arquivo do diretório initializers (talvez seja bom criar um arquivo apenas para esta finalidade). Também recomendo estudar o código acima para entender o que está acontecendo antes de simplesmente sair usando.

Ah, e você também pode definir tipos numéricos assim:

1
2
"%<age>d, %<weight>.1f" % {:age => 10, :weight => 43.4}
# => "10, 43.4"

Para mais informações sobre as novidades do Rails, siga-me no Twitter: @carlosbrando.

Comments