Puppet ERB templates: How to inject variable into statement - ruby

I'd like to make some modifications on variable in erb templates:
e.g. decode base64 and split string
It tried
<%= Base64.decode64(#my_variable).rpartition(':').last %>
and
<%= Base64.decode64(<%= #my_variable %>).rpartition(':').last %>
and via scope
<%= Base64.decode64(scope['my_class::my_variable']).rpartition(':').last %>
but it is nil.
Filepath: /usr/lib/ruby/1.9.1/base64.rb
Line: 58
Detail: undefined method `unpack' for nil:NilClass
puppet version 3.8.2

The first example that you tried is the correct ERB notation.
That error would be occurring because you passed the nil object to Base64.decode64():
[1] pry(main)> require 'base64'
=> true
[2] pry(main)> Base64.decode64(nil).rpartition(':').last
NoMethodError: undefined method `unpack' for nil:NilClass
from /Users/alexharvey/.rvm/rubies/ruby-2.1.4/lib/ruby/2.1.0/base64.rb:58:in `decode64'
(Admittedly, I don't have your Ruby 1.9.1 and I wasn't able to easily install it, but it's unlikely this changed.)
That means that #my_variable is set to nil, and that would happen if you failed to actually set the Puppet variable $my_variable in your manifest.
So you would need to call the template with a block something like this:
$my_variable = 'foo'
file { '/tmp/foo':
ensure => file,
content => template('test/mytemplate.erb')
}

Related

Only execute code in erb if variable exists?

Ruby newbie here who just started using Ruby with .erb templates and I'm having a problem with the code. I have a hangman game that's passing variables from the .rb file to the .erb file and everything was working fine until I tried to check it on initial load (no variables present) and it threw errors. So I figured I'd use defined? with an if statement to check if the variable exists and then execute the code if it does and ignore if doesn't. It works fine when I use:
<% if defined?bad_guesses %>
<%= bad_guesses %>
<% end %>
But the information I need is an array and when I try to use an .each or .times statement like this:
<% if defined?bad_guesses %>
<% bad_guesses.each do |i| %>
<%= i %>
<% end %>
<% end %>
I get:
NoMethodError at /
undefined method `each' for nil:NilClass
C:/Projects/hangman/views/index.erb in block in singleton class
<% bad_guesses.each do |i| %> hangman.rb in block in
erb :index, :locals => {:game_status => game_status, :bad_guesses => bad_guesses, :good_guesses => good_guesses, :word => word}
Any suggestions appreciated.
Also, is this even the proper way to do this? When you make an .erb template that uses variables passed in from a class in your .rb file, how do you ignore it until it exists to the template?
Passing variables using:
get '/' do
if params['make'] != nil
make = params['make'].to_i
game_status, bad_guesses, good_guesses, word = Hangman.get_word(make)
elsif params['guess'] != nil
guess = params['guess'].to_s
game_status, bad_guesses, good_guesses, word = Hangman.check_guess(guess)
end
erb :index, :locals => {:game_status => game_status, :bad_guesses => bad_guesses, :good_guesses => good_guesses, :word => word}
end
Looking at this:
<% if defined?bad_guesses %>
<% bad_guesses.each do |i| %>
<%= i %>
<% end %>
<% end %>
a few points:
defined?a is bad style; use defined?(bad_guesses) or defined? bad_guesses instead.
defined? checks if it's defined, so if you say foo = nil; defined? foo it will be true.
You could alternatively use this:
defined?(bad_guesses) && bad_guesses
On the other hand, undefined instance variables are nil by default:
# it shows undefined
defined? #non_existing_var
# but you can still check if it's truthy:
puts "found" if #non_existing_var
# it won't print anything
Similar to instance variables in this regard are hashes. The default value of an unknown key is nil.
The problem with instance variables is they are not scoped to the partial. Instead, I recommend sending your local variables as a nested hash:
locals: { data: { foo: "bar" } }
Then you can safely check for values which may not exist:
if data[:foo]
# this runs
elsif data[:non_existent]
# this doesnt run
end
For my purposes, the following syntax worked:
<% if some_var %>
<%= some_var %>
<% end %>
This block is rendered if some_var is not nil.

Ruby: How to pass a variable from server to index?

something = "0"
get "/" do
erb :index, :locals => something
end
When I do this and go to localhost, it says undefined method `keys' for "0":String. I am using sinatra. How do I pass a variable from server to index?
You're half way there, you just need to make something an object.
get "/" do
erb :index, :locals => {:something => 0}
end
and then just use it in the your index:
Something: <%= something %>

String operations not working in Sinatra / ERB

Trying to truncate a string in Ruby/Sinatra in an .erb page.
I have been trying variants of:
<%= #caption_str.truncate(20) %>
<%= #caption_str[0..20] %>
But keep getting error messages of the kind:
NoMethodError at /392471267473009
undefined method `[]' for nil:NilClass
or
NoMethodError at /392471267473009
undefined method `truncate' for nil:NilClass
All is well if I don't truncate the string, i.e.
<%= #caption_str %>
What am I missing?
NoMethodError at /392471267473009
undefined method `[]' for nil:NilClass
or
NoMethodError at /392471267473009
undefined method `truncate' for nil:NilClass
The errors are descriptive enough.
They convey that there is no [] or truncate method defined for nil:NilClass, which in this case turns out to be your #caption_str object.
Check if #caption_str is not nil and then do these operations. When #caption_str would be nil, you'll get the same error.
Since, Ruby is a dynamic programming language, we tend to forget the edge-cases when the values would be nil. Always include checks when similar situations arise.

Heroku Ruby NoMethodError string.capitalize

I use the following code to group locations depending on the first letter.
mobile_controller:
def index
#locations = Location.all.group_by{|l| l.name[0].capitalize.match(/[A-Z]/) ? l.name[0].capitalize : "#"}
end
view:
<% #locations.keys.sort.each do |starting_letter| %>
<%= starting_letter %>
<% #locations[starting_letter].each do |location| %>
<%= location.name %>
<% end %>
<% end %>
Everything works fine on my local machine, but heroku doesn't like it and keeps showing me this error:
NoMethodError (undefined method `capitalize' for 66:Fixnum):
app/controllers/mobile_controller.rb:13:in `search'
app/controllers/mobile_controller.rb:13:in `search'
How can I fix this?
Thanks in advance
Solution:
Updated my Heroku Stack to Ruby 1.9.
Your local machine is probably on Ruby 1.9, and your Heroku app is running on 1.8.
In Ruby 1.8, calling String#[] will give you the character code (a number), whereas Ruby 1.9 will give you a string with the first character.
# Ruby 1.8
"test"[0]
# => 116
# Ruby 1.9
"test"[0]
# => "t"
You can use l.name[0..0] to get around this, or switch to a Ruby 1.9 stack on Heroku.
Under Ruby 1.8, String#[] returns the ASCII code of the referenced character rather than the character itself. Try l.name[0,1].capitalize in your controller.

Problem using OpenStruct with ERB

EDIT: forgot to include my environment info... Win7x64, RubyInstaller Ruby v1.9.1-p378
EDIT 2: just updated to v1.9.1, patch 429, and still getting this same error.
Edit 3: running this same code in Ruby v1.8.7, patch 249, works fine. so it's v1.9.1 that broke it, apparently.
I'm new to using ERB and the samples i could find are... ummm... less than helpful... having played around with ERB for about an hour, I got some basic examples working (finally), but I have no idea why this doesn't work...
require 'ostruct'
require 'erb'
data = {:bar => "bar"}
vars = OpenStruct.new(data)
template = "foo "
erb = ERB.new(template)
vars_binding = vars.send(:binding)
puts erb.result(vars_binding)
this code produces the following error:
irb(main):007:0> puts erb.result(vars_binding)
NameError: undefined local variable or method `bar' for main:Object
from (erb):1
from C:/Ruby/v1.9.1/lib/ruby/1.9.1/erb.rb:753:in `eval'
from C:/Ruby/v1.9.1/lib/ruby/1.9.1/erb.rb:753:in `result'
from (irb):7
from C:/Ruby/v1.9.1/bin/irb:12:in `'
why is it looking at the main:Object binding? I told it to use the binding from the OpenStruct by passing in vars_binding
can someone fill me in on why it doesn't work, and help me get it to work?
The problem is where the binding is being executed. The 1.8.7-way obj.send(:binding) does not work anymore (see issue2161), the environment must be the object itself. So use instance_eval:
require 'ostruct'
require 'erb'
namespace = OpenStruct.new(:first => 'Salvador', :last => 'Espriu')
template = 'Name: <%= first %> <%= last %>'
ERB.new(template).result(namespace.instance_eval { binding })
#=> Name: Salvador Espriu
More about this issue in this answer.
Fix to Problem:
I stumbled upon this question when encountering the same type of error with similar code in Ruby 1.9.2.
I'm new to Ruby so I can't explain what is happening. I continued to search online and found this blog post that has an approach that seems to work. After modifying your example to incorporate this approach I end up with the following, working, code:
require 'ostruct'
require 'erb'
class ErbBinding < OpenStruct
def get_binding
return binding()
end
end
data = {:bar => "baz"}
vars = ErbBinding.new(data)
template = "foo <%= bar %>"
erb = ERB.new(template)
vars_binding = vars.send(:get_binding)
puts erb.result(vars_binding)
Additional Information:
When the code is run thru the IRB, I get:
require 'ostruct'
=> true
require 'erb'
=> true
class ErbBinding < OpenStruct
def get_binding
return binding()
end
end
=> nil
data = {:bar => "baz"}
=> {:bar=>"baz"}
vars = ErbBinding.new(data)
=> #<ErbBinding bar="baz">
template = "foo <%= bar %>"
=> "foo <%= bar %>"
erb = ERB.new(template)
=> #<ERB:0x2b73370 #safe_level=nil, #src="#coding:IBM437\n_erbout = ''; _erbout.concat \"foo \"; _erbout.concat(( bar ).to_s); _erbout.force_encoding(__ENCODING__)", #enc=#<Encoding:IBM437>, #filename=nil>
vars_binding = vars.send(:get_binding)
=> #<Binding:0x2b6d418>
puts erb.result(vars_binding)
foo baz
=> nil
What's your environment look like? This code worked for me (I just changed the string "bar" to "baz" to disambiguate in my brain, and added it to the template):
require 'ostruct'
require 'erb'
data = {:bar => "baz"}
vars = OpenStruct.new(data)
template = "foo <%= bar %>"
erb = ERB.new(template)
vars_binding = vars.send(:binding)
puts erb.result(vars_binding)
When I run it, I get:
defeateds-MacBook-Pro:Desktop defeated$ ruby erb.rb
foo baz
Under 1.8.7 on OSX:
defeateds-MacBook-Pro:Desktop defeated$ ruby -v
ruby 1.8.7 (2009-06-08 patchlevel 173) [universal-darwin10.0]
Looks like this does not work with higher ruby versions
with ruby 2.1.1
[19] pry(main)> name = "samtoddler"
=> "Suresh"
[20] pry(main)> template_string = "My name is <%= name %>"
=> "My name is <%= name %>"
[21] pry(main)> template = ERB.new template_string
=> #<ERB:0x007fadf3491c38
#enc=#<Encoding:UTF-8>,
#filename=nil,
#safe_level=nil,
#src="#coding:UTF-8\n_erbout = ''; _erbout.concat \"My name is \"; _erbout.concat(( name ).to_s); _erbout.force_encoding(__ENCODING__)">
[22] pry(main)> puts template.result
NameError: undefined local variable or method `name' for main:Object
from (erb):1:in `<main>'
with ruby 1.9.3
[2] pry(main)> name = "samtoddler"
=> "Suresh"
[3] pry(main)> template_string = "My name is <%= name %>"
=> "My name is <%= name %>"
[4] pry(main)> template = ERB.new template_string
=> #<ERB:0x007f9be2a1fdf8
#enc=#<Encoding:UTF-8>,
#filename=nil,
#safe_level=nil,
#src=
"#coding:UTF-8\n_erbout = ''; _erbout.concat \"My name is \"; _erbout.concat(( name ).to_s); _erbout.force_encoding(__ENCODING__)">
[5] pry(main)> puts template.result
My name is samtoddler
So it gives error but still works in 1.9.3 and all the versions below 1.9.3.

Resources