ERB/Templating clue for ruby - ruby

Optimally I'd want template to look something like this:
interfaces {
<%= name %> {
description "<%= description %>";
mtu <%= mtu %>;
}
}
However, I'd want lines not to be printed if the code evaluates to non-true value. It could be hack to ERB, where after \n it sets print_line = true and after evaluating code with b%> to false it sets print_line = false, and only print if print_line.
But it seems changing ERB for this isn't exactly trivial, it reads whole template, non-code parts are inserted as print "data", and result is single big string of ruby code which is evaluated as whole during #result. I would either need to eval b%> code during string-scanning and just insert print "data" if it returns true, or nothing at all or I'd need to rescan the code in #result to run b%> first.
It seems bit fruitless to even use template, if you end up writing all of it inside code blocks, like:
interfaces {
<%= name %> {
<% if description %>
description "<%= description %>";
<% end%>
<% if mtu %>
mtu <%= mtu%>;
<% end%>
}
}
or:
interfaces {
<%= name %> {
<%= "description \"#{description}\";" if description %>
<%= "mtu #{mtu};" if mtu %>
}
}
I need to support various different configuration formats, in some other configuration format it might be 'maximum-transfer-unit <%= mtu> bytes'. I wish to keep all platform-specific intelligence in the template and actual code in the template minimum. I have no problem adding platform agnostic complexity outside the templates.
Seems like relatively common use-case. Before I go NIHing my own template language or hacking ERB, are there perhaps some other template language better fit for my use-case. Or missingsomething else?
I've implemented the hack for <%b stuff %> where whole line is omitted if that evaluates to false. ERB was not designed for anything like this at all so it's very dirty, and it may be better for me to just write my own template language, unless someone can suggest maybe existing solution where I can cleanly implement something like.
For interested parties, hack is here http://p.ip.fi/-NeJ
Ended up re-inventing the wheel: https://github.com/ytti/malline

Helper methods
def description_helper(description)
"description \"#{description}\";" if description
end
def mtu_helper(mtu)
"mtu #{mtu};" if mtu
end
interfaces {
<%= name %> {
<%= description_helper %>
<%= mtu_helper %>
}
}
Decorator pattern
class MyObject
def description
'some description'
end
def mtu
nil
end
module ViewDecorator
def description
"description \"#{super}\";" if super
end
def mtu
"mtu #{super};" if super
end
end
end
o = MyObject.new
p o.description #=> "some description"
p o.mtu #=> nil
o.extend MyObject::ViewDecorator
p o.description #=> "description \"some description\";"
p o.mtu #=> nil

Related

Puppet Templates Iterations and yaml files

I have the following yaml file in my data dir:
---
type:
- config_setting1:
foo: bar
- config_setting2:
foo: bar
My .erb template looks like this:
conf {
<% settings = YAML.load_file('/etc/puppetlabs/code/environments/example/data/conf.yaml') -%>
<% settings['type'].each do |val| -%>
<%= val %>
<% end -%>
}
When I run puppet on my agent machine I end up with this:
conf {
{"config_setting1"=>nil, "foo"=>"bar"}
{"config_setting2"=>nil, "foo"=>"bar"}
}
My end goal is to get the output to look like this:
conf {
config_setting1 {
foo: bar
}
config_setting2 {
foo: bar
}
}
I know I have some clean up to do on my template to actually get things to output that way, but I'm more focused on the how than the end result at the moment. As you can see I'm familiar with using the ['type'] on the end of the settings to navigate through the nested hash, and I realize I could create this structure pretty easily if I hard coded it but I want to understand how to use it iteratively. I've been attempting to follow the Puppet Documentation on iterations but their examples don't work even when you copy them verbatim... which makes things a little difficult. How can I call pull out a single piece of data in a nested yaml file like I have? Either just the key or just a specific value? I tried something like:
<% settings['type'].each do |val| -%>
<%= settings['val'] %>
<% end -%>
and multiple variations of this but I couldn't find the right syntax to get what I wanted. I've also tried having something along the lines of <% settings['type'].each do |index, value| -%> but I was unable to get any results I could use out of that either. If anyone could point me in the right direction I'd appreciate it. I'm open to being told that I'm going about this entirely the wrong way as well; if there is a better way to get at this data I'm all ears.
Another question that's less important, but still irks me - in my load_file I have the absolute path... is there a way to use relative?
Amazing how typing something out will answer your own question. I realized there was a pretty easy solution. If we take my template:
conf {
<% settings = YAML.load_file('/etc/puppetlabs/code/environments/example/data/conf.yaml') -%>
<% settings['type'].each do |val| -%>
<%= val %>
<% end -%>
}
and on line three replace <% settings['type'].each do |val| -%> with <% settings.keys.each do |val| -%> I'm able to get what I'm looking for. I'd still be interested if there is a better way to do this though, either how I'm loading via yaml or otherwise.

