Protocols help you achieve the Open/Closed Principle in Elixir
Open for extension
Closed for modification
Protocols are a mechanism to achieve polymorphism in Elixir. Dispatching on a protocol is available to any data type as long as it implements the protocol. - Elixir Protocol Guide
defprotocolSizedo@doc"Calculates the size (and not the length!) of a data structure"defsize(data)
enddefimplSize, for: BitString dodefsize(string), do: byte_size(string)
enddefimplSize, for: Map dodefsize(map), do: map_size(map)
enddefimplSize, for: Tuple dodefsize(tuple), do: tuple_size(tuple)
end
defmoduleSizedo@doc"Calculates the size (and not the length!) of a data structure"defsize(string) when is_binary(string), do: byte_size(string)
defsize(map) when is_map(map), do: map_size(map)
defsize(tuple) when is_tuple(tuple), do: tuple_size(tuple)
end
We can extend the rendering power of Phoenix by leveraging its Phoenix.HTML.Safe Protocol
defmoduleBlog.Markdowndo
defstruct text:""defto_html(%__MODULE__{text: text}) when is_binary(text) do
Cmark.to_html(binary)
enddefimplPhoenix.HTML.Safedo# Implement the protocoldefto_iodata(%Blog.Markdown{} = markdown) do
Blog.Markdown.to_html(markdown)
endendend
post = put_in(post.body, Markdown.new(post.body))
Phoenix.View.render(
Blog.Web.PostView,
"show.html",
post: post
)
Lets make %Markdown{} implement the Ecto.Type Behaviour
Type is the backing type of our Markdown field, which is :string
Load takes data from the database, converts it to %Markdown{}
Dump takes a %Markdown{} struct, validates it, and returns a valid :string
Cast is called when casting values for Ecto.Changeset or Ecto.Query.
defmoduleBlog.Postdouse Blog.Web, :model
schema "posts"do
field :title, :string
field :author, :string
field :body, Blog.Markdown # The custom Ecto.Typeendend
defmoduleBlog.Markdowndo@behaviour Ecto.Type
deftype, do::stringdefcast(binary) when is_binary(binary) do
{:ok, %Markdown{text: binary}}
enddefload(binary) when is_binary(binary) do
{:ok, %Markdown{text: binary}}
enddefdump(%Markdown{text: binary}) when is_binary(binary) do
{:ok, binary}
endend
Now Post.body is always a %Markdown{}
post = Repo.get!(Post, 1)
true = match?(%Markdown{}, post.body)
# No longer needed# post = put_in(post.body, Markdown.new(post.body))
Phoenix.View.render(
Blog.Web.PostView,
"show.html",
post: post
)
Now we have
Built a basic blog with Markdown support
Simplified our templates by leveraging the Phoenix.HTML.Safe Protocol
Automatically casted Markdown fields at the database level with Behaviours