Store a template string in a config file and use it dynamically

I want to store a template string in a config file:


config :my_app, :general,
  my_path123_template: "/a/b/c/#{var1}/d"

and then be able to use in my app by providing value to it at runtime – dynamically.

current_var1 = params["current_var1"]
path1 = Application.get_env(:my_app, :general)[:my_path123_template] .... (current_var1) # ????

This code won’t compile because “var1” is undefined.

How to make that work? Or what’s a wise, non-hacking way to do it?

"#{}" is not a template, it’s string interpolation, it happens in place.

If you need a template, you should use one.

You could use eex, which is part of the elixir standard distribution.

4 Likes

I want whatever will work and is used in such cases. How do I use a template for this case?

You need a template. Only a template can be later supplied with the pieces to be filled in.

1 Like

FWIW, I don’t think it is too crazy to call it a “template” considering the same concept is called “template string” or “string template” in other languages.

Anyway, would using a function solve your problem? Using EEx seems like overkill if you just need to interpolate a string.

config :my_app, :general,
  my_path123_template: fn var1 -> "/a/b/c/#{var1}/d" end
current_var1 = params["current_var1"]
my_template = Application.get_env(:my_app, :general)[:my_path123_template]

my_template.(current_var1)
1 Like

A template is usually a data structure, which can later be filled with data, so it‘s some kind of function or specialized string, with defined placeholders.

String interpolation happens on the spot and is basically just a different syntax to e.g. "abc" <> to_string(foo).

Using a callback in config might actually not work. Afaik only fully qualified calls like &MyApp.func/2 do actually work in config.

2 Likes

Any downsides to your solution?

The biggest downside is, that you can’t persist functions in a config.

1 Like

String interpolation happens on the spot and is basically just a different syntax to e.g. "abc" <> to_string(foo) .

I am not really sure what you mean by “happens on the spot” here.

A template is usually a data structure, which can later be filled with data, so it‘s some kind of function or specialized string, with defined placeholders.

This is pretty much describes string interpolation here, so I am not sure what point you are making.

Using a callback in config might actually not work. Afaik only fully qualified calls like &MyApp.func/2 do actually work in config.

It works, I don’t know why it wouldn’t.

~/Development/config_test
$ iex -S mix
Erlang/OTP 21 [erts-10.0.7] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Compiling 1 file (.ex)
Generated config_test app
Interactive Elixir (1.8.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> ConfigTest.run("hello world")
"this is interpolated: hello world"
"this is interpolated: hello world"
iex(2)> ConfigTest.run(234234)
"this is interpolated: 234234"
"this is interpolated: 234234"

I agree that using the word “template” for string interpolation is fairly common, at least in other languages, so nothing super unusual about it. As an example, they’re called template literals in JS https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals

The concern I’d have about the anonymous functions is whether they work in releases, but that’s just based on some vague memory about something I overheard somewhere.

1 Like

The concern I’d have about the anonymous functions is whether they work in releases, but that’s just based on some vague memory about something I overheard somewhere.

Sure, I was mostly chose an anonymous function as a demonstration on how to defer the execution of the string until runtime.

1 Like

Using an anonymous function is probably not maintainable, but I’m not sure what you are trying to do.

I would use a module/function here, similar to what LostKobrakai said in his reply to me: &MyApp.func/2

Confirmed that functions do not work in a release

~/Development/config_test (master●)
$ mix release
Compiling 1 file (.ex)
Generated config_test app
* assembling config_test-0.1.0 on MIX_ENV=dev
* skipping runtime configuration (config/releases.exs not found)
** (Mix) Could not read configuration file. It likely has invalid configuration terms such as functions, references, and pids. 
Please make sure your configuration is made of numbers, atoms, strings, maps, tuples and lists. Reason: {3, :erl_parse, ['syntax error before: ', 'Fun']}
2 Likes

What do you mean?

Exactly what I said, you can not persist an anonymous function in a config.

It might work when started and tested through mix as others have said, but also as @mhanberg has shown in the previous post, it won’t work for a release.

1 Like

It seems as though functions in config are a somewhat common use case that we’re missing. Is there a strong technical reason to exclude functions from config that anyone is aware of?

I have also wanted to use short anonymous functions in config before.

Yes, the BEAM itself can’t cope with it.

Releases work by writing out a config file, which is loaded on boot through :file.consult/1 or its boot time equivalent.

Such a file is unable to store functions.

3 Likes

Simplest solution to you problem would be using format string, so in your config you have:

config :my_app, :template, "/a/b/c/~s/d"

And then in your application:

path = Application.get_env(:my_app, :template) |> :io_lib.format([current_var1]) |> List.to_string()
2 Likes

Cool, thanks for the explanation!