Usually when I write a program in another language like Python, there’s a clear-cut way to start something without having to manually execute a function from the command line (mix run -e Module.function()). That’s because, from the way I see it, there’s ‘main’ entry-point function from which the program launches, and other functions get run from there.
I can’t find any article that simply points to Phoenix’s main entry-point function from which I can simply execute a simple function (My.Stuff.populate_database()) without having to write some Agent/GenServer module and Supervising children? I get article’s like “What’s the main use of Phoenix”, but nothing that points to an entry-point of the program from which I can just run a function.
I need help. Where is Elixir/Phoenix’s ‘main’ function or its equivalent which which I can run a simple function? Thank you.
I am experimenting with Ecto and I have a module called Custom.DB that creates an in-memory database.
I have included the module as one of the children in application.ex.
However, before I can use it, I have to first create the tables, and populate the database. I’ve created a function for doing so, but I don’t know how to get it to run when the program starts with iex -S mix phx.server.
defmodule Custom.DB do
use Ecto.Repo,
otp_app: :testing,
adapter: Ecto.Adapters.SQLite3
def create_tables do
:testing
|> :code.priv_dir()
|> Path.join("repo/testing.sql")
|> File.read!()
|> query()
end
def show_tables do
query("select * from sqlite_schema;")
end
end
There’s no single entrypoint on the beam, which you can customize. That’s not how the abstractions are setup. The beam when starting will start a set of application. Each application can optionally provide a callback module, which (among others) will have the beam execute the start/2 function of that application. That’s where you put code to execute on startup.
You’re already have that callback module and function, because that’s where the children list is defined, which you posted.
Mhmm… thank you for trying, but I still don’t get it. Maybe my question wasn’t specific enough.
My question is: Where do I run Custom.DB.create_tables() so my database is populated with whatever I have in ‘repo/testing.sql’ when I run iex -S mix phx.server?
That totally depends on your exact setup, though I would probably do that kind of seeding in the process that owns the database, such that it’s reseeded if the process ever has to be restarted.
Do you want to do that whenever your application starts, only in development, only when using the phx.server mix task, …. There’s a lot of versions to this question, which do not share their answer.
I really think the owning process should be responsible, not the application, to ensure that a crash in the DB process at least recovers into “empty but usable” database, rather than a database that doesn’t even have a schema.
Maybe. Sometimes that additional level of rigor is worth it. But I’d argue that’s step two. It’s no longer just the question of where the beams “main” is.
So when start/2 is called your application is just beginning to start up. Your supervision tree is not yet started, which includes your repo. So calling Custom.DB.create_tables() will fail. You could run the function inside the supervision tree as well, after your repo is initialized (more flexible option) or you could run it after Supervisor.start_link, but you need to ensure to still return the result of that call from start/2. start/2 is expected to return a value according to the Application behaviour (Application — Elixir v1.19.5) and commonly that would be the result of starting a supervisor. This is to ensure the application (and often the whole vm) stops when the root process exits.
It’s not necessarily best practice, but also not too out of the ordinary. The difference is mostly in the effects you get if your create_tables fails to work somewhere. How you want to handle that is a different question.
Okay. Could you point me in the right direction so I can explore what NobbZ meant by the “owning process should be responsible”? I can understand that if the owning process fails, the database will be empty when it’s restarted with my current ‘solution’. What function must I write in Custom.DB to achieve that?
This piece of the Elixir docs talks about applications and their life cycle:
Worth knowing the underlying concept comes from Erlang, so to understand why things are the way they are you can also look into Erlang/OTP docs.
This explains the start callback:
For a custom Ecto.Repo, look at
This is the implementation of that callback when you write use Ecto.Repo in your custom module:
AFAIR there’s no “run this code on startup” hook out of the box in Ecto.Repo, but generally many possible entry points with different consequences as the other knowledgeable answers discuss.
I may be sending you off on the wrong direction as I don’t recall having to do it myself, but you can always wrap the Repo with a module you fully control and write the initialization code there, so that your supervision tree will be like MyApp -> MyApp.MyRepoInit -> MyApp.Repo.