Standalone Ecto Repo

Hello!
I have the Phoenix application and one more separate Elixir app. Both are connected to one database and I want to have only one repository for both applications. Now it’s included in my Phoenix app. So I need help in choosing the right way:

  1. Can I share my ecto repo for another elixir app from Phoenix app?
  2. Maybe I can make a repo as a standalone application and share for both apps?
  3. As I understand, I can make an umbrella. But what are the advantages?

1./2. Use the second option, it makes thing cleaner and you don’t have circular dependencies between apps.

  1. Umbrellas are basically a special folder structure with some enhancements like shared config / dependency resolution. They don’t help you any more with your mentioned problem then having separate mix projects.

so how can I share it with other apps if repo will be standalone?

Add a dependency in mix.exs to the app and call the repo module like you do it with any other module you depend on.

If you’re asking how to refer to a separate application in your local file system, you can do so by adding {:my_app, path: "path/to/my_app"} to your dependencies in mix.exs. Take a look at https://hexdocs.pm/mix/Mix.Tasks.Deps.html

1 Like

Where would you put schemas, then, and why?

Ecto actually doesn’t really care. The repo just takes data and interacts with the db based on the data. So the question of where and why is very much free to be answered depending on the project constraints.

2 Likes

In that case, I don’t see the benefit. That isn’t to say there isn’t one. It’s just that I don’t see it. By that, I mean, if we call Repo, and this Repo happens to be in a separate app, does it make a difference to the caller?

I might call Repo.foo from any number of umbrella apps. Does it matter to them if the dependency comes directly from Ecto or from another app in the umbrella?

I personally like to put module names to ‘implementations’ in the config.exs, that way it is easy to change based on environment, makes testing so much easier (though less for Ecto.Repo’s, in other cases it is super useful), or just pass them into functions. I ‘try’ to design with pattern of accepting an override in every function that uses it with a default of the application environment, basically this:

def get_blah(id, opts \\ [repo: Application.get_env(MyApp, :repo, nil) || throw "MyApp.repo configuration not set"] do
  ... do stuff that uses `opts[:repo]` like `opts[:repo].insert(...)` ...
end

Although I have a helper to do that more easily (that also does a compile-time test to make sure it’s actually set at compile-time in addition to the runtime).

As an aside, this is the Witness programming pattern, it’s very useful though has some mental overhead to actually ‘use’ it. I should make a library someday to simplify its use, hmm…

Sorry, but all I can think when I saw your get_blah example is… bloody hell, that’s ugly! :joy:

Lol! Honestly in most cases I have more than one possible option so most code is actually like this (copy pasted from work project):

  def get_inventory(env \\ nil, section_slug, opts \\ []) do
    {repo, opts} = get_opt_repo(opts, MainProject)

Where get_opt_repos (along with lots of other get_opt* helpers) is defined in one of my main imported modules as:

  def get_opt_repos(opts, scope, key \\ :repo) do
    Keyword.pop_lazy(opts, key, fn ->
      case Application.get_env(scope, key, nil) do
        nil -> throw "Configuration #{scope}.#{key} is not set"
        repo when is_atom(repo) -> res # TODO: Maybe test if res is actually a proper Repo module?
        invalid -> throw "Configuration #{scope}.#{key} is invalid, needs to be an atom, is currently: #{inspect invalid}"
      end
    end)
    |> case do
      {repo, opts} when is_atom(repo) -> res # TODO: Maybe test if res is actually a proper Repo module?
      {invalid, _opts} -> throw "Option #{key} is invalid, needs to be an atom, is currently: #{inspect invalid}"
    end
  end

^.^;

I would love a framework that handles arbitrary options, validating their values, etc… etc… Hmm, maybe Ecto changesets would work for that, though sounds like a lot of overhead…

You have no idea how much I wish Elixir were statically typed… ^.^;

How would Elixir being statically typed help in this example?

1 Like

Because then I could constrain the passed in options and arguments based on their types, thus validation at that point is far less of an issue and could even auto-generate things like string->struct mappings and more. :slight_smile: