Hi Tony,
I had to do something similar a couple of months ago, and I tackled
it like this:
I created a module that defined an array of hashes which represented
the supported tags. Here are some examples:
tags << {
:find => 'order.id',
:verify => 'id',
:replace => 'id.to_s',
:default => '',
:description => 'The ID of the order.',
:domain => 'Orders'
}
tags << {
:find => 'order.status',
:verify => 'status',
:replace => 'status.name',
:default => '',
:description => 'The status of the order.',
:domain => 'Orders'
}
tags << {
:find => 'order.user.name',
:verify => 'user',
:replace => 'user.name',
:default => '',
:description => 'The name of the user who submitted the order.',
:domain => 'Orders'
}
and then to make parsing a little easier, wrap each “find” in some
kind of marker
tags.each do |tag|
tag[:find] = "{t:#{tag[:find]}}"
end
this happens to be similar to RadiantCMS’s style, so your the tag in
text will look like
Hi {t:order.user.name}, your order shipped on {t:order.shipped}.
I included “description” and “domain” for building a small help popup
for the user when inserting tags. I had tags in two main domains,
Users and Orders, but this way I could add whatever else I needed.
Now, to parse these, I came up with this method:
def TemplateTags.replace_tags(object, template, content = nil)
tags = supported_tags # this method returns the array of support
tags
tags.each do |tag|
find = tag[:find]
replace = tag[:replace]
verify = tag[:verify]
default = tag[:default]
if find.include?('content')
if content
template.gsub!(find, content)
end
elsif replace
begin
replace = if object.instance_eval(verify) then
object.instance_eval(replace) else default end
rescue
replace = default
end
template.gsub!(find, replace)
end
end
return template
end
To this, you pass an object (order, user, whatever), the template
(with all of the tags), and, in my case, an optional “content”.
Content was something that the system needed to put in, and it’s
template tag was defined as
tags << {
:find => 'content',
:verify => nil,
:replace => nil,
:default => nil,
:description => 'Place holder for system generated messages.',
:domain => 'Orders'
}
This is kind of a kludge, because the user had to know to put
{t:content} in their template. But in the particular case in which
this was implemented, that was a reasonable request.
The trick to this is verifying that the object can actually respond
to the method that you might want to call on it, which is the purpose of
if object.instance_eval(verify)
Essentially what you are doing is saying "check to see if the object
has , and if it does, then ask it for ". Two
examples are the tags noted above, order.id and order.status. First,
order.id:
the template will look like this:
“Your order, id #{t:order.id}, shipped yesterday”
so in the code, you pass an order object to replace_tags.
replace_tags needs to make sure that object can respond to id, and if
it can, it calls id. So in this case you are doing something like
if object responds id, then call id
In the second example, order.status, my status was an association, so
to get to it normally, you’d use order.status.name. The tag would
look like
“Your order is now {t:order.status}”
so in replace_tags, I first had to check to see if the object had the
proper association, which is the verify:
if object responds to status, call status.name
I hope that makes sense. This is the first time I’ve had to try to
explain it to anyone, and typing it out at that! To sit down with
someone and explain it would, I think, be much easier. I also admit
that this might not be the best way to address this problem, but this
worked for what I needed. The main downside is you have to create an
array of hashes that represent all of the tags you support, but I
don’t know how to get around that.
Let me know if you have any questions. I can create a small working
example if you need me to.
Peace,
Phillip