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.
Related
I'm new, was tasked to do a front end job but I thought i can figure out ERB easily.
In my app.rb
variables defined
def find_listings
3.times.map { random_item }
end
def random_item
{
name: 'blah',
location: 'LA'
}
end
get '/' do
#listings = find_listings
erb :index
end
In my ERB file I am trying to iterate
index.erb
<% #listings.each do |value | %>
<strong><%= value %><br/>
<%# This returns {:name=>"blah", :location=>"New York"} %>
<strong><%= value['name']%></strong><br/>
<%# This returns nothing %>
<% end %>
How do i get the name value?
Your hash has symbols as keys (not strings), so you can access name this way: value[:name].
There's something weird going on here that I can not understand when I try to implement multiple content_for blocks in ERB using plain Ruby.
Here is my code:
# helper.rb
require 'erb'
def render(path)
ERB.new(File.read(path)).result(binding)
end
def content_for(key, &block)
content_blocks[key.to_sym] = block
end
def yield_content(key)
content_blocks[key.to_sym].call
end
def content_blocks
#content_blocks ||= Hash.new
end
And template:
<% #test.html.erb %>
<% content_for :style do %>
style
<% end %>
<% content_for :body do %>
body
<% end %>
<% content_for :script do %>
script
<% end %>
When I open irb to test, I get
irb(main):001:0> require './helper'
=> true
irb(main):002:0> render 'test.html.erb'
=> "\n\n\n"
irb(main):003:0> content_blocks
=> {:style=>#<Proc:0x005645a6de2b18#(erb):2>, :body=>#<Proc:0x005645a6de2aa0#(erb):5>, :script=>#<Proc:0x005645a6de2a28#(erb):8>}
irb(main):004:0> yield_content :script
=> "\n\n\n\n script\n"
irb(main):005:0> yield_content :style
=> "\n\n\n\n script\n\n style\n"
Why yield_content :script got the \n\n\n prepended and why yield_content :style got the script\n\n in result.
If you do
ERB.new(File.read(path)).src
Then you can see what erb compiles your template to. The template ends up looking like (formatted for readability)
_erbout = ''
_erbout.concat "\n"
#test.html.erb
_erbout.concat "\n"
content_for :style do
_erbout.concat "\n style\n"
end
Where _erbout is the buffer that accumulates the output from your template. _erbout is just a local variable,m
When you call your procs they are all just appending to that same buffer, which already contains the result from the template render.
I am having trouble trying to get information from a default attributes file into a template using Chef. Currently, I have this:
# attributes/default.rb
default['environment']['extrahosts'] = [ 'hostname1:address1', 'hostname2:address2' ]
#recipes/default.rb
extra_hosts = node[:environment][:extrahosts]
...
...
template '/blahblah' do
source 'blahblah.erb'
variables( :extra_hosts => extra_hosts )
end
#templates/blahblah.erb
<% for #item in #extra_hosts %>
- <%= #item %>
<% end %>
Although this doesn't work. What do I add to my template to yield:
- hostname1:address1
- hostname2:address2
The way you write a loop in Ruby is to use the each method and a block.
<% #extra_hosts.each do |item| %>
- <%= item %>
<% end %>
Also note that the loop variable doesn't have the at sign because it isn't an instance variable.
I'm trying to write some puppet .erb, I'd like to handle this "environment" variable if it's:
undefined
a string with newlines
an array.
I've got as far as this:
<% Array(environment).join("\n").split(%r{\n}).each do |f| %>
one line: <%= f %>
<% end %>
But haven't gotten around the undefined case yet. I've tried this
<% if (defined?(environment)).nil? %?
<% Array(environment).join("\n").split(%r{\n}).each do |f| %>
one line: <%= f %>
<% end %>
<% end %>
but am still getting "(erb):11: undefined local variable or method `environment' for main:Object (NameError)" when trying to test it like this:
ruby -rerb -e "environmentUNDEFINEME= [ 'cronvar=cronval', 'var2=val2' ];
puts ERB.new(File.read('templates/job.erb')).result"
Sorry this is so basic, but somebody's got to ask the easy questions. Any help?
I would do this:
<% if defined?(environment) %>
<% Array(environment).each do |f| %>
one line: <%= f %>
<% end %>
<% end %>
I didn't understand why you joining on new lines and then splitting on them again, so I removed it from the example.
I have a form builder and the normal |f| variable. In the middle I'd like to iterate over some hash and get a list of keys by which to build the form. How can I incorporate "f" as an argument to the enumerator.
<%= form_for ..... do |f| %>
<% available_types.each do |k,v| %>
<%= f.text_filed :selection, :value => v %>
<% end %>
<% end %>
As you can see in the code above, "f" is outside of the scope. Ideas?
f should still be in the scope. You misspelled text_field though.