30

So here is my problem.

I want to retrieve a string stored in a model and at runtime change a part of it using a variable from the rails application. Here is an example:

I have a Message model, which I use to store several unique messages. So different users have the same message, but I want to be able to show their name in the middle of the message, e.g.,

"Hi #{user.name}, ...."

I tried to store exactly that in the database but it gets escaped before showing in the view or gets interpolated when storing in the database, via the rails console.

Thanks in advance.

4 Answers 4

70

I don't see a reason to define custom string helper functions. Ruby offers very nice formatting approaches, e.g.:

"Hello %s" % ['world']

or

"Hello %{subject}" % { subject: 'world' }

Both examples return "Hello world".

Sign up to request clarification or add additional context in comments.

6 Comments

This is imho the most elegant and simple solution to the problem
I agree. This is the simplest solution using existing functionality.
very nice, I'm going with this.
is it SQL safe interpolation, didn't find anything about it?
The user can break this code easily by providing not existing keys in the template.
|
23

If you want

"Hi #{user.name}, ...."

in your database, use single quotes or escape the # with a backslash to keep Ruby from interpolating the #{} stuff right away:

s = 'Hi #{user.name}, ....'
s = "Hi \#{user.name}, ...."

Then, later when you want to do the interpolation you could, if you were daring or trusted yourself, use eval:

s   = pull_the_string_from_the_database
msg = eval '"' + s + '"'

Note that you'll have to turn s into a double quoted string in order for the eval to work. This will work but it isn't the nicest approach and leaves you open to all sorts of strange and confusing errors; it should be okay as long as you (or other trusted people) are writing the strings.

I think you'd be better off with a simple micro-templating system, even something as simple as this:

def fill_in(template, data)
  template.gsub(/\{\{(\w+)\}\}/) { data[$1.to_sym] }
end
#...
fill_in('Hi {{user_name}}, ....', :user_name => 'Pancakes')

You could use whatever delimiters you wanted of course, I went with {{...}} because I've been using Mustache.js and Handlebars.js lately. This naive implementation has issues (no in-template formatting options, no delimiter escaping, ...) but it might be enough. If your templates get more complicated then maybe String#% or ERB might work better.

2 Comments

Thanks, that worked... I was already storing the single quoted string. I was just missing how to interpolate after getting it from the database. I am aware of the security risk around using this, but the users can't change this string directly.
I will add that if your string contains HTML use the raw (apidock.com/rails/ActionView/Helpers/OutputSafetyHelper/raw) method vs the html_safe method.
0

gsub is very powerful in Ruby.

It takes a hash as a second argument so you can supply it with a whitelist of keys to replace like that:

template = <<~STR
Hello %{user_email}!

You have %{user_voices_count} votes!

Greetings from the system
STR

template.gsub(/%{.*?}/, {
  "%{user_email}" => '[email protected]',
  "%{user_voices_count}" => 5,
  "%{release_distributable_total}" => 131,
  "%{entitlement_value}" => 2,
})

Compared to ERB it's secure. And it doesn't complain about single % and unused or inexistent keys like string interpolation with %(sprintf) does.

1 Comment

I like this one. Simple, elegant, and flexible. It suited my project perfectly.
-1

Adding another possible solution using Procs:

#String can be stored in the database
string = "->(user){ 'Hello ' + user.name}"

proc = eval(string)

proc.call(User.find(1)) #=> "Hello Bob"

1 Comment

Be very careful using eval(); you're giving that code full access to your system.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.