I’m trying to do this thing in Elixir and so far I’ve tried various approaches but without luck. This is what I’m trying to do:
During compilation of module A in a library, collect configuration from modules (likely in other libraries) that are dependent on module A. It is unknown which the dependent modules are.
Generate functions in module A using the collected configuration
What would you want to achieve with this, and why not use your module A in the applications that depend on it (say, module B and C), thus generating the functions in modules B and C?
Because module A is going to call these generated functions later on. Can it find the functions easily if they are in modules B and C if it doesn’t know beforehand which modules B and C are?
I’m building a library for handling permission checks. I want to make it pluggable so that other libraries can define permission types such as role together with a callback that handles that specific permission check, such as checking a role. Then later, when a permission is being checked, I need to have this data available so that I know which callback to use for each permission type. One way I could do this is to use a genserver which stores the data, but that could possibly create a bottleneck if a lot of permissions are checked concurrently, so I’d rather generate functions for each permission type during compilation that calls the appropriate callback. I know how to do that part, I just need to get the data in time and that seems to be the tricky part.
I’d probably write a behavior for it like in Plug. The client would specify the callback, and your library would call it as an opaque/anonymous function with the data provided.
Now I’ve taken a look at behaviours but it seems to me like their use is limited to being equivalent to interfaces. I hope that I’m wrong there. Given the permission type “role”, how can I find the correct implementation using this code?
logical_permissions.ex
defmodule LogicalPermissions do
@callback check_permission(String.t, Tuple.t) :: {:ok, term} | {:error, term}
@callback permission_type() :: String.t
end
test_module.ex
defmodule TestModule do
@behaviour LogicalPermissions
def check_permission(value, context) do
"Checking role permission"
end
def permission_type do
"role"
end
end
What do you mean by “find” in this context? You can pass your TestModule to your LogicalPermissions “handler” somewhere in your code. Like
defmodule LogicalPermissions.Handler do
def register(module) do
valid_module?(module) || raise(ArgumentError, message: "invalid callback module")
# add to callback list like in plug
end
@spec valid_module?(module) :: boolean
defp valid_module?(module) do
exports = module.module_info(:exports)
{:permission_type, 0} in exports and
{:check_permission, 2} in exports
end
end
I’m not quite clear what check_permission is supposed to return, you might want to restrict it’s type signature a bit, it might make it easier to reason about.
Thanks for the example, I’ll look into it further. That does look like what I’m after. The function signatures are mostly just copy and paste at this point so don’t pay too much attention to them.
I don’t think you’re misunderstanding - from what I can tell at a glance plug is using module attributes for storing the plugs and that is indeed one of the approaches I’ve tried, I just wasn’t successful. Having a concrete example like this will surely help me.
I actually managed to get this to work. I ended up not going with the pattern used in Plug because I wasn’t sure that I would be able to get a hold of the list in compile time, so instead I used a behaviour and then during compilation I check every module in every app to see which ones adopt that behaviour and then I can generate my functions from that information. Man, this was complicated and I’m quite certain that my code sucks at this point (it starts up every app so it takes quite a long time to compile for example) but at least it was possible to do. If you’re interested you can find the code at https://github.com/ordermind/logical-permissions-elixir. Thanks to everyone who helped me out and you’re very welcome to suggest improvements to my code. The main part of the code is in https://github.com/ordermind/logical-permissions-elixir/blob/master/lib/logical_permissions.ex which is going to be the main module. It’s really polluting the module so I’d like to put it in another module like in a macro or something, but I wasn’t able to get that to work at this point unfortunately.