katafrakt
Providing default implementations - a design problem with protocols
Hi, I’m contemplating writing an admin library, similar to Kaffy and LiveAdmin, but filling a few gaps found while using Kaffy. For that I figure I would leverage protocols instead of giant configs (LiveAdmin) or magic admin modules (Kaffy). But after some initial coding I see a few problems.
What I have right now is this:
defimpl Admin.Resource, for: App.Accounts.User do
def name(_), do: "User"
def slug(_), do: "user"
def form(user) do
import Admin.FormBuilder
new(user)
|> title(if user.id, do: "User #{user.id}", else: "New user")
|> field(:email)
|> field(:first_name)
|> field(:last_name)
|> build()
end
end
This is nice, but obviously the number of customizable things grows in time. Not only I want to have customizable form, but also records lists, their fields, custom actions, custom mass actions etc. On the other hand, I don’t want to force user to define all functions when they just may use defaults (for example, no need for custom slug or resource name).
So far I have two ideas and I want to know what you think:
1. Have a macro with default
defimpl Admin.Resource, for: App.Accounts.User do
# I don't want to customize slug and name
def form(user) do
# ...
end
use Admin.Resource.DefaultImplementations
end
This has a problem with either having the users remember to put it at the end of the module, so default implementation does not shadow custom implemementations - this is not POLS and what is usually done with use. Also, it generates warnings. Alternatively, there would have to be an option to what default implementations to use, for example:
defimpl Admin.Resource, for: App.Accounts.User do
use Admin.Resource.DefaultImplementations, except: [:form, :custom_actions]
end
Both are not really great in my opinion, I would especially hate generating many warnings for the user (so using import instead is not great too).
2. Use many small protocols
Alternatively, I could have a protocol for just about anything that is customizable and user would choose what to implement. So we will have Admin.Resource, Admin.ResourceTable.Columns, Admin.ResourceTable.Actions, Admin.ResourceTable.MassActions, Admin.ResourceTable.Query, Admin.ResourceTable.SearchQuery, Admin.Resource.FormCustomActions etc. Each one of these will probably will end up with just one function (call?). Seems sensible to some extent, but I feel that having to implement these many protocols might put people off.
Which solution appeals to you more? Or maybe there’s some alternative I missing? Or maybe using protocols for that wasn’t such a great idea…
Most Liked
al2o3cr
This sounds like the situation defoverridable is intended for.
zachallaun
Every time I’ve personally started off with a protocol for some generic piece of functionality, I’ve eventually switched to a behaviour. I believe they are both more explicit and more flexible, and lend themselves well to the defoverridable strategy that @al2o3cr mentioned.
defmodule AdminResources.User do
use Admin.Resource, schema: App.Accounts.User
# defines default impls for all required callbacks
# override where needed
@impl true
def whatever do
…
end
end
BartOtten
That was yesterday, today is a new day!
Which made you valuable for the community already. Take credit ![]()
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #javascript
- #code-sync
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








