Ruby ERB yield inside another template - ruby

Note: I'm not using rails, sinatra, or tilt, just ruby's built in ERB.
Suppose I have two erb files:
file1.erb:
Start
<%= yield if block_given? -%>
End
and file2.erb:
<% render('file1.erb') do %>
Some text
<% end %>
I'd like to get output that looks like:
Start
Some text
End
However, with the following ruby code:
require 'erb'
def render(file)
content = File.read(file)
b = binding
ERB.new(content, trim_mode: '-').result b
end
res = render('file2.erb')
I only get "Some Text". And if I change the first line of file2.erb to use <%= instead of <% then I get a syntax error:
Traceback (most recent call last):
3: from test.rb:9:in `<main>'
2: from test.rb:6:in `render'
1: from /usr/lib/ruby/2.7.0/erb.rb:905:in `result'
/usr/lib/ruby/2.7.0/erb.rb:905:in `eval': (erb):1: syntax error, unexpected ')' (SyntaxError)
...t.<<(( render('file1.erb') do ).to_s); _erbout.<< "\\n Some T...
... ^
(erb):3: syntax error, unexpected `end', expecting ')'
; end ; _erbout.<< "\\n".freeze
^~~
(erb):4: syntax error, unexpected end-of-input, expecting ')'
Is there some way to get this to work with the 'erb' module?
I can get the yielding to work if I use a block that returns an expression directly, but that won't work for my case.
Another answer has a solution where the calling code can render a template inside a different layout. But that doesn't really work for me, because I need to define the layout to use inside the template itself (there isn't really an equivalent of a controller in my code).
I thought of maybe doing something like, changing .result to .run, and running it like:
$stdout = outstream
render('file2.erb')
But for some reason the results in the output:
Start
Some Text
End
Some Text
Note the extra "Some Text" at the end.

As you already poined, since we are not outputing the render method return value with <%= the in first case we are only getting Some text in output the rest is just used in processing.
In second when you used run with template which is causing the result to run twice, first time for template which is called in first script file and then second time for template which is called inside file2.erb render.
To solve the problem you can capture the output in a variable and then print the output from the captured variable as below:
file2.erb
<% #result = render('file1.erb') do -%>
<% "Some text\n" -%>
<% end -%>
<%= #result -%>
Output
Start
Some Text
End
Note:
You can use HEREDOC to put large amount of text between <% -%>
<% #result = render('file1.erb') do -%>
<%
<<~EOF
Some Text
EOF
-%>
<% end -%>
<%= #result -%>

Related

How to render a value from an .rb file into an .erb file

I don't have much experience with Ruby all I wan't to do is render a value that I declare in an .rb file in an .erb file.
In my .rb file I have this:
def abc()
begin
"aaaaa"
end
end
In my .erb file I have this:
Hello <% abc %>
When I run the app I only see:
Hello
But I expect to see:
Hello aaaa
Anybody can give me a hand, I don't really know ruby at all. Also I have no idea if this is ruby or ruby on rails so sorry if the tag below is wrong.
In Sinatra, register your method as a helper in .rb file:
helpers do
def abc
"aaaaa"
end
end
Omit parentheses if your methods don't need arguments. Also, begin/end block isn't necessary here.
You can call your helper in .erb template:
<%= abc %>
Don't forget = in the opening tag.
http://sinatrarb.com/intro.html section 'Helpers'.
It's unclear what you want to achieve. But If you just want some text in your erb you can do something like this:
erb :myerb, locals: {text: "aaaaa", saved: false}
myerb.erb
<% if saved %>
Hello <%= text %>
<% endif %>
This would also work for functions.
First of all, you need to be aware that a defined method inherently includes the functionality of a begin/end block so you donĀ“t need to put them again. Assuming you are using sinatra, here is what I think you need:
my.rb
require 'sinatra'
def abc
"aaaa"
end
get '/' do
erb :my, locals: {variable: abc}
end
my.erb
<html>
<body>
<p>
Hello <%= variable %>
</p>
</body>
</html>
Run ruby my.rb and then open http://localhost:4567/

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.

Slim templates: How to rescue an exception?

I would like to convert the following erb code into slim.
<% begin %>
<%= some_function %>
<% rescue Exception %>
<%= some_other_function %>
<% end%>
My approach is:
- begin
= some_function
- rescue Exception
= some_other_function
But that gives an error:
index.slim:34: syntax error, unexpected keyword_ensure, expecting $end
How do I rescue exceptions properly using slim?
You need to make a Helper.
It's in that helper that you should put begin/rescue logic.
# my_helper.rb
class MyHelper
def my_func
begin
some_function
rescue
some_other_func
end
end
end
# slim view
= my_func
This was actually a bug in slim and is fixed in slim 1.3.7 upwards (https://github.com/slim-template/slim/commit/e4df090c2c82c3563bcc4e625cbd6ab55a60caf8)
The syntax now works exactly as expected. No helper method nor indenting is necessary.

Ruby instance variable in a Puppet template (erb)

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?

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.

Resources