Extract Database Modules (for Phoenix App.) into Their Own Library?

Basically, I’m writing a Phoenix app. but a lot of the database (and the modules which handle INSERTing-into/UPDATEing and retrieving from the database) are stuff that could be reused for similar projects.

I gather that the migrations (the database should look the same, obviously, for the modules to work) would need to be generated and provided via a script that must be run (How to run migrations that are in a library? but Oban is an example given in that conversation). This is fine and makes sense.

Where I’m uncertain of how to do things is to structure the library which would hold the database-handling modules (would said library need to be a Phoenix project? Could it just need to worry about having Ecto as a dependency?) and how to include said library (in another project) such that I could invoke, say, reading data from the library and it’d know to use the database of the other project. Or call one of the functions to write some data to the database and it’d know to use the database of the other project.

This library isn’t complex or doing anything terribly out of the box or crazy. It’s your standard read-Object-A-and-Object-B-from-the-database-and-put-them-into-struct.s/schemas and write-data-to-database-as-Objects-A-and-B.

I just don’t want to have to write the same logic twice and, if two projects are both going to have Object-A and Object-B in their databases, I’d rather have a library I can just include as a dependency and, then, run LibraryObjectAs.list_all_object_a() without having to define that function out in both projects; the dependency takes care of writing the function once, for me, and I just import the library and can use the function seamlessly with my database of my project.

As such, I don’t think something like an umbrella project would work as I’d like for it to be able to be used by others, outside of myself.

I just…don’t have any idea of how I’d set this up.

I’m not 100% sure I get everything you’re after—are you saying the schemas would also live in the library? If so, off the top of my head I would:

a) I would create a custom mix task in the library that will generate the appropriate migrations into your app.
b) Add a config option to your library to set the Repo it uses: config :my_library, :repo, MyApp.Repo.

In terms of keeping schemas in a library, I could see you running into problems where you’d want associations from your app on the schemas in your library, maybe. You could possibly run into some other issues too :thinking: But anyway, this is one way to do it!

I was thinking of keeping schemas in the library (at least, for those tables which are stemming from the library). While you can, obviously, do some creative stuff with the schemas, I’ve mostly been using them to map a module to the database table. Keeping the schemas (that are supposed to map to predefined DB structures) in the project, rather than the library, would mean needing to recreate the same schema in any new project that relies on the library (so goes my reasoning, at least).

Now, I may be misusing or misunderstanding something about how these things work but I’ve created associations, so far, by invoking the module that contains a schema (that I want to associate to); couldn’t I just reference the modules in the library that have the schema I wanted (e.g. something like belongs_to(:object_a, Library.ObjectA, foreign_key: :id, references: :object_a_id)) from within my project (which has the library as a dependency)?

Ah! There we go; I’d stumbled upon a few Elixir/Phoenix projects that really underscored the flexibility available in similar manners as this but help if I could remember where I’d seen them, again, now that I needed to implement something similar. Thanks so much for spelling this out so clearly.

Yes for sure, I was just saying if you found yourself needing the inverse association (has_many :object_bs, MyApp.ObjectB) then it’s going to start getting a little messy. Maybe you won’t, just something to be aware of!

Ahhh; I gotcha. I don’t think I will but a fair point!

Oh! One more question (if you have an idea): what would be the best method to generate the initial project structure of the library? I imagine I won’t need the Phoenix dependencies? I could, of course, setup all the files by hand (I expect they’ll – largely – be similar to the files as they exist in my current project) but I figured a mix command would be smarter in case I don’t think of anything or miss something, as is often the case with manual approaches.

Just $ mix new my_lib. You’ll need at least ecto as a dep, possibly ecto_sql.

Mmm; makes sense. Thanks – so much –, again!

1 Like

You might find this library useful for those cases: Exto — exto v0.3.0

1 Like

Well I’ll be… cool!