katafrakt

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

al2o3cr

This sounds like the situation defoverridable is intended for.

zachallaun

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

BartOtten

That was yesterday, today is a new day!

Which made you valuable for the community already. Take credit :wink:

Last Post!

katafrakt

katafrakt

And that’s totally fine. I didn’t mean it to sound like there is a bug in Kaffy that is does not use LiveView. IMO it’s fine to have it or not have - just a different design decision.

I feel similar about this point - it’s a decision. It’s great to have an admin panel working basically OOTB after configuring it (I believe this is how it works in Django, for example). It’s also great (I think) to choose another approach, where you expect the user to set some minimal expectations upfront. In my experience you end up customizing everything anyway, but YMMV of course. This is the approach mostly influenced by Avo for Rails.

Where Next?

Popular in Questions Top

RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New

Other popular topics Top

KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36689 110
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 44608 311
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New

We're in Beta

About us Mission Statement