For the past 10 years, my programming experience has mostly focussed on JavaScript and PHP. I've learned a few functional programming concepts through JavaScript, such as function purity, immutability, and side effects, but I've never worked with a true functional language. To learn more about functional programming, I've decided to learn Elixir and write about what I learn to help me learn.
Recently I created a small API in Elixir/Phoenix and deployed it to Heroku using the free plan. The API read an HTML file stored in a data
folder located in the project root.
In one of my controllers, I had the following:
def index(conn, %{"course" => course, "year" => year}) do
Path.expand("./../../../data/#{year}", __DIR__)
|> Path.join("#{course}.html")
|> log
|> File.read
|> handle_file(conn, course, year)
end
Locally, the path that was logged was /Users/david/Sites/students/data/2019/itp405.html
. Everything worked.
Once I pushed it up to Heroku, I noticed that the file couldn't be read. After inspecting the logs on Heroku (using heroku logs --app davids-api
), I found that the path that was generated from Path.expand
was /tmp/build_b2f02984e1fe996483c2e4e14d6e337d/data/2019/itp405.html
. For whatever reason, that path wasn't working and resulted in a "file not found" error.
Eventually I discovered that the file could be read using the path /app/data/2019/itp405.html
. My final solution turned out to be:
def index(conn, %{"course" => course, "year" => year}) do
get_path(course, year)
|> log
|> File.read
|> handle_file(conn, course, year)
end
defp get_path(course, year) do
if System.get_env("HEROKU_EXEC_URL") do
"/app/data/#{year}/#{course}.html"
else
Path.expand("./../../../data/#{year}", __DIR__)
|> Path.join("#{course}.html")
end
end
It turns out there is an environment variable named HEROKU_EXEC_URL
when running on Heroku. I ended up using that to conditionally determine the file path. This isn't an ideal solution, but it got my app working. If you know of a better way, please let me know in the comments!
UPDATE (2/3/2019)
It turns out, there is a better way to do this, as David made me aware of in the comments section. Instead of putting the data
folder in the root of my project, I moved it into the priv
directory. Then, I used code:priv_dir
from Erlang to get the path to the priv
directory:
defp get_path(course, year) do
"#{:code.priv_dir(:students)}/data/#{year}/"
|> Path.join("#{course}.html")
end
This function takes the application name, which you can find in the project
function in mix.exs
.
No more conditional logic based on a Heroku environment variable!