cjbottaro
Getting a list of "tagged" modules
Easiest to ask this in code…
defmodule Foo do
use Tagger
tag(:red)
end
defmodule Bar do
use Tagger
tag(:blue)
end
Then at runtime I want to be able to say:
Tagger.all_tagged # %{ Foo => :red, Bar => :blue }
How to define Tagger to do this? I know how to use module attributes and macros to create methods on Foo and Bar to keep track of what they are tagged with, but how to aggregate that info into another module?
Thanks for the help.
Marked As Solved
voltone
I would probably use module attributes to store the tag in the compiled module:
defmodule Tagger do
defmacro __using__([]) do
quote do
Module.register_attribute(__MODULE__, :tag, persist: true)
end
end
end
defmodule Foo do
use Tagger
@tag :red
end
You can then inspect the attributes using module_info/1:
iex(1)> Foo.module_info(:attributes)[:tag]
[:red]
Given a list of modules you can then simply map, filter, aggregate, etc. using Enum.
As for the list of modules, you can use {:ok, modules} = :application.get_key(:my_app, :modules) to get all modules for your OTP app. Or, if you want to scan for modules more broadly, you can look at iex’s autocomplete for inspiration:
https://github.com/elixir-lang/elixir/blob/master/lib/iex/lib/iex/autocomplete.ex#L247
Also Liked
OvermindDL1
It works for any code that is indeed loaded. It is considered loaded when some function on the module is called, until that point it is not loaded (unless the erlang compiler option in the boot forces loads everything in the one file). Your modules will appear once you call something on them.
An application is loaded by the boot loader, but again that gets back into manual plugin definitions instead of dynamic detection. ^.^
voltone
If you look closely you’ll see that the IEx code only relies on :code.all_loaded() alone when :code.get_mode() returns anything other than :interactive.
When you start your application as part of a release, the :embedded mode is used, and all modules included in the release a loaded right after the VM has started. On the other hand, when you run iex on your Mix project, the code server uses :interactive mode in which modules are located and loaded on-demand, as @OvermindDL1 described.
As for application loading, iex -S mix loads your main application and all its dependencies (actually, it first loads the dependencies and then the main app). Some dependencies may explicitly load/start additional applications as they initialize.
RomanKotov
Elxir Protocols have an interesting reflection feature. It looks like SomeProtocol.__protocol__(:impls) and can return a {:consolidated, modules}. It shows all modules, that implement the protocol. You can use this to tag modules, like:
defprotocol TagExample.Tagged do
@spec tags(t()) :: [atom()]
def tags(data)
end
defmodule TagExample.Tag do
alias TagExample.Tagged
defmacro __using__(_opts) do
quote do
Module.register_attribute(
__MODULE__,
:tag,
persist: true,
accumulate: true
)
defimpl TagExample.Tagged do
def tags(_) do
:attributes
|> @for.__info__()
|> Keyword.get(:tag, [])
end
end
end
end
defimpl Tagged, for: Atom do
def tags(module) do
:attributes
|> module.__info__()
|> Keyword.get(:tag, [])
end
end
@spec tagged_modules() :: [module()]
def tagged_modules do
modules =
case Tagged.__protocol__(:impls) do
{:consolidated, modules} ->
modules
_ ->
Protocol.extract_impls(
Tagged,
:code.lib_dir()
)
end
for module <- modules,
module.__info__(:attributes)
|> Keyword.has_key?(:tag),
do: module
end
@spec modules_with_tag(tag :: atom()) :: [module()]
def modules_with_tag(tag) do
for module <- tagged_modules(),
module
|> Tagged.tags()
|> Enum.member?(tag),
do: module
end
end
defmodule TagExample.TaggedModule1 do
use TagExample.Tag
@tag :tag1
@tag :tag2
end
defmodule TagExample.TaggedModule2 do
use TagExample.Tag
@tag :tag2
@tag :tag3
end
# iex> TagExample.Tag.tagged_modules()
# [TagExample.TaggedModule2, TagExample.TaggedModule1]
# iex> TagExample.Tag.modules_with_tag(:tag2)
# [TagExample.TaggedModule2]
# iex> TagExample.Tag.modules_with_tag(:nonexisting_tag)
# []
This example uses module attributes, but you can store tags somewhere else (possibly in methods).
I have also created a blog post with edge cases for this implementation.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance









