I have a simple module responsible for communicating with an API. As a convenience to the user, I allow users to pass a "short" url
/my/route or the full url
https://api.mysite.com and handle it with
defmodule MyApp.MyApiHandler do use HTTPoison.base @endpoint = "https://api.mysite.com" def process_url(@endpoint <> _path = url), do: url def process_url(url), do: @endpoint <> url end
This works perfectly! Urls that start with the endpoint are unmodified, and those don't have endpoint added.
The issue I'm encountering is when I try to move this endpoint from a module attribute to a method that reads from config at run time:
defmodule MyApp.MyApiHandler do use HTTPoison.base def process_url(endpoint() <> _path = url), do: url def process_url(url), do: endpoint() <> url defp endpoint do Application.get_env(:MyApp, __MODULE__)[:endpoint] end end
I receive the following error
lib/my_api_handler.ex: cannot invoke local endpoint/0 inside match, called as endpoint()
I'd really prefer to store this config in config (so I can change it per environment). Is there a way to do that while preserving the matching?
The existing SO question for this error doesn't seem to cover my use case (I'm not attempting to assign in the match) but I could also be wrong there.pattern-matchingelixir
You can define
endpoint as a macro.
defmodule Test do defmacro endpoint do Application.get_env(:MyApp, __MODULE__)[:endpoint] end def process_url(endpoint() <> _path = url), do: url def process_url(url), do: endpoint() <> url end
Put it in the
@endpoint Application.get_env(:MyApp, __MODULE__)[:endpoint]
The two answers posted already will fetch the env variable at compile time instead of run time which means if you change the value using
Application.put_env, the function will keep on using the old value. The simplest way I can think of to implement this with a value that can change at run time would be to use
def process_url(url) do endpoint = Application.get_env(...) if String.starts_with?(url, endpoint), do: url, else: endpoint <> url end