Batch connect erb template `eval` strange behavior

I’m trying not to repeat myself in all my different batch connect apps. So I created /var/www/ood/apps/common/attributes.yml and I import that file into all the form.yml:

attributes:
<%= File.read('/var/www/ood/apps/common/attributes.yml') %>

This works well.

I have logic in submit.yml that checks if a field is blank and changes that field to a default value. So I created /var/www/ood/apps/common/form_default_values.rb:

def default(x, default_value)
    return x.blank? ? default_value : x
end

partition = default(_partition, "cpu-preempt")
cpus_per_task = default(cpus_per_task, 2)
time_limit = default(time_limit, "1:00:00")
mem = "#{default(mem_gigs, 8)}GB"
gpus = default(gpus, 0)

What I thought should work but didn’t was this:

---
batch_connect:
<%
eval(File.read('/var/www/ood/apps/common/form_default_values.rb'))
%>
script:
  native:
  - --partition=<%=partition%>
  - --cpus-per-task=<%=cpus_per_task%>
  - --time=<%=time_limit%>
  - --mem=<%=mem%>
  - --gpus=<%=gpus%>

All those variables which are defined in form_default_values.rb come out as empty strings. I assume that they’re undefined and that <%=undefined%> just comes out as nothing.

What does work for some reason is this:

---
batch_connect:
<%
eval(File.read('/var/www/ood/apps/common/form_default_values.rb'))
partition=partition
cpus_per_task=cpus_per_task
time_limit=time_limit
mem=mem
gpus=gpus
%>

script:
  native:
  - --partition=<%=partition%>
  - --cpus-per-task=<%=cpus_per_task%>
  - --time=<%=time_limit%>
  - --mem=<%=mem%>
  - --gpus=<%=gpus%>

By adding the pointless instruction foobar=foobar, I can now reference foobar a few lines later. Am I crazy? Can anyone else reproduce this?

I also tried adding binding as a second positional argument to eval, same result.

The use of eval here isn’t providing the context of the file, just the result of running it - eval isn’t supposed to be used the way you’re using it (and probably shouldn’t be used at all in production code - it’s a security risk). eval is out of scope here, so ERB can’t see those variables without the references you made.

You could use a module or .yml.erb file to define these values - either option would probably work. Is there a reason you chose to use eval? That seems like it’s over-complicating matters a bit when what you’re doing is more or less the same thing as pulling in a hash of values.

I’m pretty sure this solution does what you’re looking for:

# /var/www/ood/apps/common/defaults.rb
module CommonDefaults
  def self.default_values(input_values)
    {
      partition: input_values.fetch(:partition, "cpu-preempt"),
      cpus_per_task: input_values.fetch(:cpus_per_task, 2),
      # ... other defaults ...
    }
  end
end
# file.yml
<%
  require '/var/www/ood/apps/common/defaults'
  values = CommonDefaults.default_values(local_values)
%>
script:
  native:
  - --partition=<%= values[:partition] %>
  - --cpus-per-task=<%= values[:cpus_per_task] %>
  - ...

Edit - You can also add initializers to Rails to load the file for you so the require isn’t needed.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.