Skip to content

Instantly share code, notes, and snippets.

@roehst
Last active May 22, 2023 09:28
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save roehst/1dd4ff9a2d75a7285b144ca9b7150748 to your computer and use it in GitHub Desktop.
Save roehst/1dd4ff9a2d75a7285b144ca9b7150748 to your computer and use it in GitHub Desktop.
Experimenting with Elixir Metaprogramming: Agentize

Experimenting with Elixir Metaprogramming: Agentize

In this post, we will explore a simple experiment on Elixir metaprogramming. The experiment is called Agentize and its goal is to create a Storage module that is an Agent with a map for a given struct.

I was originally reaching for ETS, but this method turned out much simpler.

The Agentize Experiment

The idea behind Agentize is to provide an easy way to store and retrieve data for a given struct. It uses Elixir metaprogramming to generate a Storage module that is an Agent with a map for the specified struct.

The experiment is composed of two files: agentize.ex and user.ex.

agentize.ex

The agentize.ex file contains the implementation of the Agentize module.

defmodule Agentize do
  defmacro agentize(struct_name) do
    quote do
      defmodule Storage do
        use Agent

        def start_link(_opts \\ []) do
          Agent.start_link(fn -> %{} end, name: unquote(struct_name))
        end

        def put(key, value) when is_map(value) and is_struct(value, unquote(struct_name)) do
          Agent.update(unquote(struct_name), fn state -> Map.put(state, key, value) end)
        end

        def get(key) do
          Agent.get(unquote(struct_name), fn state -> Map.get(state, key) end)
        end

        def delete(key) do
          Agent.update(unquote(struct_name), fn state -> Map.delete(state, key) end)
        end

        def list_users do
          Agent.get(unquote(struct_name), fn state -> Map.values(state) end)
        end
      end
    end
  end
end

The agentize macro takes the name of the struct as its argument and generates a module called Storage that extends the Agent behaviour. This module defines the following functions:

  • start_link: starts the Agent with an empty map as its initial state and the name of the struct as its name.
  • put: updates the state of the Agent with a new value for a given key. The value must be a map that matches the specified struct.
  • get: retrieves the value of a given key from the state of the Agent. delete: removes the value of a given key from the state of the Agent.
  • list_users: retrieves all the values in the state of the Agent.

user.ex

The user.ex file defines a struct called User and uses the Agentize module to generate the Storage module for it.

defmodule User do
  require Agentize

  defstruct [:name, :email, :age]

  Agentize.agentize(User)
end

The User struct defines three fields: name, email, and age. The require Agentize statement loads the Agentize module, and the Agentize.agentize(User) statement generates the Storage module for the User struct.

Example

Using the Agentize experiment can save you a lot of time and effort when experimenting with Domain-Driven Design ideas in Elixir. With just a few lines of code, you can easily persist data for your struct in an Agent with a map, without having to set up a complex database system.

# start the User Agent
{:ok, user_agent} = User.Storage.start_link()

# add a new user
new_user = %User{name: "John", email: "john@example.com", age: 30}
User.Storage.put(:john, new_user)

# retrieve the user
user = User.Storage.get(:john)

IO.puts("User name: #{user.name}")
IO.puts("User email: #{user.email}")
IO.puts("User age: #{user.age}")

# delete the user
User.Storage.delete(:john)

Conclusion

In this post, we explored a simple experiment on Elixir metaprogramming called Agentize. We learned how to generate an Agent with a map for a given struct using Elixir macros. This experiment is a good example of how metaprogramming can be used to simplify common tasks in Elixir.

Reach the author at ro dot stevaux at gmail dot com

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment