Rubyで文字列の式展開を後から行うには?


例えば、なんらかの名前を受け取り、その名前に対して挨拶するようなクラスとして、以下のようなものを定義したとします。

[code]

     class SimpleGreeting
       def initialize name
         @name = name
       end
    
       def say
         "Hello, #{@name}"
       end
     end
    
[/code]

このとき、`say`の内部にある文字列を変えることができれば、もっと柔軟な挨拶ができるのではないか、と思いました。しかし、最初に文字列を渡して、あとから式展開を行うスマートな方法がわかりません。それに近い挙動は再現できたのですが、とてもスマートとはいえない気がします。

[code]

     class Greeting
       def initialize name, template
         @name = name
         @template = "\"#{template}\""
       end
    
       def say
         eval <<-EOF
     name = '#{@name}'
     #{@template}
         EOF
       end
     end
    
[/code]

このようなクラスを定義してあげれば、確かに下のように利用することは可能です。

[code]

     puts Greeting.new('esehara', 'Hello, #{name}').say
     #output: Hello, esehara
     puts Greeting.new('esehara', 'こんにちは, #{name}').say
     #output: こんにちは, esehara
    
[/code]

とはいえ、このような挙動を、もっと簡単に実現する方法がありそうな気がしましたが、どうでしょうか。それとも、このような文字列の使い方そのものが余りスマートではないと考えるべきなのでしょうか?




initialize にブロックを渡すのがスマートかなと思います。

[code]

    class Greeting
      def initialize(name, &block)
        @name = name
        @block = block
      end
    
      def say
        @block.call(@name)
      end
    end
    
    puts Greeting.new("Fate") { |name| "Hello, #{name}" }.say
    puts Greeting.new("フェイトちゃん") { |name| "こんにちは, #{name}" }.say
    
[/code]

結果:

[code]

    Hello, Fate
    こんにちは, フェイトちゃん
    
[/code]

attr_accessor を定義すると、さらに汎用的に使えそうです。




敢えて `eval` を使いつつスッキリさせるならば、式中に`{}`を含められない制限はできてしまいますが、こんな感じでしょうか。

[code]

    class Greeding
      def initialize name, template
        @name = name
        @template = template
      end
    
      def say
        @template.gsub(/#\{([^}]*)\}/) { eval($1).to_s }
      end
    end
    
[/code]

文字列中の `#{EXPR}` を正規表現で抜き出して `eval(EXPR).to_s` の結果に置き換えています。

ただしその例のように単に変数を参照するだけならば [Kernel#format](http://docs.ruby-
lang.org/ja/2.1.0/class/Kernel.html#M_FORMAT) を使って、`say`
メソッドは以下のように書き換えて、与えるテンプレートを `'Hello, %{name}'` のように変えてしまうのも良いのではないでしょうか。

[code]

    def say
      @template % { name: @name }
    end
    
[/code]

