Skip to content

Commit 435d6cc

Browse files
committed
feat: allow custom field mappings
I would like to have some custom fields consistently through whole application and I didn't find any way how to currently do this. My reasoning is that writing AshBackpex resources should be as brief as possible and I really hate the idea of adding `module` to i.e. each field with date. Ideal solution would be to have :field_mappings in Application config which would be found by AshBackpex. But I suppose libraries shouldn't be based on anything in application so I'm introducing new "attribute" for resource named field_mappings working like this: field_mappings: %{ Ash.Type.UtcDatetime => MyappWeb.Backpex.Fields.RelativeDateTime, Ash.Type.UtcDatetimeUsec => MyappWeb.Backpex.Fields.RelativeDateTime, Ash.Type.DateTime => MyappWeb.Backpex.Fields.RelativeDateTime, Ash.Type.NaiveDateTime => MyappWeb.Backpex.Fields.RelativeDateTime } That way I can use my custom module with backpex macro which set this as default for all my admin pages. Simple test covering the functionality is included.
1 parent c890795 commit 435d6cc

4 files changed

Lines changed: 109 additions & 51 deletions

File tree

lib/ash_backpex/live_resource/dsl.ex

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,12 @@ defmodule AshBackpex.LiveResource.Dsl do
309309
doc:
310310
"Panels to be displayed in the admin create/edit forms. Format: [panel_key: \"Panel Title\"]"
311311
],
312+
field_mappings: [
313+
type: {:map, :atom, :atom},
314+
default: %{},
315+
doc:
316+
"Override default Ash type to Backpex field module mappings. Format: %{Ash.Type.DateTime => MyApp.Fields.CustomDateTime}"
317+
],
312318
pubsub: [
313319
doc: "PubSub configuration.",
314320
type: :keyword_list,

lib/ash_backpex/live_resource/transformers/generate_backpex.ex

Lines changed: 59 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -146,82 +146,90 @@ defmodule AshBackpex.LiveResource.Transformers.GenerateBackpex do
146146

147147
try_derive_module = fn attribute_name ->
148148
type = derive_type.(attribute_name)
149+
field_mappings = Spark.Dsl.Extension.get_opt(__MODULE__, [:backpex], :field_mappings) || %{}
149150

150-
case type do
151-
Ash.Type.Boolean ->
152-
Backpex.Fields.Boolean
151+
# Check custom mappings first, then fall back to defaults
152+
case Map.get(field_mappings, type) do
153+
nil ->
154+
case type do
155+
Ash.Type.Boolean ->
156+
Backpex.Fields.Boolean
153157

154-
Ash.Type.String ->
155-
attribute_name |> select_or.(Backpex.Fields.Text)
158+
Ash.Type.String ->
159+
attribute_name |> select_or.(Backpex.Fields.Text)
156160

157-
Ash.Type.Atom ->
158-
attribute_name |> select_or.(Backpex.Fields.Text)
161+
Ash.Type.Atom ->
162+
attribute_name |> select_or.(Backpex.Fields.Text)
159163

160-
Ash.Type.CiString ->
161-
attribute_name |> select_or.(Backpex.Fields.Text)
164+
Ash.Type.CiString ->
165+
attribute_name |> select_or.(Backpex.Fields.Text)
162166

163-
Ash.Type.Time ->
164-
Backpex.Fields.Time
167+
Ash.Type.Time ->
168+
Backpex.Fields.Time
165169

166-
Ash.Type.Date ->
167-
Backpex.Fields.Date
170+
Ash.Type.Date ->
171+
Backpex.Fields.Date
168172

169-
Ash.Type.UtcDatetime ->
170-
Backpex.Fields.DateTime
173+
Ash.Type.UtcDatetime ->
174+
Backpex.Fields.DateTime
171175

172-
Ash.Type.UtcDatetimeUsec ->
173-
Backpex.Fields.DateTime
176+
Ash.Type.UtcDatetimeUsec ->
177+
Backpex.Fields.DateTime
174178

175-
Ash.Type.DateTime ->
176-
Backpex.Fields.DateTime
179+
Ash.Type.DateTime ->
180+
Backpex.Fields.DateTime
177181

178-
Ash.Type.NaiveDateTime ->
179-
Backpex.Fields.DateTime
182+
Ash.Type.NaiveDateTime ->
183+
Backpex.Fields.DateTime
180184

181-
Ash.Type.Integer ->
182-
attribute_name |> select_or.(Backpex.Fields.Number)
185+
Ash.Type.Integer ->
186+
attribute_name |> select_or.(Backpex.Fields.Number)
183187

184-
Ash.Type.Float ->
185-
attribute_name |> select_or.(Backpex.Fields.Number)
188+
Ash.Type.Float ->
189+
attribute_name |> select_or.(Backpex.Fields.Number)
186190

187-
:belongs_to ->
188-
Backpex.Fields.BelongsTo
191+
:belongs_to ->
192+
Backpex.Fields.BelongsTo
189193

190-
:has_many ->
191-
Backpex.Fields.HasMany
194+
:has_many ->
195+
Backpex.Fields.HasMany
192196

193-
:count ->
194-
Backpex.Fields.Number
197+
:count ->
198+
Backpex.Fields.Number
195199

196-
:exists ->
197-
Backpex.Fields.Boolean
200+
:exists ->
201+
Backpex.Fields.Boolean
198202

199-
:sum ->
200-
Backpex.Fields.Number
203+
:sum ->
204+
Backpex.Fields.Number
201205

202-
:max ->
203-
Backpex.Fields.Number
206+
:max ->
207+
Backpex.Fields.Number
204208

205-
:min ->
206-
Backpex.Fields.Number
209+
:min ->
210+
Backpex.Fields.Number
207211

208-
:avg ->
209-
Backpex.Fields.Number
212+
:avg ->
213+
Backpex.Fields.Number
210214

211-
{:array, Ash.Type.Atom} ->
212-
attribute_name |> multiselect_or.(Backpex.Fields.Text)
215+
{:array, Ash.Type.Atom} ->
216+
attribute_name |> multiselect_or.(Backpex.Fields.Text)
213217

214-
{:array, Ash.Type.String} ->
215-
attribute_name |> multiselect_or.(Backpex.Fields.Text)
218+
{:array, Ash.Type.String} ->
219+
attribute_name |> multiselect_or.(Backpex.Fields.Text)
216220

217-
{:array, Ash.Type.CiString} ->
218-
attribute_name |> multiselect_or.(Backpex.Fields.Text)
221+
{:array, Ash.Type.CiString} ->
222+
attribute_name |> multiselect_or.(Backpex.Fields.Text)
219223

220-
{:array, Ash.Type.Integer} ->
221-
attribute_name |> multiselect_or.(Backpex.Fields.Number)
224+
{:array, Ash.Type.Integer} ->
225+
attribute_name |> multiselect_or.(Backpex.Fields.Number)
226+
227+
{:array, Ash.Type.Float} ->
228+
attribute_name |> multiselect_or.(Backpex.Fields.Number)
229+
end
222230

223-
{:array, Ash.Type.Float} ->
224-
attribute_name |> multiselect_or.(Backpex.Fields.Number)
231+
custom_module ->
232+
custom_module
225233
end
226234
end
227235

test/ash_backpex/live_resource/transformer_test.exs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,16 @@ defmodule AshBackpex.LiveResource.TransformerTest do
103103
assert Keyword.get(fields, :author).module == Backpex.Fields.BelongsTo
104104
end
105105

106+
test "derive custom Backpex field types with field overrides" do
107+
fields = TestCustomFieldMappingsLive.fields()
108+
109+
# this one is standard and not overridden
110+
assert Keyword.get(fields, :title).module == Backpex.Fields.Text
111+
112+
# this one would be Ash.Type.DateTime if not overridden
113+
assert Keyword.get(fields, :published_at).module == Demo.Backpex.Fields.CustomDateTime
114+
end
115+
106116
test "derive default and non-default primary key with init_order" do
107117
assert TestPostLive.config(:primary_key) == :id
108118

test/support/test_live.ex

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,37 @@ defmodule TestNonDefaultPrimaryKeyNameLive do
170170
end
171171
end
172172
end
173+
174+
defmodule Demo.Backpex.Fields.CustomDateTime do
175+
@moduledoc "Dummy field module for testing :field_mappings config override"
176+
use Backpex.Field
177+
178+
@impl Backpex.Field
179+
def render_value(assigns) do
180+
~H"<span>{@value}</span>"
181+
end
182+
183+
@impl Backpex.Field
184+
def render_form(assigns) do
185+
~H"""
186+
<input type="datetime-local" />
187+
"""
188+
end
189+
end
190+
191+
defmodule TestCustomFieldMappingsLive do
192+
@moduledoc false
193+
use AshBackpex.LiveResource
194+
195+
backpex do
196+
resource(AshBackpex.TestDomain.Post)
197+
layout({TestLayout, :admin})
198+
199+
field_mappings(%{Ash.Type.DateTime => Demo.Backpex.Fields.CustomDateTime})
200+
201+
fields do
202+
field(:title)
203+
field(:published_at)
204+
end
205+
end
206+
end

0 commit comments

Comments
 (0)