AttrAccessor

View Source

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 Enum etc 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
end

Then 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)
# true

Mapping 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"}
  ]
end

Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/attr_accessor.