Vysakh Sreenivasan
Vysakh Sreenivasan

Reputation: 1729

How to create unbound variable part of an array(a method param) in a method created by macros

In a phoenix app, I'm trying to implement authorization like this

#= Bouncers.Bouncer.check_entry(conn, %MyApp.User{})
#=> true

defmodule Bouncers.Bouncer do
  alias Bouncers.Areas

  def check_entry(conn, resource) do
    verify(resource, conn.path_info)
  end

  defp verify(resource, path) do
    try do
      Areas.verify(resource, path)
    rescue
      _ -> false
    end
  end

end

defmodule Bouncers.Doors do

  defmacro bouncer_for(model, allowed_routes) do
    for route <- allowed_routes do
      quote bind_quoted: [route: route, resource: model] do
        def verify(resource, route), do: true
      end
    end
  end
end

defmodule Bouncers.Areas do
  require Bouncers.Doors
  import  Bouncers.Doors

  bouncer_for %MyApp.User{}, [
    ["api", "users"]
  ]
end

bouncer_for creates multiple verify method for a Struct and a path_info array

bouncer_for %MyApp.User{},  [
      ["api", "users"],
      ["api", "posts"]
]

will create

def verify(%MyApp.User{}, ["api", "users"], do: true
def verify(%MyApp.User{}, ["api", "posts"], do: true

It works for these paths, but I'm stuck at paths that are of the format ["api", "users", _ ], this throws an error about unbound variable,. I would like to something like this atleast,

bouncer_for %MyApp.User{},  [
      ["api", "users", "*"],
]

And replace "*" with _ inside the parameter of the method generated by the macro,. Any pointers?

Upvotes: 3

Views: 292

Answers (1)

Patrick Oscity
Patrick Oscity

Reputation: 54684

You need to define your macro like this:

defmodule Bouncers.Doors do
  defmacro bouncer_for(model, allowed_routes) do
    for route <- allowed_routes do
      quote do
        def verify(unquote(model), unquote(route)), do: true
      end
    end
  end
end

Then it should work as expected.

The reason for this is that bind_quoted will attempt to pass down the binding (i.e. actual values) to the macro, but ["api", "users", _] is not a valid value in itself. It is rather a pattern, which you can embed in the dynamically generated function definition using unquote. This will behave as if you had written the pattern directly in the definition. To illustrate this problem, here is some code that is roughly equivalent to the definitions generated by the macros:

# using bind_quoted
@route ["api", "users", _]                    # invalid value, raises error
def verify(%MyApp.User{}, @route), do: true

# using unquote
def verify(%MyApp.User{}, ["api", "users", _]), do: true

Upvotes: 4

Related Questions