DEV Community

David Paluy
David Paluy

Posted on • Edited on

Replacing placeholders in Ruby on Rails

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
Enter fullscreen mode Exit fullscreen mode

Now, we can do:

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

Let's review the code together.

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

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.

Top comments (3)

Collapse
 
sowenjub profile image
Arnaud Joubay • Edited

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
Collapse
 
dpaluy profile image
David Paluy

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

Collapse
 
sowenjub profile image
Arnaud Joubay • Edited

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")