AttrAccessor
Ruby style accessors for Elixir structs.
Provides AttrAccessor module provides attr_reader, attr_writer and attr_accessor macros to define "get" and/or "set" functions for reading/write struct field values.
Benefits
- accessors are functions, so can be used in pipe chain
-
accessors are functions, so can be passed to
Enumetc higher order functions - opaque structs can more easily keep implementation details separate
Basic Usage
Say we've got an basic "todo" item struct define as:
defmodule Todo do
defstruct [:id, :title, :description, :created_at, :done_at]
def done?(todo) do
todo.done_at != nil
end
end
We can use AttrAccessor to create get/set functions.
defmodule Todo do
defstruct [:id, :title, :description, :created_at, :done_at]
import AttrAccessor
attr_accessor [:title, :description]
attr_reader [:id, :created_at]
attr_writer :done_at
def done?(todo) do
todo.done_at != nil
end
endThen we can use those accessor functions to read and write data on the struct value.
my_todo = %Todo{id: 123, created_at: DateTime.utc_now()}
Todo.id(my_todo)
# 123
my_todo = Todo.title("my todo item")
my_todo = Todo.description("do some stuff")
Todo.title(my_todo)
# "my todo item"
Todo.description(my_todo)
# "do some stuff"
Todo.done?(my_todo)
# false
my_todo = Todo.done_at(my_todo, DateTime.utc_now())
Todo.done?(my_todo)
# trueMapping update functions
AttrAccessor also creates a "bang" method for each attr_writer defined on a struct to allow the current value to be passed to a function, and the resulting value set.
my_todo = %Todo{} |> Todo.title("My todo")
# %Todo{title: "My todo"}
my_todo = my_todo |> Todo.title!(&String.upcase/1)
# %Todo{title: "MY TODO"}Pipe friendly
On it's own AttrAccessor may not provide much over built in syntax for accessing struct fields, but because they are regular Elixir functions they can be used with pipe chains to incrementally build a struct.
%Todo{id: 123, created_at: DateTime.utc_now()}
|> Todo.title("my todo item")
|> Todo.description("do some stuff")
|> Todo.done_at(DateTime.utc_now())Using as higher order functions
Because accessors are functions, they can also be used with Enum etc functions
todos = [
%Todo{title: "do thing 1"},
%Todo{title: "do thing 2"},
]
todo_titles = todos |> Enum.map(&Todo.title/1)
# ["do thing 1", "do thing 2"]
now = DateTime.utc_now()
completed_todos = todos |> Enum.map(&Todo.done_at(&1, now))
completed_todos |> Enum.map(&Todo.done?/1)
# [true, true]Installation
If available in Hex, the package can be installed
by adding attr_accessor to your list of dependencies in mix.exs:
def deps do
[
{:attr_accessor, "~> 0.1.0"}
]
endDocumentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/attr_accessor.