Ruby instance variable in a Puppet template (erb) - ruby

I do not understand the behaviour of this Ruby code I have written inside a Puppet template (erb). It must be related to some property of instance variables that I ignore and have not been able to find out.
At the Puppet side a variable $local_users can be declared (and initialized) or not in the file site.pp. In the template code shown below, the if sentence checks if it has been previously initialized.
<% if #local_users -%>
<%= fail('local_users has to be an array') unless local_users.class == Array -%>
<% else -%>
<% local_users = [ "root" ] -%>
<%# some code to add more users to the array -%>
<% end -%>
<% local_users.uniq.each do |user| -%>
<%= user %>
<% end -%>
If $local_users is not declared in the site.pp file (else branch), this code works flawlessly. If it is declared (if branch), then it fails when trying to apply the Puppet manifest:
Failed to parse template sendmail/local-users.erb: undefined method `uniq' for nil:NilClass at /usr/share/puppet/modules/sendmail/manifests/config.pp:39
(line 39 is where the template is invoked: content => template('sendmail/sendmail.mc.erb'),)
The problem is that in the loop the local_users variable is not recognized as an Array. I solved the problem just declaring a local variable at the if branch:
<% if #local_users -%>
<%= fail('local_users has to be an array') unless local_users.class == Array -%>
<% local_users = #local_users -%>
<% else -%>
But inside the if branch it is an Array, since local.users.class == Array returns true (at this point I can use the method local_users.uniq too without problems). Furthermore, I have tried with if local_users (without #) and inside the if branch local_users is still an Array, but in the loop keeps failing.
Here are my questions:
How can this behaviour be explained? Why is the local_users variable an Array inside the if branch and not at the loop?
Is my workaround correct or are there better ways to do this?

Since in Ruby nil is an object with its own class (NilClass) and since (try this with irb):
nil.respond_to? :class
# => true
the statement local_users.class == Array return false.
local_user is a variable local to the view, while #local_user is an instance variable of the controller behind that view.
There are two different things, that's why you have to assign local_user to #local_user in order to not have a nil when ou invoke local_users.uniq.
Btw, why are you working with local_users instead of simply use #local_users and moving all the logic on the controller?

Related

Puppet ruby template with ip validation - skip failed values not error and stop

I have the following code in a Puppet (ruby) .erb template with a validate function on each value that is iterated over from an array. I want it to continue with next values and preferably issue a warning, or silently continue. The code below errors and stops if an entry fails validation.
For example if the array is $nameservers = ['1.1.1.1','/some.domain/2.2.2.2'] I want it to only process the 1.1.1.1 entry.
This is actually so I can use the same input array for either /etc/resolv.conf or /etc/dnsmask.conf as determined by other logic in my manifest that gets processed by one of 2 templates (no, I'm not crazy) :).
I know that validation can be done in the manifest or on top scope variables, however I have a need to do this in the template.
search <%= scope.lookupvar('dns_search') %>
<% scope.lookupvar('nameservers').each do |server| -%>
<% if scope.function_validate_ip_address([server]) -%>
nameserver <%= server -%>
<% end -%>
<% end -%>
Cheers
I managed to get it working with the following code. The only thing I can't get to add is a trailing newline after the LAST entry only. Any comments on how this block could be improved appreciated.
search <%= scope.lookupvar('dns_search') %>
<% scope.lookupvar('nameservers').each do |server| -%>
<% begin -%>
<% if scope.function_validate_ip_address([server]) %>
nameserver <%= server -%>
<% end -%>
<% rescue => e -%>
<% scope.call_function('warning',[e]) -%>
<% next %>
<% end -%>
<% end -%>

undefined method 'image' for nil:class

Im trying to display all the images from active storage on an index page. Note: The image will display in the show page
Heres what i have on the index page:
<% #imagelists.each do |il| %>
<%= image_tag(#il.image) &>
<% end %>
I can do the following which shows the active storage data
<% #imagelists.each do |il| %>
<%= #il.image &>
<% end %>
When you do this:
<% #imagelists.each do |il| %>
You are defining a variable il which is available within that each block.
However, in your code you are referencing #il - an instance variable which doesn't exist. Unlike a normal variable, when you reference an instance variable which isn't defined, you just get nil, not a NoMethodError.
So, just use il instead of #il and you'll be fine.

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.

How do I perform inline calculations on two variables within an .erb file?

I have the following .erb view in a Sinatra app:
<% sessions.each do |session| %>
<%= session.balance_beginning %>
<%= session.balance_ending %>
<% end %>
It works as expected, displaying the beginning and ending balances recorded for each session. I would like to calculate the net balances from within the .erb file, but I can't figure out how to do it. I have tried variations of this:
<% sessions.each do |session| %>
<%= session.balance_ending - session.balance_beginning %>
<% end %>
That doesn't work. I receive the following error in Sinatra:
undefined method `-' for nil:NilClass
How do I do what I'm trying to do?
Right #Zabba, in this case I think you would add a method to your Session model so you could call session.net_balance.
Then in your balance_ending and balance_beginning methods you would want to handle nil, either raise an error or return zero if that is valid.

Sinatra query collection

I'm new to Sinatra and I'm trying to figure out how querying a collection in templates work. In this particular example I'm trying to find out if in a specific collection (c in this example) of objects if there is an object with a certain value.
<% if c.votes #then filter by an id for example through all of the objects... %>
yes, it exists
<% else %>
nope, doesn't exist
<% end %>
Also, I'm used to django's filters, is there a comparable documentation online that outlines the various query functions for Sinatra?
Is it just a standard collection? You could use any?, which returns true if the provided block ever finds a match. You would then test each object for the value you are looking for in that block.
<% if c.votes.any? { |a| a.id == whatever } %>
...
<% else %>
...
<% end %>
It really depends on what "votes" is.
In rails you would use <% if c.votes.present? %> which is helpful because otherwise if c.votes is an empty array the condition would evaluate to true.
In Sinatra you don't have .present?, but you have a couple options: <% unless c.vote.empty? %> or <% if !c.votes.empty %>. I don't like the readability of either option, so I would recreate add the present? method to Array:
class Array
def present?
!empty?
end
end
Where you add this depends on how you have your Sinatra app setup. One option would ti added it directly to your main app file.

Resources