Where to put boilerplate code used in templates

Hi,

Over time we are accumulating a lot of boilerplate code across the assorted apps we have, where is the best place to put these? I think I want to create a gem and require it somewhere, but don’t really know ruby well enough to know where to put that gem nor where in ondemand to require it. Is there a doc or guide for this I am missing when I search? Note that I’m not a ruby or web developer, I just what to know the bare minimum to take snippet that I have to keep in sync across all apps and put them in one place.

Best,

griznog

The bare minimum that can be done at this point would be to do something similar to https://listsprd.osu.edu/pipermail/ood-users/2018-November/000297.html and reproduced here:

In /etc/ood/config/apps/dashboard/initializers/qlist.rb

module Purdue
  class Qlist
    def self.qlist(cluster)
      @qlist ||= Hash.new do |h, key|
        Rails.logger.warn "executing expensive qlist"
        h[key] = build_qlist(key)
      end

      @qlist[cluster]
    end

    def self.build_qlist(cluster)
      ["#{cluster}-debug", "#{cluster}-serial", "#{cluster}-parallel" ]
    end
  end
end

# call to build cache on initialize (optional)
Purdue::Qlist.qlist('owens')
Purdue::Qlist.qlist('ruby')
Purdue::Qlist.qlist('pitzer')
Purdue::Qlist.qlist('oakley')

In any interactive app web form where ERB is rendered by the Dashboard app you can then do for example:

attributes:
  queue:
    widget: select
    label: "Queue"
    options:
    <% Purdue::Qlist.qlist('owens').each do |queue| %>
      - [ "<%= queue %>",    "<%= queue %>"   ]
    <% end %>

If you are not memo-izing values or have an inexpensive method you could use it without class methods, for example in /etc/ood/config/apps/dashboard/initializers/custom_code.rb:

module OhioSupercomputerCenter
  class Helper
    def num_cores
      {
        widget: "number_field",
        label: "Number of cores",
        value: 1,
        help: "Number of cores on node type (4 GB per core unless requesting whole node). Leave blank if requesting full node.",
        min: 1,
        max: 28,
        step: 1,     
      }.to_json
    end
  end
end

And then in an erb file:

attributes:
  num_cores: <%= OhioSupercomputerCenter::Helper.new.num_cores %>
  jupyterlab_switch:
    widget: "check_box"

For that it might be better to have the helper return a hash and convert it to json string in the erb:

module OhioSupercomputerCenter
  class Helper
    def num_cores
      {
        widget: "number_field",
        label: "Number of cores",
        value: 1,
        help: "Number of cores on node type (4 GB per core unless requesting whole node). Leave blank if requesting full node.",
        min: 1,
        max: 28,
        step: 1,     
      }
    end
  end
end
attributes:
  num_cores: <%= OhioSupercomputerCenter::Helper.new.num_cores.to_json %>
  jupyterlab_switch:
    widget: "check_box"

Then you could replace one or more values of the hash to customize for a particular app like in the case where one app you want the user to be able to configure up to 40 cores:

attributes:
  num_cores: <%= OhioSupercomputerCenter::Helper.new.num_cores.merge("max" => 40).to_json %>
  jupyterlab_switch:
    widget: "check_box"

So there are some ideas there. The important thing is to wrap whatever code you add in an initializer in a module that is a unique name, such as your institution, so that it never conflicts with any code developed upstream in the future.

Is there a doc or guide for this I am missing when I search?

Not yet unfortunately. I opened an issue to add it: https://github.com/OSC/ood-documentation/issues/250. If there are examples of specific things you would like to see documented, like how to use a trick like this to remove some type of duplication, let me know.

1 Like

Thanks @efranz this will work perfectly for us, I think.

griznog