Last active
August 23, 2018 13:47
-
-
Save minhajuddin/95dd97de406451f1433e45dcf5da2519 to your computer and use it in GitHub Desktop.
Compiled configuration
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This is the target module which will be overwritten after dynamic compilation | |
# You'll be using this to read configuration in your code. For instance, if you | |
# have a configuration key called `:redis_timeout`, you could read it using | |
# `MM.Config.get(:redis_timeout)` | |
defmodule MM.Config do | |
# we use a default implementation which raises an error when our code tries | |
# to read configuration before it is compiled. | |
def get(_key), do: raise("Config has not been compiled yet!") | |
end | |
defmodule MM.ConfigCompiler do | |
@moduledoc """ | |
Compiles essential settings into a dynamic module | |
This allows us to read these settings very fast. | |
The sole purpose of this module is to take a dynamic module and a keyword list | |
and compile it so that the dynamic module returns values of the keyword list | |
""" | |
@doc """ | |
Compiles a new module embedding the keyword list as functions. | |
# Example | |
iex> MM.ConfigCompiler.compile(MM.Config, [redis_timeout: 1000, host: "192.168.1.1"]) | |
iex> MM.Config.get(:redis_timeout) | |
1000 | |
iex> MM.Config.get(:non_existent_key) | |
** (RuntimeError) CONFIG_NOT_FOUND: non_existent_key | |
""" | |
def compile(module_name, config) do | |
# the compile module creates a new module using quote in conjunction with | |
# Code.eval_quoted | |
# we first create a quote containing the module definition | |
# We need `Macro.escape` to escape complex elixir types like maps when used inside quote | |
# We also use `location: :keep` to show us the file where this is being done when an error is raised | |
quote bind_quoted: [config: Macro.escape(config), module_name: module_name], | |
location: :keep do | |
# define our module | |
defmodule module_name do | |
# for each key value pair in the input keyword list | |
for {k, v} <- config do | |
# define a function head matching the literal key and return the literal value | |
# e.g. def get(:redis_timeout), do: 1000 | |
def get(unquote(k)), do: unquote(v) | |
end | |
# if the input key doesn't match any of the previous function heads, it falls down | |
# to this default callback where we raise an exception | |
def get(any), do: raise("CONFIG_NOT_FOUND: #{inspect(any)}") | |
end | |
end | |
# We have the whole quoted module at this point and we just push it into | |
# Code.eval_quoted to compile it. | |
|> Code.eval_quoted([], __ENV__) | |
end | |
end | |
# This is a helper module which encapsulates the reading and compiling of configuration | |
# so that it can be called without a lot of ceremony | |
defmodule MM.ConfigHelper do | |
def recompile do | |
config = Application.get_all_env(:my_app) | |
# => Returns something like below | |
# [ | |
# {:idle_timeout_ms, 120000}, | |
# {:statsd_tags, ["host:evtp", "app_version:0.1.0", "env:dev"]}, | |
# {:namespace, MM}, | |
# # ... | |
# ] | |
# Overwrite the current module with a new definition containing function | |
# heads for the config values. | |
MM.Config.compile(MM.Config, config) | |
end | |
end | |
# in your application.ex and whenever you want to update env/recompile | |
MM.ConfigHelper.recompile |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment