sodapopcan

sodapopcan

Compile time checking struct keys for a protocol

I have a protocol that is used on Ecto Schemas. The protocol requires that two fields are defined with specific defaults.

I have come up with a working solution that I’m pretty happy with, but I’m just looking for feedback as I’m unsure if this is the optimal way (and maybe I’ve looked past a much simpler solution). In short, I used __after_compile__ and Ecto’s reflection functions to ensure the keys are there.

Obviously one drawback of this is that it must be @derived though I’m not so worried about that.

I’m also wondering about the line %{context_modules: [module]} <- env and if that could have any ramifications. Could there ever be more than one context module in this scenario?

Here is the code:

defmodule MyApp.MyProtocolError do
  defexception [:message]
end

defmodule MyApp.MyProtocolCompile do
  @required_fields [
    name: %{type: :string},
    checked: %{type: :boolean, default: true}
  ]

  def __after_compile__(env, _bytecode) do
    with %{context_modules: [module]} <- env do
      messages =
        for {field, opts} <- required_fields, reduce: [] do
          messages ->
            if module.__schema__(:virtual_type, field) != opts.type do
              message =
                " * #{__MODULE__} must have a field `:#{field}` of type `:#{opts.type}`"

              message =
                message <>
                  ((Map.has_key?(opts, :default) && " and default to `#{opts.default}`") || "")

              [message | messages]
            else
              messages
            end
        end

      if messages != [] do
        raise MyApp.MyProtocolError,
          message: "\n" <> Enum.join(messages, "\n")
      end
    end
  end
end

defprotocol MyApp.MyProtocol do
  def do_it(module)

  @impl true
  defmacro __deriving__(module, options) do
    quote location: :keep do
      @after_compile MyApp.MyProtocolCompile

      defimpl MyApp.MyProtocol, for: unquote(module) do
        def do_it(_module) do
          unquote(options).key
        end
      end
    end
  end
end

The other thing I did was put the __after_compile__ in the __deriving__ so I could just unquote(module) to get the module, but I like this way a bit better.

Where Next?

Popular in Questions Top

Tee
can someone please explain to me how Enum.reduce works with maps
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a &gt; b) do {:ok, "a"} end if (a &lt; b) do {:ok, b} end if (a == b) do {:ok, "eq...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
PeterCarter
There are pre-rolled solutions for other frameworks that do work. However, Phoenix does not seem to have these. Have people had good expe...
New

Other popular topics Top

marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 42842 311
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
vegabook
I'm brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New

We're in Beta

About us Mission Statement