Dear Tidal… Fix this! This is not the first time/artist I have seen random albums for; I can generally parse the noise if I know the artist well or they haven’t dropped an album in years but I shouldn’t have to!

I’m ending my Neovim experiment, it’s still fun to hop in for trivial stuff but switching back to Sublime has released such a cognitive load from my mind that helps me to focus on business problems and not “wait which buffer was that in?”

Ecto, iLike you

In Elixir macros are… discouraged, from the docs:

Macros should only be used as a last resort. Remember that explicit is better than implicit. Clear code is better than concise code.

I get it; working in Ruby where every codebase seems to be playing a game of Schrödinger’s Macro it’s refreshing to work in an ecosystem where the code in your editor is what it is. As such I’ve always tried to embrace minimalism in Elixir. Yet Elixir has macros and there are some really good “last resorts” as mentioned above. I’ve encountered one such case a few times when working in Ecto; out of the box Ecto has at least 99% of anything I could ever want in a database wrapper. Over the years there have been an odd handful of cases where I’ve wanted to extend the functionality of Ecto in one way or another. I’m going to provide a few examples of this in action.

Keep in mind both of these examples, out of context may feel a bit contrived and in neither case is the macro reducing the lines of code. However; if placed in the Repo module these macros would make convenient reusable Ecto functions which could be called throughout the codebase.

Combining iLikes

A few years back I was working on some search functionality for a client. Their products were all made to order for specific customers. Allowing customers to search their order history came with several different query options including the name (first, last, email) of the person who placed the order, those same options of the salesperson who placed the order in their behalf or various attributes about the product. This led to a whole chain of joins and ilikes:

   val ->
        query
        |> join(:left, [order: o], u in assoc(o, :user), as: :user)
        |> join(:left, [order: o], s in assoc(o, :salesperson), as: :sp)
        |> join(:left, [user: u], uc in assoc(u, :user_credentials), as: :uc)
        |> join(:left, [salesperson: sp], sc in assoc(sp, :user_credentials), as: :sc)
        |> join(:left, [order: o], oli in assoc(o, :order_line_items), as: :oli)
        |> join(:left, [oli: oli], prod in assoc(oli, :product_item), as: :prod)
        |> join(:left, [prod: prod], config in assoc(pi, :configuration), as: :config)
        |> join(:left, [config: config], pt in assoc(config, :product_type), as: :pt)
        |> search_function(val)
        |> group_by([order: o], o.id)
 	end 
 end 
 
   defp search_function(query, value) do
    str = "%#{value}%"

    query
    |> where([order: o, uc: uc, sc: sc, conf: conf, pt: pt],
    	ilike(o.po_number, ^str) or
        ilike(uc.email, ^str) or
        ilike(uc.firstname, ^str) or
        ilike(uc.lastname, ^str) or
        ilike(sc.email, ^str) or
        ilike(sc.firstname, ^str) or
        ilike(sc.lastname, ^str) or
        ilike(pt.name, ^str) or
        ilike(pt.design_id, ^str)
        )
  end

It’s readable enough, especially the joins; I’d argue that Ecto’s elegant syntax actually makes this slightly more readable than a standard SQL statement but search_function is a bit much; to the point where Credo started lighting up cyclomatic complexity warnings

There was a better way. Maybe not for all cases; frankly if I hadn’t been warned about the complexity I would have called it day here. I thought it would be fun to condense this and pipe all joins into a smaller search_function somehow with fewer ilikes. This is where one can make good use of macros and Ecto:


  defp search_function(query, value) do
    str = "%#{value}%"

    query
    |> where(
      [order: o, uc: uc, ic: ic, bd: bd, obi: obi],
      multiple_ilike([:email, :firstname, :lastname], uc, str) or
        multiple_ilike([:email, :firstname, :lastname], ic, str) or
        multiple_ilike([:name, :design_id], bd, str) or
        ilike(o.po_number, ^str)
    )
  end
  
  
  defmacro multiple_ilike(keys, schema, value) do
    Macro.expand(ilike_irr(keys, schema, value), __CALLER__)
  end

  defp ilike_irr([key | keys], schema, value) do
    quote do
      ilike(field(unquote(schema), unquote(key)), ^unquote(value)) or
        unquote(ilike_irr(keys, schema, value))
    end
  end
  
  defp ilike_irr([key, key2], schema, value) do
    quote do
      ilike(field(unquote(schema), unquote(key)), ^unquote(value)) or
        ilike(field(unquote(schema), unquote(key2)), ^unquote(value))
    end
  end

