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 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.
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.
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.
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)
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