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:
[name]
[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)
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
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
)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.
and then
I18n.t("name_with_reviews", count: 2, name: "Bob")
Of course if you don't want to support plural it's just
and
I18n.t("name_with_reviews", name: "Bob")