Working from the top this takes our lines of code from 9 to 4 still making just as many ilike calls. I would have employed multiple_ilike/3 for orders as well if we were searching more than one column.

It’s fairly standard recursion in Elixir, made only a little more frightening with the quoting and unquoting of macro code and passed in runtime values.

To illustrate lets call it: multiple_ilike([:email, :firstname, :lastname], user_credentials, "%trav%") . The recursive call to ilike_irr/3 needs at least two columns (although one could handle a single column for a safer API). Each iteration uses Ecto’s ilike#2 function it takes your list of columns (keys) the table (schema) and the search string. We unquote these values because they are not part of the macro ie we want them to be whatever we are passing in. The first iteration above is going to add to the query: ilike(field(user_credentials, :email), "%trav%")) fairly straightforward (if you aren’t familiar with Ecto, field/2 is a way of dynamically accessing a column which we need because we the Macro won’t know the schema/keys being passed in at compile time). This initial ilike/2 is appended with an or/2 or in regular SQL “or” and the macro is called again. ilike(field(user_credentials, :firstname), "%trav%") which makes up the right hand side of the or we continue in this fashion until there are only 2 keys left at which point we return both ilike queries having a fully formed statement with multiple ilike ... or ilike ... statements chained together.

I love stuff like this; Ecto already feels like magic (not because it’s obfuscating anything just because of how smooth things are) and this lets me add a few ingredients of my own to the potion.

Happened to wake up ten minutes before the Nobel Peace Prize announcement. Predictions: Donald Trump will not win, the United States will declare war on Norway.

Copy git hashes

I’ve been reaching more and more for git history commands to get details about the file I’m working on. I used to use tools like GitHub desktop or Sublime Merge but I never felt like they added that much value, it’s just faster to to call up a git log somefile or git log -L 5,10:somefile. The only shortcoming of this approach is it generally leaves me wanting a commit hash in my clipboard (often to switch to or to run git diff with). No more! Today I doubled down grabbing these hashes without having to mouse over and select the hash; I give you: git diff myfile --pretty=oneline | head -c7 | pbcopy This is the most simple form of this that I can find.

--pretty=oneline ensures the commit hash is first, piped into head -c7 we get the first 7 characters of the hash (you could grab more or use some kind of regex to get the whole thing but I believe 7 is the minimal amount you can give git where it will reliably find a commit). Pipe it to pbcopy and you got a little git hash.
It’s a fair amount of typing, I think I could set --pretty=oneline in my git config and frankly I could likely alias this whole thing as some kind of function in my .zshconfig but for now it is what it is.

Pretty funny that Obsidian makes you type out the “Quit without saving” Vim command to enable Vim mode. I love it but this is the first knowledge test I’ve taken to toggle a setting.

Doubling down on my Vim usage lately, getting to know some of the dot motions really feels like a superpower. I hate to say I’ve drunk the Kool-aid but I think I’m getting there.

My daughter found two monarch caterpillars on our milkweed plants which has kicked off an amazing journey.

Its wild how much they eat and also how fast they grow.

Finished reading: A Different Kind of Power by Jacinda Ardern 📚

I liked this book a lot, and I like Ardern a lot. I know she’s not overly popular in New Zealand, but all the negative review’s I’ve read center on mask mandates and required vaccinations which I have no issues with. I’d be curious to hear from come Kiwis on about this.