'Replace smartcodes with gsub
I work on ruby > 3.0 and i need to replace a text content (in-line html text with smartcode). this text can be long and for example it's like this : "Hello, {{viewer_name}} ! How are you ?"
I have a method to replace theses smartcodes :
def populate_smartcodes(content)
content.gsub(/\{{(.*?)\}}/).each do |value|
smartcode = value[/#{Regexp.escape('{{')}(.*?)#{Regexp.escape('}}')}/m, 1]
str_smartcode = "{{#{smartcode}}}"
case smartcode
when 'viewer_name'
content = content.gsub(str_smartcode, viewer.name)
when 'company_city'
content = content.gsub(str_smartcode, company.city )
end
content
end
company_city and viewer_name are variables i need to provide with viewer an instance of User::Viewer.
And i have a lot of smartcodes to replace ... I think this is not a good way to do it but it's working.
Can you help me to improve the performance or the way to do it ?
Edit :
I have pdf content shown to the user, they just have a list of 'keys' or 'smartcode' available, this way everyone can change their pdf as the want to (through the content). We keep only a text with the pdf content in the database. Maybe we can use another strategy ? For now i wanted a way to replace string element by another value.
Solution 1:[1]
It is possible to extract smartcodes from the template in a much simpler (cleaner?) way. We know for sure, that they always contain 4 parentheses - 2 from each side. So we can just take what gsub matches, and take 2..-3 chars as the actual binding name:
content = "Hello, {{viewer_name}}! How are you?"
content.gsub!(/{{\w+?)}}/) do |matched_chunk|
matched_chunk[2..-3] # => viewer_name
end
Next, we would like to resolve these matched smartcodes into something meaningful. And we would like to do it dynamically to avoid hardcoding of many-many individual smartcodes, correct?
In your example you resolve viewer_name into viewer.name and company_city into company.city - it looks like some pattern <receiver>.<property>. If this is the case, we could use this "generality" to resolve things dynamically without the need to hardcode resolution for each "smartcode":
content.gsub!(/{{\w+?)}}/) do |matched_chunk|
recv, prop = matched_chunk[2..-3].split("_", 2)
instance_eval("#{recv}.#{prop}") # the receiver should be defined in the current scope, obviously
rescue
matched_chunk
end
Well, it could work. Probably :) But it smells as hell:
I limit the split by 2, to ensure we always stick with
receiver.propertypattern. So, something likeuser_first_namewill be resolved asuser.first_name. But this assumption might be just wrong in general! It might be in fact something likeuser.first.name, or evenfoo_barthat should be resolved intosomething_else- so this solution is fragile at minimum...instance_evalis a Pandora box, and we should be extremely careful with it. Idealy, We should call it in an isolated, safe context (some blank slate object)
Both of these problems are solvable: we can create a special template builder object as a safe blank late context for our instance_eval; we could then inject the necessary bindings so that instance_eval could resolve our smartcodes properly (in this case we could even replace instance_eval with safer magic or maybe no magic at all - hardcoding things sometimes is not that bad idea) etc...
But following this rabbit hole, we would sooner or later recreate something like liquid or mustache, just way less mature and more error-prone.
So my proposal is don't play around regexes, try to adopt some battle-tested template engine first (and roll back to the custom implementation only if you have really strong reasons) :)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Konstantin Strukov |
