fceruti

fceruti

Sup ppl, I just wanted to share a little macro I created to simplify LiveView code. It allows you to convert

socket.assings.variable

into

~a|variable|

For instance here’s a function from Chris McCord’s live_trek

  def handle_event("delete", %{"id" => id}, socket) do
    todo = Todos.get_todo!(socket.assigns.scope, id)
    {:ok, _} = Todos.delete_todo(socket.assigns.scope, todo)

    {:noreply, socket}
  end

Would be

  def handle_event("delete", %{"id" => id}, socket) do
    todo = Todos.get_todo!(~a|scope|, id)
    {:ok, _} = Todos.delete_todo(~a|scope|, todo)

    {:noreply, socket}
  end

Necessary? Not really.

But I thought it looks much better, specially when you have lot’s of socket.assigns and want to reduce verbosit, avoid unpacking assigns and reduce formatter line breaks.

Here’s v0.1

  defmacro sigil_a(expr, _modifiers) do
    socket = Macro.var(:socket, nil)

    quote do
      Map.get(unquote(socket).assigns, String.to_existing_atom(unquote(expr)))
    end
  end

And here’s a vim command to search and replace:

:%s/socket\.assigns\.\([a-zA-Z0-9_]*\)/\~a|\1|

Showing Posts 1 to 10

tfwright

tfwright

Nice idea. Curious, why Map.get rather than fetch? I think the behavior being replaced raises if the expected key is missing, whereas this will return nil I think

fceruti

fceruti OP

No reason, probably a bug :stuck_out_tongue:

ityonemo

ityonemo

  1. You can also move String.to_existing_atom to just String.to_atom outside of the quoted block, this saves you a function call at runtime.
  2. might want to try var!(socket) inside the quote instead of Macro.var outside the quote. I think they are equivalent but var!(…) Is slightly more legible and idiomatic IMO
zachallaun

zachallaun

If you wanted to be really evil, you could overload the @ operator if the caller environment contains socket :smiling_imp:

defmodule Evil do
  defmacro __using__(_) do
    quote do
      import Kernel, except: [@: 1]
      import Evil
    end
  end

  import Kernel, except: [@: 1]

  defmacro @name do
    var = Macro.var(:socket, nil)

    if assigns_call?(__CALLER__, name) do
      {name, _, _} = name
      quote(do: Map.fetch!(unquote(var).assigns, unquote(name)))
    else
      quote(do: Kernel.@(unquote(name)))
    end
  end

  defp assigns_call?(env, {name, _, nil}) when is_atom(name) do
    Macro.Env.has_var?(env, {:socket, nil})
  end

  defp assigns_call?(_, _), do: false
end

defmodule Hmm do
  use Evil

  @okay :foo

  def maybe(socket) do
    @okay
  end

  def wow do
    @okay
  end
end

dbg(Hmm.maybe(%{assigns: %{okay: :NEAT!}}))
dbg(Hmm.wow())

(don’t do this)

v0idpwn

v0idpwn

This would have a lot of potential if there were good overridable unary operators in Elixir.

Unfortunately, +, -, !, ^, not, @, & are too useful and meaningful to be overriden. There is ~~~ but its ugly and too hard to write :stuck_out_tongue: .

Makes me think Elixir maybe could support an unary $ operator.

ityonemo

ityonemo

I mean, it’s what happens in EEx templates, though!

cmo

cmo

$ is being used for the potential new type system

theodore

theodore

Pretty cool!

sodapopcan

sodapopcan

Innnnnteresting. I was trying to figure out how to do something like this and couldn’t figure it out. I was trying to use var!/2 for this to no avail. I never understood that you can check the caller env.

I was looking to create a single guard that would work on both socket and assigns. I have a lot of single module CRUD LiveViews so I wanted something like:

def handle_event("update", %{"user" => user_params}, socket) when in_action([:edit]) do
  #...
end

def render(assigns) when in_action([:edit]) do
  ~H"""
  """
end

This would check whichever existed of socket.assigns.live_action or assigns.live_action was in [:edit].

Even though I don’t think that is particularly evil (that is, what I wanted to do—what you’re suggesting is absolutely pure evil :see_no_evil_monkey: :sweat_smile:) , I ultimately decided I’d rather not do anything out the ordinary like this so I never bothered to seek help figuring it out, but it’s nice to know how it could be done, so thanks!

(that is, if that would even solve the problem… I haven’t actually tried, just responded immediately, lol, but still informative for me)

zachallaun

zachallaun

Glad it was informative!

It’s definitely worth digging into the environment info you have available in macros. Even in the OP’s example, you could check the caller env for a socket var and raise an informative error if it doesn’t exist (as opposed to the underlying Map.fetch! error or whatever you end up using).

Where Next? Top

Trending in Guides/Tuts Top

c4lliope
# ~/src/livebook/.iex.exs System.cmd("xdg-open", [ LivebookWeb.Endpoint.access_url() ] ) Because Livebook requires a unique passcode on ...
New
krasenyp
You probably already know that <span>{nil}</span> in a HEEX template produces <span> </span> when rendered. I fin...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews