How to access config key in package?

Hi,

I have a question about my project.

In config.exs I have set a value:
config :random_user_api, random_me_url: "http://api.randomuser.me/"

And it’s accessible when I’m using my project, but when I downloaded it from hex this key is nil. Should I attach config.exs file in mix.exs?

For now my package function in mix.exs doesn’t contains info about files at all.

3 Likes

The configs are for settings that the end-users should set. Thus when they import that package from hex then they should set any configs as necessary (or use defaults if appropriate).

1 Like

So should I attach config files before publishing to hex?

1 Like

No, configs will not be used at all when your project is a dependency. You should document what configs the user should set in their own config files and what the defaults are if they do not.

1 Like

How to set config of the project, which uses this dependency then? I used in my code:
Application.get_env :my_app_name, :my_key

1 Like

Mix configs are for end projects. The users of libraries can configure any application in their config/config.exs. You can provide defaults in the application part of the library’s mix.exs with the env keyword.

You can see an example in ecto: https://github.com/elixir-ecto/ecto/blob/3263ff87fc147aa0299ea5bdc1e02cfc6b2efa84/mix.exs#L37

def application do
  [applications: [:logger, :decimal, :poolboy],
   env: [json_library: Poison], mod: {Ecto.Application, []}]
end

Such config is accessible with Application.get_env(:ecto, :json_library)

7 Likes

Somehow it doesn’t work…

My config.exs snippet:

  use Mix.Config
  import_config "#{Mix.env}.exs"
  config :random_user_api, random_me_url: "http://api.randomuser.me/"`

For each Mix.env I add also one config- for testing purposes is to get TestAPI instead of real one.

Then I use it in my engine.ex:

  @random_me_api Application.get_env :random_user_api, :random_me_api
  @random_me_url Application.get_env :random_user_api, :random_me_url

Also I tried before your comment with this “env” stuff in application function, but thanks for the comment :slight_smile:

  def application do
    [applications: [:logger, :httpoison, :jsx],
     env: [random_me_api: RandomUserApi.API, 
           random_me_url: "http://api.randomuser.me/"]]
  end

And still when I download my library using Hex I got, when trying to compile it:

== Compilation error on file lib/RandomUserApi/engine.ex ==
** (CompileError) lib/RandomUserApi/engine.ex:13: invalid literal nil in <<>>
    (elixir) src/elixir_bitstring.erl:149: :elixir_bitstring.build_bitstr/4
    (stdlib) lists.erl:1353: :lists.mapfoldl/3
    (stdlib) lists.erl:1353: :lists.mapfoldl/3

So it treats my module attribute as a nil…

1 Like

Do you have a complete Short, Self Contained, Compilable, Example that we can try? :slight_smile:

1 Like

Yes, the configuration in mix.exs is the runtime configuration of your application. You’re trying to access the configuration at compile-time with module attributes.
Mix configuration works slightly differently, as it’s accessible at all time.

I’d say, though, that configurations such as this one, should be read at runtime with Application.get_env/3 each time, and not bound at compile-time. Binding the configuration value at compile-time partially defeats the purpose of having such a configuration. Your users would need to recompile the dependency when they change the configuration value, as dependencies are not recompiled when config changes (imagine compiling every dependency every time you change something in config).

Since you’re looking to provide the default’s - may I suggest using Application.get_env(app_name, key, default_value)? No need to fiddle with the environment at all in that case.

5 Likes

Ok,

Thanks to @michalmuskala I handled this issue, but another one occured. When using library as a standalone HTTPoison works fine, but as a library error popped out:

{:error,
 %HTTPoison.Error{id: nil,
  reason: {:noproc,
   {:gen_server, :call,
    [:hackney_manager,
     {:new_request, #PID<0.229.0>, #Reference<0.0.1.1506>,
      {:client, :undefined, {:metrics_ng, :metrics_dummy},
       :hackney_tcp_transport, 'www.wp.pl', 80, "www.wp.pl", [], nil, nil, nil,
       true, :hackney_pool, 5000, false, 5, false, 5, nil, nil, nil, :undefined, 
       :start, nil, :normal, false, false, false, :undefined, false, nil,
       :waiting, nil, 4096, "", [], :undefined, nil, nil, nil, ...}},
     :infinity]}}}}
1 Like

Did you add httpoison to your application list in your mix.exs file?

1 Like

Yes, check post nr 7. I listed it in application.

1 Like

Unsure then, I use HTTPoison without issue. Do you have a single/few line/lines that I can copy-paste into my shell to test it that fails for you?

1 Like

This error indicates that hackney / httpoison is not started. Are you inside a custom mix task, or running mix run --no-start?

2 Likes

No, I tested it with new app, where I put my library as a dependency (mix new …, mix deps.get etc.).
You can check my repo.

1 Like

Anyone has any ideas how to solve it?

Edit: I solved it. Ahhhh. I didn’t add my library into applications. It works just as fine :slight_smile:

Lol, yeah always have to remember to add all of your prod dependencies into your applications, even if they have no application code, or else you will break releases too. :wink:

Following up on this is there any standardish way to provide for compile time configuration defaults for a Mix project used as a dependency in an Application?

More to the point I have some regex which i apply on data, which should be changeable but not necessarily at runtime.

Still there are lots of Regexes, so i wouldn’t want to force the user of the dependency to include all of them his config, as he might be perfectly happy with 99% of the defaults.

The standard config is good for that, just in your app test if it exists and if so use it, else use a default. :slight_smile:

Ok, so just documenting then that the dep user should include deps/my_lib/config/config.exs.

Hope this will not be surprising for lib users :slight_smile:
EDIT:
I don’t think I can add deps/my_lib/config/config.exs to config/config.exs, because the latter is loaded for mix deps.get before the package is available.

EDIT2:
@OvermindDL1: You where right to point out that i need to check the the dep my_lib is present, as the config is read in multiple passes. This does the trick:

config_file =  Mix.Project.deps_paths()[:my_lib] <> "/config/config.exs"
if File.exists? config_file do
  import_config config_file
end` 

Still seems a bit complex for inclusion in a config.