loading...

Replacing placeholders in Ruby on Rails

dpaluy profile image David Paluy Updated on ・2 min read

Many times we need to include dynamic data in our text. There are many ways to do this, just by having gsub method.

I'd like to share my favorite approach for placeholders replacing.

Let's assume we have Article class and we want it to generate a dynamic title that can be used for the SEO purpose.

The "Article" has name:string and reviews_count:integer attributes.
We need to generate a dynamic title: [name] - [reviews_count] reviews. The title pattern can be customized in our back-office.

article.title_patern = '[name] - [reviews_count] reviews'

Let's use the Ruby on Rails metaprogramming techniques to implement this functionality:

class Article < ApplicationRecord
  def title
    title_pattern.gsub(/\[(.*?)\]/) { |key| dynamic_value(key) }.strip
  end

  private

  DYNAMIC_CMDS = %w[
    name
    reviews_count
  ].freeze

  def dynamic_value(key)
    cmd = key[/\[(.*?)\]/, 1]
    DYNAMIC_CMDS.include?(cmd) ? send(cmd) : ''
  end
end

Now, we can do:

article = Article.new(name: 'Amazing Ruby', reviews_count: 123)
article.title # "Amazing Ruby - 123 reviews"

Let's review the code together.

def title
  title_pattern.gsub(/\[(.*?)\]/) { |key| dynamic_value(key) }
end

We are using gsub with a block pattern that helps us to use logic to decide how to replace something. We are searching for any words in square brackets [ANYTHING]. Of course, you can define your own pattern, for example: {{NAME}} or %NAME%. Just change the Regex accordingly.

In our case, the title_patern is [name] - [reviews_count] reviews. So the gsub will search for each key in brackets. We have two keys:

  1. [name]
  2. [reviews_count]

for each key, will call dynamic_value method.

I'm using ruby send. There are various options dynamically call the method or attribute in ruby. But send method has the best performance.

Note: dynamic methods can be dangerous. Especially, send can invoke private methods. This is the reason, DYNAMIC_CMDS check is required to keep your code safe.

Posted on by:

dpaluy profile

David Paluy

@dpaluy

"Of course, I'm an optimist - I don't see much point in being anything else."

Discussion

pic
Editor guide
 

I would do that with the I18n gem that ships with Rails instead, it's made for that, handles plurals, prepares you for multiple languages if that was ever needed down the road, and it's as easy to change for non-developers.

In config/locals/en.yml

en:
  name_with_reviews:
    one: %{name} - %{count} review
    other: %{name} - %{count} reviews

The count key has a special role that tells the gem which translation to pick. You can read more about pluralization here: guides.rubyonrails.org/i18n.html#p....

And in your views (or in Article#title)

I18n.t "name_with_reviews", name: article.name, count: article.reviews_count
 

If the template is static, definitely the I18n is the right solution. But we have to provide the ability to the business team to customize those SEO templates

 

You can still do it with I18n if your business team needs to input those via an admin interface instead of a yaml file.

 I18n.backend.store_translations "en", name_with_reviews: { one: article.title_pattern_singular, other: article.title_pattern_plural }

and then I18n.t("name_with_reviews", count: 2, name: "Bob")

Of course if you don't want to support plural it's just

 I18n.backend.store_translations "en", name_with_reviews: article.title_pattern }

and I18n.t("name_with_reviews", name: "Bob")