How to Delegate with Defdelegate
Let me know if this sounds familiar:
You start working on a project. You add a schema (let’s say User). You create a User context module around functionality for a User. Everything is going well.
Three months later, that User now has several schemas associated with it (Roles, Settings, etc.) and the User context is now 500 lines long. But all the functions seem to make sense here.
Hit to close to home?
Code without defdelegate
defmodule Example.Users do def get_user(id), do: #... def get_user_by_email(email), do: #... # 500 lines later def get_roles_for_user(user_id), do #... def get_settings_for_user(user_id), do #... #.... end
Other languages have techniques to handle this, but Elixir has a secret weapon we can wield to tame our context. That weapon is known as
defdelegate. By definition defdelegate “defines a function that delegates to another module.”
Now that we have our sword, let us go to war!
We will start by making two new modules and move the
get_settings_for_user to each one respectively.
defmodule Example.Users.Roles do def get_roles_for_user(user_id), do #... end
defmodule Example.Users.Settings do def get_settings_for_user(user_id), do #... end
Finally, we update our Users module, delegating the functions we moved using
defmodule Example.Users do def get_user(id), do: #... def get_user_by_email(email), do: #... defdelegate get_roles_for_user(user_id), to: Example.Users.Roles defdelegate get_settings_for_user(user_id), to: Example.Users.Settings #.... end
We have now cleaned up our
Example.Users module while keeping our API the same. No upstream changes to code are needed!
Using defdelegate to Refactor
defdelegate can be a great refactoring tool. But don’t overuse it. Focus its use on keeping your API consistent for you and your users. Sometimes it’s better to create a new module instead.