How to concatenate #something inside a render function in a template

I have this function in my template:
<%= for {element, id} <- Enum.with_index(MyProject.PageView.Recursion.buildElements(#header_linkNumber),1) do %>
<%= render FabricaASA.ComponentView, #header_linkType,
button_id: "#{id}",
button_mainStyle: #header_mainStyle
%>
<% end %>
Now I would like to concatenate, on my right side, #header_mainStyle + id so that from other template, for each created element, I could pass: header_mainStyle1, header_mainStyle2,...header_mainStyleN
Also, on the left side, where I have button_mainStyle: I would like to concatenate #header_linkType + _mainStyle: so that I could dynamically change it to, link_mainStyle: or button_mainStyle:
Up to now I wasn't able to do it properly...
I'm afraid you are doing something wrong if you need such thing. Maybe there's a simpler solution...
Anyway: since some version of Phoenix (I'm sorry I don't know which one precisely, maybe 1.0?), #-variables are stored in #conn.assigns map and you can access them by name there. In older versions, these variables were macros and this kind of magic did not work.
So you can try to put this into the controller:
def index(conn, _params) do
render conn, "index.html", [var1: "var1"]
end
and this into the page template:
<p>var1: <%= #var1 %></p>
<p>assigns:</p>
<%= for i <- 1..10 do %>
<p>var<%= i %>:<p>
<pre><%=
varname = "var#{i}" |> String.to_atom
inspect(#conn.assigns[varname]) %>
</pre>
<% end %>
...you will see var1 to var10 bindings (screenshot: http://postimg.org/image/4b4790cjz/). But it's little bit black magic and probably wrong approach.

Ruby - trying to make hashtags within string into links

I am trying to make the hashtags within a string into links.
e.g. I'd like a string that's currently: "I'm a string which contains a #hashtag" to transform into: "I'm a string which contains #hashtag"
The code that I have at the moment is as follows:
<% #messages.each do |message| %>
<% string = message.content %>
<% hashtaglinks = string.scan(/#(\d*)/).flatten %>
<% hashtaglinks.each do |tag| %>
<li><%= string = string.gsub(/##{tag}\b/, link_to("google", "##{tag}") %><li>
<% end %>
<% end %>
I've been trying (in vain) for several hours to get this to work, reading through many similar stackoverflow threads- but frustration has got the better of me, and as a beginner rubyist, I'd be really appreciate it if someone could please help me out!
The code in my 'server.rb' is as follows:
get '/' do
#messages = Message.all
erb :index
end
post '/messages' do
content = params["content"]
hashtags = params["content"].scan(/#\w+/).flatten.map{|hashtag|
Hashtag.first_or_create(:text => hashtag)}
Message.create(:content => content, :hashtags => hashtags)
redirect to('/')
end
get '/hashtags/:text' do
hashtag = Hashtag.first(:text => params[:text])
#messages = hashtag ? hashtag.messages : []
erb :index
end
helpers do
def link_to(url,text=url,opts={})
attributes = ""
opts.each { |key,value| attributes << key.to_s << "=\"" << value << "\" "}
"<a href=\"#{url}\" #{attributes}>#{text}</a>"
end
end
Here is the code to get you started. This should replace (in-place) the hashtags in the string with the links:
<% string.gsub!(/#\w+/) do |tag| %>
<% link_to("##{tag}", url_you_want_to_replace_hashtag_with) %>
<% end %>
You may need to use html_safe on the string to display it afterwards.
The regex doesn't account for more complex cases, like what do you do in case of ##tag0 or #tag1#tag2. Should tag0 and tag2 be considered hashtags? Also, you may want to change \w to something like [a-zA-Z0-9] if you want to limit the tags to alphanumerics and digits only.

How to apply a mapping to a value in a Puppet/Ruby ERB template?

I'm writing an ERB template (for a Puppet module) that gets passed an Hash like this:
{"stuff" => {"foo"=>"aaa", "bar"=>"ccc"},
"other" => {"foo"=>"bbb", "bar"=>"ddd"}}
and I'm iterating over it in my templates producing rows of text:
<% #my_data.each_pair do |k, v| -%>
<%= k %> <%= v["foo"] %>:<%= v["bar"] %>
<% end -%>
Now I'd like to apply some mapping to the "foo" data with a second hash I'll pass to the template. In pseudocode:
mappings = {"aaa" => "something", "bbb" => "somethingelse"}
<% #my_data.each_pair do |k, v| -%>
<%= k %> <%= TRANSLATE_SOMEHOW(v["foo"], mappings) %>:<%= v["bar"] %>
<% end -%>
...in order to get "something" whenever the value was "aaa", and so on. I expect to get the original value if there is no corresponding key in the "mappings".
Doing that kind of thing in Puppet's language is probably possible (by extending it with some Ruby code) I think it is probably more appropriate in the ERB template, but I don't know how to do that and not knowing Ruby isn't helping me - tried google without much success.
I'm looking for code to achieve that in an ERB function or some pointers to relevant documentation for my RTFM pleasure.
EDIT:
for future readers, here's DigitalRoss' answer translated to my ERB example above:
<% #my_data.each_pair do |k, v| -%>
<%= k %> <%= mappings[v["foo"]] || v["foo"] %>:<%= v["bar"] %>
<% end -%>
With the erb stuff removed for clarity, this is what you want to do. (The p() function just prints its argument. You can try this in irb.)
#my_data.each do |k, v|
f, b = v['foo'], v['bar']
p(mappings[f] || f)
p(mappings[b] || b)
end

Rails 3 refactoring issue

The following view code generates a series of links with totals (as expected):
<% #jobs.group_by(&:employer_name).sort.each do |employer, jobs| %>
<%= link_to employer, jobs_path() %> <%= "(#{jobs.length})" %>
<% end %>
However, when I refactor the view's code and move the logic to a helper, the code doesn't work as expect.
view:
<%= employer_filter(#jobs_clone) %>
helper:
def employer_filter(jobs)
jobs.group_by(&:employer_name).sort.each do |employer,jobs|
link_to employer, jobs_path()
end
end
The following output is generated:
<Job:0x10342e628>#<Job:0x10342e588>#<Job:0x10342e2e0>Employer A#<Job:0x10342e1c8>Employer B#<Job:0x10342e0d8>Employer C#<Job:0x10342ded0>Employer D#
What am I not understanding? At first blush, the code seems to be equivalent.
In the first example, it is directly outputting to erb, in the second example it is returning the result of that method.
Try this:
def employer_filter(jobs)
employer_filter = ""
jobs.group_by(&:employer_name).sort.each do |employer,jobs|
employer_filter += link_to(employer, jobs_path())
end
employer_filter
end
Then call it like this in the view:
raw(employer_filter(jobs))
Also note the use of "raw". Once you move generation of a string out of the template you need to tell rails that you don't want it html escaped.
For extra credit, you could use the "inject" command instead of explicitly building the string, but I am lazy and wanted to give you what I know would work w/o testing.
This syntax worked as I hoped it would:
def employer_filter(jobs_clone)
jobs_clone.group_by(&:employer_name).sort.collect { |group,items|
link_to( group, jobs_path() ) + " (#{items.length})"
}.join(' | ').html_safe
end

Resources