Case to bind variable with assigns

I have a really simple component that places a rectangle over the corner of an image to leave a cutout. As written below it works as intended:

attr :top, :boolean, default: false
  attr :bottom, :boolean, default: false
  attr :left, :boolean, default: false
  attr :right, :boolean, default: false

  def corner_cutout(assigns) do
    assigns =
      assign(assigns,
        rotation:
          case {assigns.top, assigns.bottom, assigns.left, assigns.right} do
            {true, false, true, false} -> 30
            {true, false, false, true} -> 330
            {false, true, true, false} -> 30
            {false, true, false, true} -> 330
          end
      )

    ~H"""
    <div class={"w-32 h-32 bg-white transform rotate-[#{@rotation}deg]"}></div>
    """
  end

and this allows me to really cleanly use the cutout in another component:

~H"""
<.corner_cutout top left />
"""

What bugs me though is the case statement, where it’s quite hard to read what is matching for each of the case clauses since it’s relying on positional arguments.

I tried re-writing by passing in assigns to the case statement so that it would match on something like {assigns.top, assigns.left} -> 30 and multiple variations of this, but always get the error cannot invoke remote function assigns.top/0 inside a match.

Is there a way to re-write this so it’s more readable and I don’t have to match the position of the booleans to know what’s matching?

You can match assigns like this:

case assigns do
  %{top: true, left: true} -> 30
  %{top: true, right: true} -> 330
end

Or you can even type out the ones that should be false to make sure that the input is valid:

case assigns do
  %{top: true, bottom: false, left: true, right: false} -> 30
end
3 Likes

At a minimum, consider extracting the case to a private function:

defp cutout_rotation(assigns) do
  case {assigns.top, assigns.bottom, assigns.left, assigns.right} do
    {true, false, true, false} -> 30
    {true, false, false, true} -> 330
    {false, true, true, false} -> 30
    {false, true, false, true} -> 330
  end
end

Another way to shorten it would be to notice that only 4 of the possible 16 combinations are included in the case, so a cond for just those would be shorter:

defp cutout_rotation(assigns) do
  cond do
    assigns.top && assigns.left -> 30
    assigns.top && assigns.right -> 330
    assigns.bottom && assigns.left -> 30
    assigns.bottom && assigns.right -> 330
  end
end

I’m not normally a huge fan of pattern-matching a bunch of fields, but you could do it if all the assigns. in the previous version seem repetitive:

defp cutout_rotation(%{top: top, left: left, right: right, bottom: bottom}) do
  cond do
    top && left -> 30
    top && right -> 330
    bottom && left -> 30
    bottom && right -> 330
  end
end
2 Likes