Is there a way to search for an Ecto Schema module by only using the table name?

As per the title, is there a way to call a magical function that returns an atom, namely the schema module? Something like this:

Ecto.Somewhere.magic_function_returning_schema_for_table_name("companies")

Which would, f.ex., return MyApp.Companies.Company Ecto Schema module – which has the schema macro and everything else in it.

I am trying to create certain table-agnostic functions which take a configuration in the form of mapping between a table name and actions per column. I’d like that configuration to be as minimal as possible and I don’t want to mandate the users of my code to also provide a fully qualified Elixir module name.

Is there a way?

2 Likes

Given that schemas are hardly coupled to tables I doubt something like this exists. You could have the following module:

defmodule A do
  schema "" do
  end
end

… and use it like this {"table_a", A} and {"table_b", A}, which are valid Ecto.Queryables.

That can work. Any idea how to encode such a tuple as a key of a JSON object though?

Nevermind my last question.

I figure I’ll just replace my config that now looks like this:

{
  "companies": {
    ...column configuration here...
  }
}

…with:

{
  "MyApp.Companies.Company": {
    ...column configuration here...
  }
}

Sure it’s a bit more writing for whoever wants to use the code, but at least the table name is directly accessible from the schema module by doing MyApp.Companies.Company.__meta__.source (or Ecto.get_meta).

Accepting @LostKobrakai’s answer since it pointed me in the right direction.

1 Like

I have a vague guess what you are building and I am excited.

Each Ecto schema module exports an __schema__/1 function that you can use for reflection. The following snippet is probably a bad idea but here’s an example of getting all schemas/tables for hexpm project:

iex> app = :hexpm
iex> {:ok, modules} = :application.get_key(app, :modules)
iex> Enum.map(modules, fn mod -> function_exported?(mod, :__schema__, 1) && {mod, mod.__schema__(:source)} end) |> Enum.filter(& &1)
[
  {Hexpm.Accounts.AuditLog, "audit_logs"},
  {Hexpm.Accounts.Email, "emails"},
  {Hexpm.Accounts.Key, "keys"},
  {Hexpm.Accounts.Key.Use, nil},
  {Hexpm.Accounts.KeyPermission, nil},
  {Hexpm.Accounts.Organization, "organizations"},
  {Hexpm.Accounts.OrganizationUser, "organization_users"},
  {Hexpm.Accounts.PasswordReset, "password_resets"},
  {Hexpm.Accounts.Session, "sessions"},
  {Hexpm.Accounts.User, "users"},
  {Hexpm.Accounts.UserHandles, nil},
  {Hexpm.BlockAddress.Entry, "blocked_addresses"},
  {Hexpm.Repository.Download, "downloads"},
  {Hexpm.Repository.Install, "installs"},
  {Hexpm.Repository.Package, "packages"},
  {Hexpm.Repository.PackageDependant, "package_dependants"},
  {Hexpm.Repository.PackageDownload, "package_downloads"},
  {Hexpm.Repository.PackageMetadata, nil},
  {Hexpm.Repository.PackageOwner, "package_owners"},
  {Hexpm.Repository.Release, "releases"},
  {Hexpm.Repository.ReleaseDownload, "release_downloads"},
  {Hexpm.Repository.ReleaseMetadata, nil},
  {Hexpm.Repository.ReleaseRetirement, nil},
  {Hexpm.Repository.Repository, "repositories"},
  {Hexpm.Repository.Requirement, "requirements"}
]

the ones with nil are embedded schemas.

3 Likes

Thank you. I agree it should not be done but it’s good to have the option if need be.

I could imagine an Ecto 3.2 that can allow such dynamism?

You’d still need to be careful with e.g. https://hexdocs.pm/ecto/2.2.6/Ecto.Schema.html#belongs_to/3-polymorphic-associations, which doesn’t use the name defined for schema/2.

1 Like

Anonymiser. Not very exciting, to be fair. :slight_smile:

Damn, I thought about something else.

True. In my case though, I simply need a way to resolve a top-level DB-backed table name to an Ecto schema.

Otherwise it gets really tricky.

What’s that? Might give me an idea for the future.

I way to migrate the database from the current (DB based) state to a desired (dynamically built) state during runtime. Like Doctrine does it within the PHP ecosystem.

Can you clarify what that dynamically built state would be? Is it just in-memory structures, or something else?

Well, yeah, it would be in-memory I guess. But generally, it wouldn’t matter where it comes from. Could be from a GenServer or a config file. That structure could be a map where the key is the table name and the value is another data structure explaining the columns and other stuff like indexes.

Then, the imaginary lib could at least have two functions:

TheLib.diff(repo, db_schema_description_map)
TheLib.migrate(repo, db_schema_description_map)

migrate/2 could also be apply/2, you get the point.

My personal usecase would be that I hold the schema definition somewhere (e.g. GenServer) and have other stuff mutate it during runtime and then the DB could be changed accordingly.

What Doctrine does (by default or by config, I am not sure), it does not delete columns automatically, so there won’t be any data loss.

@dimitarvp If you want, I can explain my usecase in more detail via PM. Maybe it’s also a project we could work on together. I always wanted to build such a thing in Elixir, I just suck at DB stuff :smiley:

1 Like

So you are interested in transparently carrying data sets / databases between engines? Correct me if I’m wrong.

As for private messages, by all means, do contact me. Lately I’m obsessed with static strong typing and with sqlite in particular but what you propose is also quite interesting and useful.

No, just a dynamic database schema definition that can be applied to the database at runtime.

Will send you a PM tomorrow.