sparky
sparky

Reputation: 142

Pass a string as method parameter to be interpolated later

Is it possible to pass a string to a method in ruby and have that method interpolate that string?

I have something like this in mind:

do_a_search("location = #{location}")
...
def do_a_search(search_string)
  location = .... #get this from another source
  Model.where(search_string)
end

The context is RoR but this is a general ruby question I think. I realise the example above looks a bit convoluted but I'm trying to refactor a bunch of very repetitive methods.

The issue is that if I put the string to be interpolated in double quotes, location doesn't exist when the method is called, if I put it in single quotes, it will never be interpolated...

What I really want to do is put it in single quotes and interpolate it later. I don't think this is possible, or am I missing something?

edit: to be clear (as I think I have oversimplified what I'm trying to do above), one of the issues here is that I might want to call this method in multiple contexts; I might actually want to call

do_a_search("country = #{country}")

or even

do_a_search("country = #{country} AND location = #{location})

(country also existing as a local var within my method). I therefore want to pass everything necessary for the substitution in my method call

I think the String.interpolate method from the facets gem would solve my problem but it doesn't work in rails 4

Upvotes: 4

Views: 2652

Answers (4)

Uri Agassi
Uri Agassi

Reputation: 37409

You can late-bind the extrapolation by using binding and ERB:

require 'erb'

def do_a_search(search_string)
  location = 'this'
  ERB.new(search_string).result(binding)
end

do_a_search('location = <%= location %>')
# => "location = this"

Or, you can directly use eval:

def do_a_search(search_string)
  location = 'this'
  eval search_string, binding
end

do_a_search('"location = #{location}"')
# => "location = this"

This will, of course, be acceptable only if the strings you receive in do_a_search are trusted and/or sanitized.

Upvotes: 0

sparky
sparky

Reputation: 142

As I mentioned above, the Facets Gem would help with this, but doesn't seem to work in rails 4

However its code to extend String is very simple:

class String
  def self.interpolate(&str)
    eval "%{#{str.call}}", str.binding
  end
end

I think that implementing & then using that inside the method is the most sensible way of doing what I'm looking for, but I'll accept Sawa's answer, because as Paul Richter & Chuck point out, it would I think work, but at the risk of "blowing up" if I don't quite get the calls right.

Upvotes: 2

Chuck
Chuck

Reputation: 237010

What you want is basically a format string, so I think you'd be better served by sprintf or %.

do_a_search("location = %s")
...
def do_a_search(search_string)
  location = .... #get this from another source
  Model.where(search_string % location)
end

If you have multiple things you want to interpolate and don't want to enforce an order, you can use a Hash and named specifiers.

do_a_search("location = %{location}")
...
def do_a_search(search_string)
  location = .... #get this from another source
  Model.where(search_string % {location: location})
end

Upvotes: 1

sawa
sawa

Reputation: 168071

For that purpose, % is used. First, create a string:

s = "location = %{location}"

Later, you can apply % to it:

s % {location: "foo"} # => "location = foo"

If you do not have to name the parameter(s), then it is simpler:

s = "location = %s"
s % "foo" # => "location = foo"

Upvotes: 10

Related Questions