Personalized embark actions
I adopted Embark in 2025. To be honest, it was more because it seemed to be the typical companion for vertico + consul, than because I really understood what I could do with it.
Besides some very basic use (which I routinely forget) I didn’t bother to explore it in more detail.
Until I noticed that Embark could give me a fundamental component for a mechanism I had been trying to implement in the past.
First, some context. For my job, I often have to navigate through a quite large dbt project. If I were less picky about text editors I’d just use VS Code and the PowerUser package. But, it's no mistery, I’m fond of staying inside Emacs. Where, quite incredibly, there is no dbt support package available.
One simple thing I'd particulary like is the ability of hopping from one file to another following dbt references.
A reference, in dbt, is a Jinja expression that can be used in place of a table or view name, to inform dbt of a dependency between two models (a model is, simplifying, a named SQL query, saved in a file with that name).
For example, you can write a file called us_customers.sql like this:
select * from {{ ref('mart_customers') }}
where country = 'US'
and dbt knows that the mart_customers model (contained in a file
mart_customers.sql, in some directory of the project) is an upstream
dependency of us_customers.
This is useful because then you can invoke dbt like this (notice the +
sign):
dbt run -m mart_customers+
and dbt knows that it needs to refresh mart_sustomers
PLUS its downstream dependencies, including us_customers.
A typical dbt project may contain hundreds of interdependent models, organized in a tree of directories. So, references are very convenient.
Now to my specific Emacs problem. What I'd like to be able to do is, when the cursor is on a reference, to visit the file that contains the definition of the referenced model, without having to navigate the directories to find it.
The first useful thing that Embark does is figuring out what's the
identifier I'm on with the cursor. In the example above, that would be
the string mart_customers. In Embark (and Emacs) parlance, that is
called the target at point. More precisely, in this case, that's the
identifier at point.
When I invoke embark-act (it's customary to map this function to
C-.), Embark figures what's the identifier at point, and I get a
contextual list of actions that can be applied.
Now, I need to add my personalized action to that list.
For my needs, that is just a function that, given a reference name
(the identifier at point) puts .sql at the end of the string, then
uses the result as an argument for project-root-find-file. Model files
are guaranteed to be unique across all the project (otherwise dbt does
not work), so there is no ambiguity about the file I want to visit.
Here the implementation, and how it was integrated in the Embark's identifier target list.
(defun larsen/embark-dbt-navigate-reference (reference &rest args)
"Opens the model source code for the dbt reference at point"
(interactive "sReference: ")
(project-root-find-file (string-join (list reference ".sql"))))
(use-package embark
:ensure t
:bind (("C-." . embark-act)
:map embark-identifier-map
("D" . larsen/embark-dbt-navigate-reference)))