Crowdhailer
Implementing middleware using `defoverridable` and `super`
EDIT: I have written a blog post on this topic
I have been implementing middleware using the defoverridable and super. For example
# myapp.ex
defmodule MyApp do
use Tokumei.Routing
use Tokumei.Exceptions
use Tokumei.ContentLength
use Tokumei.CommonLogger
use Tokumei.MethodOverride
# define routes
end
The Routing module creates a function handle_request/2 and in each middle ware this function is defined as overridable and a new definition created that calls into super as needed. For example the implementation of Tokumei.MethodOverride is as follows, link to code on gihtub.
defmodule Tokumei.MethodOverride do
def override_method(request = %{method: :POST, query: query}) do
{method, query} = Map.pop(query, "_method")
case method && String.upcase(method) do
nil ->
request
method when method in ["PUT", "PATCH", "DELETE"] ->
method = String.to_existing_atom(method)
%{request | method: method, query: query}
end
end
def override_method(request) do
request
end
defmacro __using__(_opts) do
quote do
defoverridable [handle_request: 2]
def handle_request(request, config) do
request = unquote(__MODULE__).override_method(request)
super(request, config)
end
end
end
end
My questions are:
- Is there any cost to repeatedly overriding a function?
- Are there any other libraries doing similar?
- General comments about the approach
Most Liked
josevalim
I will change the private name of the autogenerated function to include a random component just so people don’t do that.
OvermindDL1
I hope there is not much cost! I’ve been using a lot of super to override action/2 in controllers to handle exceptions such as redirect’s when a permission exception is thrown. ^.^
But yeah, I’ve not looked at how super is implemented, hmm…
So it seems to be implemented at:
So it looks up the previous function definition and seems to call it, and just above that is where you see it defines in ets the information of what is overridable, with the store function being at the bottom.
So, it looks like the old/parent definitions are defined with a combination of the name and a counter, so always unique, and defp’d, so calls to them are always ‘local’ calls (in erlang parlance), which is fast.
So no issue, it looks like super is fast, I’d not worry about lots of them at all. 
OvermindDL1
Also entirely an implementation detail and you should not do this, but you could ‘directly’ call an older version of a function by using its hidden/internal name (for a ‘blah’ function it would be the function name of :"blah (overridable #{count})" for whatever count you want to go ‘up in the stack’). ^.^








