Seems I’ve been spending too much time using things that live reload code, the files DO recompile if I change the JSON, I was just waiting for it to happen live ![]()
I’m sure there are things I can tweak in the below, but it seems to work fine for what I need right now.
- Split over multiple JSON files
- Custom Ecto field for converting when entering/retrieving from DB
- Allow atoms to be used during dev/debug, but integers for storage/comms
- Re-compiles when any JSON file changes
- Dynamically create “nested modules” based on the JSON filename
So this is end result of all the suggestions (thanks again!)
defmodule API.Const do
@moduledoc """
A single point that we can use constants from. Rather than using something
like a map to expose atoms => integers we create a set of functions that
let us encode/decode from either atom => integer or integer => atom. Doing this
makes it a lot easier to debug things as we are basically just using atoms
everywhere in the code - we change to integers on the app boundary.
We still break up the constants with regards to their respective areas, and
then we just include everything in here. We store the constatnts as JSON files
so that we can export into frontend clients if needed,
Discussion here: https://forum.elixirforum.com/t/shared-module-constants/2799/
Eg:
API.Const.System.encode(:ORG_STATUS_ACTIVE) => 100101
API.Const.System.decode(100101) => :ORG_STATUS_ACTIVE
"""
@root_dir File.cwd!
@const_dir Path.join(~w(#{@root_dir} lib imsapi common constants))
@const_files Path.wildcard("#{@const_dir}/**/*.json")
for filename <- @const_files do
# Watch for changes and recompile
@external_resource filename
# This will be the new module: API.Const.<group>
group = filename
|> Path.basename(".json")
|> String.capitalize
file_consts = File.read!(filename)
|> Poison.decode!
# Creates a module under the main API.Const module using the filename.
#
# Eg a system.json file would end up as `API.Const.System`
defmodule Module.concat([API, Const, group]) do
for {key, value} <- file_consts do
def encode(unquote(String.to_atom(key))), do: unquote(value)
def decode(unquote(value)), do: unquote(String.to_atom(key))
end
# Creates a new module that can be used with Ecto models to hsve s field
# that is automatically encoded/decoded as data is retrieved/entered.
# Name is the same as the outer module, with `Ecto` appended
#
# Eg a system.json file would end up as `API.Const.System.EctoField`
defmodule Module.concat([API, Const, group, EctoField]) do
@behaviour Ecto.Type
@group_mod Module.concat([API, Const, group])
def type, do: :integer
def cast(int) when is_integer(int), do: {:ok, apply(@group_mod, :decode, [int])}
def cast(atom) when is_atom(atom), do: {:ok, atom}
def cast(_), do: :error
def load(int) when is_integer(int), do: {:ok, apply(@group_mod, :decode, [int])}
def load(_), do: :error
def dump(atom) when is_atom(atom), do: {:ok, apply(@group_mod, :encode, [atom])}
def dump(_), do: :error
end
end
end
end


















