Collect configuration from dependent modules during compilation

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:

  1. 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.
  2. Generate functions in module A using the collected configuration

Is this possible to do in Elixir? If so, how?

:wave:

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?

So is it like a @behaviour (like a genserver behavior)? Can you provide an example that’s a bit more concrete, please?

1 Like

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.

Sounds great, thanks for the tip!

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.

Maybe https://github.com/elixir-plug/plug/blob/master/lib/plug/builder.ex would help? Although I might be completely misunderstanding what you are trying to accomplish here …

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.

Maybe as another example, here’s my (very simplistic) approach to authorization / permissions: Should user permissions / data correctness logic live in my Ecto validations or Context logic?

1 Like

I’m not sure if this would help you or not but have a look at FunWithFlags. If anything it might give you some ideas :slight_smile:

Thanks, I’ll take a look.

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.

Oh my god, this was so easy to do using config instead of my other approach, and without the drawbacks. I should be fine now, thanks again everyone!

1 Like