From fa4bbd944c23b296a3c080f47fdaf942e5334b78 Mon Sep 17 00:00:00 2001 From: thanos Date: Wed, 1 Oct 2025 06:06:01 -0400 Subject: [PATCH 01/38] added the handling for embeds_one to the InlineCRUD field --- lib/backpex/fields/inline_crud.ex | 63 +++++++++++++++++-------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index 9fca07f26..853540aa1 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -2,7 +2,7 @@ defmodule Backpex.Fields.InlineCRUD do @config_schema [ type: [ doc: "The type of the field.", - type: {:in, [:embed, :assoc]}, + type: {:in, [:embed, :assoc, :embed_one]}, required: true ], child_fields: [ @@ -32,7 +32,7 @@ defmodule Backpex.Fields.InlineCRUD do > > Everything is currently handled by plain text input. - ### EmbedsMany + ### EmbedsMany and EmbedsOne The field in the migration must be of type `:map`. You also need to use ecto's `cast_embed/2` in the changeset. @@ -97,6 +97,10 @@ defmodule Backpex.Fields.InlineCRUD do @impl Backpex.Field def render_value(assigns) do + assigns = + assigns + |> assign(:value, if(assigns[:field_options].type == :embed_one, do: [assigns[:value]], else: assigns[:value])) + ~H"""
@@ -157,34 +161,37 @@ defmodule Backpex.Fields.InlineCRUD do phx-throttle={Backpex.Field.throttle(child_field_options, assigns)} /> - -
- -
+ <%= if @field_options.type != :embed_one do %> +
+ +
+ <% end %> - - + <%= if @field_options.type != :embed_one do %> + + <% end %> - - + <%= if @field_options.type != :embed_one do %> + + <% end %> <%= if help_text = Backpex.Field.help_text(@field_options, assigns) do %> {help_text} <% end %> @@ -195,7 +202,7 @@ defmodule Backpex.Fields.InlineCRUD do @impl Backpex.Field def association?({_name, %{type: :assoc}} = _field), do: true - def association?({_name, %{type: :embed}} = _field), do: false + def association?({_name, %{type: _}} = _field), do: false @impl Backpex.Field def schema({name, _field_options}, schema) do From 0e1c9e399d3005dd45ac990bcb7c0ea767ae0811 Mon Sep 17 00:00:00 2001 From: thanos Date: Sat, 18 Oct 2025 09:25:00 -0400 Subject: [PATCH 02/38] added demo based on producst as discussed in #1583 --- .tool-versions | 2 -- demo/lib/demo/ecto_factory.ex | 4 ++++ demo/lib/demo/product.ex | 14 ++++++++++++ demo/lib/demo_web/live/product_live.ex | 19 ++++++++++++++++ .../20251018130237_products_add_more_info.exs | 9 ++++++++ lib/backpex/fields/inline_crud.ex | 22 ++++++++++++++----- 6 files changed, 62 insertions(+), 8 deletions(-) delete mode 100644 .tool-versions create mode 100644 demo/priv/repo/migrations/20251018130237_products_add_more_info.exs diff --git a/.tool-versions b/.tool-versions deleted file mode 100644 index 0583ee187..000000000 --- a/.tool-versions +++ /dev/null @@ -1,2 +0,0 @@ -erlang 28.0.4 -elixir 1.18.4 diff --git a/demo/lib/demo/ecto_factory.ex b/demo/lib/demo/ecto_factory.ex index c06a08114..38dff94eb 100644 --- a/demo/lib/demo/ecto_factory.ex +++ b/demo/lib/demo/ecto_factory.ex @@ -61,6 +61,10 @@ defmodule Demo.EctoFactory do quantity: Enum.random(0..1_000), manufacturer: "https://example.com/", price: Enum.random(50..5_000_000), + more_info: %{ + weight: Enum.random(1..100), + goes_well_with: Faker.Food.description() + }, suppliers: build_list(Enum.random(0..5), :supplier), short_links: build_list(Enum.random(0..5), :short_link) } diff --git a/demo/lib/demo/product.ex b/demo/lib/demo/product.ex index 325c577d9..85dddc93d 100644 --- a/demo/lib/demo/product.ex +++ b/demo/lib/demo/product.ex @@ -19,6 +19,11 @@ defmodule Demo.Product do currency: :EUR, opts: [separator: ".", delimiter: ",", symbol_on_right: true, symbol_space: true] + embeds_one :more_info, MoreInfo do + field :weight, :integer + field :goes_well_with, :string + end + has_many :suppliers, Supplier, on_replace: :delete, on_delete: :delete_all has_many :short_links, ShortLink, on_replace: :delete, on_delete: :delete_all, foreign_key: :product_id @@ -31,6 +36,9 @@ defmodule Demo.Product do def changeset(product, attrs, _metadata \\ []) do product |> cast(attrs, @required_fields ++ @optional_fields) + |> cast_embed(:more_info, + with: &more_info_changeset/2 + ) |> cast_assoc(:suppliers, with: &Demo.Supplier.changeset/2, sort_param: :suppliers_order, @@ -44,4 +52,10 @@ defmodule Demo.Product do |> validate_required(@required_fields) |> validate_length(:images, max: 2) end + + def more_info_changeset(more_info, attrs) do + more_info + |> cast(attrs, [ :weight, :goes_well_with]) + |> validate_required([:weight]) + end end diff --git a/demo/lib/demo_web/live/product_live.ex b/demo/lib/demo_web/live/product_live.ex index 1465bceb3..481684e34 100644 --- a/demo/lib/demo_web/live/product_live.ex +++ b/demo/lib/demo_web/live/product_live.ex @@ -85,6 +85,25 @@ defmodule DemoWeb.ProductLive do label: "Price", align: :right }, + more_info: %{ + module: Backpex.Fields.InlineCRUD, + label: "More Info", + type: :embed_one, + except: [:index], + child_fields: [ + + weight: %{ + module: Backpex.Fields.Text, + label: "Ave. Weight (kg)" + }, + goes_well_with: %{ + module: Backpex.Fields.Textarea, + label: "Goes well with", + input_type: :textarea, + rows: 5 + } + ], + }, suppliers: %{ module: Backpex.Fields.InlineCRUD, label: "Suppliers", diff --git a/demo/priv/repo/migrations/20251018130237_products_add_more_info.exs b/demo/priv/repo/migrations/20251018130237_products_add_more_info.exs new file mode 100644 index 000000000..0669b52d0 --- /dev/null +++ b/demo/priv/repo/migrations/20251018130237_products_add_more_info.exs @@ -0,0 +1,9 @@ +defmodule Demo.Repo.Migrations.ProductsAddMoreInfo do + use Ecto.Migration + + def change do + alter table(:products) do + add :more_info, :map + end + end +end diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index 853540aa1..48a7bafd5 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -1,7 +1,7 @@ defmodule Backpex.Fields.InlineCRUD do @config_schema [ type: [ - doc: "The type of the field.", + doc: "The type of the field. One of `:embed`, `:embed_one` or `:assoc`.", type: {:in, [:embed, :assoc, :embed_one]}, required: true ], @@ -20,7 +20,7 @@ defmodule Backpex.Fields.InlineCRUD do ] @moduledoc """ - A field to handle inline CRUD operations. It can be used with either an `embeds_many` or `has_many` (association) type column. + A field to handle inline CRUD operations. It can be used with either an `embeds_many`, `embeds_one`, or `has_many` (association) type column. ## Field-specific options @@ -43,8 +43,8 @@ defmodule Backpex.Fields.InlineCRUD do ... |> cast_embed(:your_field, with: &your_field_changeset/2, - sort_param: :your_field_order, - drop_param: :your_field_delete + sort_param: :your_field_order, # not required for embeds_one + drop_param: :your_field_delete # not required for embeds_one ) ... end @@ -99,7 +99,10 @@ defmodule Backpex.Fields.InlineCRUD do def render_value(assigns) do assigns = assigns - |> assign(:value, if(assigns[:field_options].type == :embed_one, do: [assigns[:value]], else: assigns[:value])) + |> assign( + :value, + if(assigns[:field_options].type == :embed_one, do: [get_value(assigns, :value)], else: assigns[:value]) + ) ~H"""
@@ -202,7 +205,7 @@ defmodule Backpex.Fields.InlineCRUD do @impl Backpex.Field def association?({_name, %{type: :assoc}} = _field), do: true - def association?({_name, %{type: _}} = _field), do: false + def association?({_name, %{type: _type}} = _field), do: false @impl Backpex.Field def schema({name, _field_options}, schema) do @@ -218,4 +221,11 @@ defmodule Backpex.Fields.InlineCRUD do do: input_type defp input_type(_child_field_options), do: :text + + defp get_value(assigns, field) do + case Map.get(assigns, field, %{}) do + nil -> %{} + value -> value + end + end end From 6b6f4f9de460dbe9629372a48fe71f565c3c7a5f Mon Sep 17 00:00:00 2001 From: thanos Date: Sat, 18 Oct 2025 09:54:36 -0400 Subject: [PATCH 03/38] fixed format --- .tool-versions | 2 +- demo/lib/demo/product.ex | 4 ++-- demo/lib/demo_web/live/product_live.ex | 33 +++++++++++++------------- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/.tool-versions b/.tool-versions index b2f3ccbc5..bf44d4afd 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ erlang 28.1 -elixir 1.18.4 +elixir 1.18.4-otp-28 diff --git a/demo/lib/demo/product.ex b/demo/lib/demo/product.ex index 1202d8785..cb4d9bc6a 100644 --- a/demo/lib/demo/product.ex +++ b/demo/lib/demo/product.ex @@ -53,7 +53,7 @@ defmodule Demo.Product do def more_info_changeset(more_info, attrs) do more_info - |> cast(attrs, [ :weight, :goes_well_with]) + |> cast(attrs, [:weight, :goes_well_with]) |> validate_required([:weight]) - end + end end diff --git a/demo/lib/demo_web/live/product_live.ex b/demo/lib/demo_web/live/product_live.ex index 2007f2261..38c8ef431 100644 --- a/demo/lib/demo_web/live/product_live.ex +++ b/demo/lib/demo_web/live/product_live.ex @@ -86,23 +86,22 @@ defmodule DemoWeb.ProductLive do align: :right }, more_info: %{ - module: Backpex.Fields.InlineCRUD, - label: "More Info", - type: :embed_one, - except: [:index], - child_fields: [ - - weight: %{ - module: Backpex.Fields.Text, - label: "Ave. Weight (kg)" - }, - goes_well_with: %{ - module: Backpex.Fields.Textarea, - label: "Goes well with", - input_type: :textarea, - rows: 5 - } - ], + module: Backpex.Fields.InlineCRUD, + label: "More Info", + type: :embed_one, + except: [:index], + child_fields: [ + weight: %{ + module: Backpex.Fields.Text, + label: "Ave. Weight (kg)" + }, + goes_well_with: %{ + module: Backpex.Fields.Textarea, + label: "Goes well with", + input_type: :textarea, + rows: 5 + } + ] }, suppliers: %{ module: Backpex.Fields.InlineCRUD, From 5fe0ef7c42b28a62e9e640d222c548af4261d093 Mon Sep 17 00:00:00 2001 From: thanos Date: Sun, 19 Oct 2025 21:54:41 -0400 Subject: [PATCH 04/38] Revert .tool-versions change --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index bf44d4afd..b2f3ccbc5 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ erlang 28.1 -elixir 1.18.4-otp-28 +elixir 1.18.4 From f020f744694acccbef6e9d166d59e56d8c7cdbc0 Mon Sep 17 00:00:00 2001 From: thanos Date: Sun, 19 Oct 2025 23:35:43 -0400 Subject: [PATCH 05/38] adding :map to Backpex.Fields.InlineCRUD and a polymorphic example to demo --- demo/lib/demo/ecto_factory.ex | 14 ++ demo/lib/demo/entity.ex | 23 ++ .../components/layouts/admin.html.heex | 12 + demo/lib/demo_web/live/car_live.ex | 90 +++++++ demo/lib/demo_web/live/entity_live.ex | 54 +++++ demo/lib/demo_web/live/person_live.ex | 0 .../20251020033241_create_entity.exs | 12 + lib/backpex/fields/inline_crud.ex | 228 +++++++++++++++--- 8 files changed, 394 insertions(+), 39 deletions(-) create mode 100644 demo/lib/demo/entity.ex create mode 100644 demo/lib/demo_web/live/car_live.ex create mode 100644 demo/lib/demo_web/live/entity_live.ex create mode 100644 demo/lib/demo_web/live/person_live.ex create mode 100644 demo/priv/repo/migrations/20251020033241_create_entity.exs diff --git a/demo/lib/demo/ecto_factory.ex b/demo/lib/demo/ecto_factory.ex index 38dff94eb..2d233e388 100644 --- a/demo/lib/demo/ecto_factory.ex +++ b/demo/lib/demo/ecto_factory.ex @@ -106,4 +106,18 @@ defmodule Demo.EctoFactory do %{label: Enum.at(labels, index), url: "https://example.com/"} end end + + def entities_factory do + Demo.Repo.insert(%Demo.Entity{ + identity: "René Descartes", + type: "person", + fields: %{"email" => "descartes@philosophers.fr", "phone" => "+33-14325-7123", "age" => 53} + }) + + Demo.Repo.insert(%Demo.Entity{ + identity: "BMW 320i", + type: "car", + fields: %{"engine_size" => 2000, "colour" => "Metalic Sliver", "year" => 1984} + }) + end end diff --git a/demo/lib/demo/entity.ex b/demo/lib/demo/entity.ex new file mode 100644 index 000000000..26a3b886e --- /dev/null +++ b/demo/lib/demo/entity.ex @@ -0,0 +1,23 @@ +defmodule Demo.Entity do + @moduledoc false + use Ecto.Schema + + import Ecto.Changeset + + @primary_key {:id, :binary_id, autogenerate: true} + schema "entities" do + field :identity, :string + field :type, :string + field :fields, :map, default: %{} + + timestamps() + end + + @required_fields [:identity, :type, :fields] + @doc false + def changeset(address, attrs, _metadata \\ []) do + address + |> cast(attrs, @required_fields) + |> validate_required(@required_fields) + end +end diff --git a/demo/lib/demo_web/components/layouts/admin.html.heex b/demo/lib/demo_web/components/layouts/admin.html.heex index 048401a82..6d8f331d4 100644 --- a/demo/lib/demo_web/components/layouts/admin.html.heex +++ b/demo/lib/demo_web/components/layouts/admin.html.heex @@ -92,6 +92,18 @@ + + <:label>Polymorphic Example + + Entities + + + Persons + + + Cars + + {render_slot(@inner_block)} diff --git a/demo/lib/demo_web/live/car_live.ex b/demo/lib/demo_web/live/car_live.ex new file mode 100644 index 000000000..0a3eb7896 --- /dev/null +++ b/demo/lib/demo_web/live/car_live.ex @@ -0,0 +1,90 @@ +defmodule DemoWeb.CarLive do + use Backpex.LiveResource, + adapter_config: [ + schema: Demo.Entity, + repo: Demo.Repo, + update_changeset: &__MODULE__.changeset/3, + create_changeset: &__MODULE__.changeset/3, + item_query: &__MODULE__.item_query/3 + ], + layout: {DemoWeb.Layouts, :admin}, + fluid?: true + + @moduledoc """ + This module is similar to `PersonLive` in that it demos the use of a InlineCRUD set for type `:map`. + It also demos how you can use the power of `Backpex` to do a poor man's polymorphism by using `Backpex.LiveResource` + to control the values entered into the generic `:map` field while reusing the same schema + for different entity types. + + It also is an example of how you could validate the values in the `:map` field by using a `validate` + function and calling `Backpex.Fields.InlineCRUD.changeset` inside the `changeset` function. + """ + + import Ecto.Query, only: [where: 3] + + @impl Backpex.LiveResource + def singular_name, do: "Car" + + @impl Backpex.LiveResource + def plural_name, do: "Cars" + + # + # Added the item_query function to filter entities by type + # + def item_query(query, _, _assigns) do + query + |> where([entity], entity.type == "car") + end + + # + # Added the changeset function to create a new entity with type "car", + # and validate the child fields in the entity + # + def changeset(entity, params, metadata \\ []) do + entity + |> Demo.Entity.changeset(params |> Map.put("type", "car")) + |> Backpex.Fields.InlineCRUD.changeset(:fields, metadata) + end + + @impl Backpex.LiveResource + def fields do + [ + identity: %{ + module: Backpex.Fields.Text, + label: "Model", + searchable: true + }, + fields: %{ + module: Backpex.Fields.InlineCRUD, + label: "Information", + type: :map, + except: [:index], + child_fields: [ + engine_size: %{ + module: Backpex.Fields.Text, + label: "Engine Size (cc)", + input_type: :integer + }, + colour: %{ + module: Backpex.Fields.Text, + label: "Colour" + }, + year: %{ + module: Backpex.Fields.Text, + label: "Year", + input_type: :integer + } + ], + validate: fn changeset -> + changeset + |> Ecto.Changeset.validate_required([:colour, :year]) + |> Ecto.Changeset.validate_number(:year, + greater_than: 1900, + less_than: Date.utc_today().year, + message: "must be between 1900 and #{Date.utc_today().year}" + ) + end + } + ] + end +end diff --git a/demo/lib/demo_web/live/entity_live.ex b/demo/lib/demo_web/live/entity_live.ex new file mode 100644 index 000000000..2c0fef296 --- /dev/null +++ b/demo/lib/demo_web/live/entity_live.ex @@ -0,0 +1,54 @@ +defmodule DemoWeb.EntityLive do + use Backpex.LiveResource, + adapter_config: [ + schema: Demo.Entity, + repo: Demo.Repo, + update_changeset: &Demo.Entity.changeset/3, + create_changeset: &Demo.Entity.changeset/3 + ], + layout: {DemoWeb.Layouts, :admin}, + fluid?: true + + @impl Backpex.LiveResource + def singular_name, do: "Entity" + + @impl Backpex.LiveResource + def plural_name, do: "Entities" + + @impl Backpex.LiveResource + def item_actions(default_actions) do + default_actions + |> Keyword.delete(:delete) + end + + @impl Backpex.LiveResource + def can?(_assigns, :index, _item), do: true + + @impl Backpex.LiveResource + def can?(_assigns, _action, _item), do: false + + @impl Backpex.LiveResource + def fields do + [ + identity: %{ + module: Backpex.Fields.Text, + label: "Identity", + searchable: true + }, + type: %{ + module: Backpex.Fields.Text, + label: "Type", + searchable: true, + readonly: true + }, + fields: %{ + module: Backpex.Fields.Textarea, + rows: 10, + label: "Fields", + searchable: true, + readonly: true, + except: [:index] + } + ] + end +end diff --git a/demo/lib/demo_web/live/person_live.ex b/demo/lib/demo_web/live/person_live.ex new file mode 100644 index 000000000..e69de29bb diff --git a/demo/priv/repo/migrations/20251020033241_create_entity.exs b/demo/priv/repo/migrations/20251020033241_create_entity.exs new file mode 100644 index 000000000..1252f0f73 --- /dev/null +++ b/demo/priv/repo/migrations/20251020033241_create_entity.exs @@ -0,0 +1,12 @@ +defmodule Demo.Repo.Migrations.CreateEntities do + use Ecto.Migration + + def change do + create table(:entities) do + add :identity, :string, null: false + add :type, :string, null: false + add :fields, :map + timestamps() + end + end +end diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index 48a7bafd5..593cea9cc 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -1,8 +1,8 @@ defmodule Backpex.Fields.InlineCRUD do @config_schema [ type: [ - doc: "The type of the field. One of `:embed`, `:embed_one` or `:assoc`.", - type: {:in, [:embed, :assoc, :embed_one]}, + doc: "The type of the field. One of `:embed`, `:embed_one` or `:assoc` or `:map`.", + type: {:in, [:embed, :assoc, :embed_one, :map]}, required: true ], child_fields: [ @@ -16,11 +16,19 @@ defmodule Backpex.Fields.InlineCRUD do """, type: :keyword_list, required: true + ], + validate: [ + doc: """ + An optional validation function used to validate `:map` child fields. It takes the changeset + and returns a changeset. You can use it to validate the `child_fields` + of a `map`, see the examples . + """, + type: {:fun, 1} ] ] @moduledoc """ - A field to handle inline CRUD operations. It can be used with either an `embeds_many`, `embeds_one`, or `has_many` (association) type column. + A field to handle inline CRUD operations. It can be used with with columns of type `map`, `embeds_many`, `embeds_one`, or `has_many` (for associations). ## Field-specific options @@ -84,7 +92,59 @@ defmodule Backpex.Fields.InlineCRUD do } ] end + ### Map + + By using the `:map` type you can use the `InlineCRUD` to control waht fields can be stored in a `map`. + With the `:map` type the InlineCRUD uses the `input_type` option to build out the `types` map needed + to create a changeset. If the `input_type` is not set, it defaults to `:string`. + Another option is the `validate` callback that you can add to `fields` settings to + process the `child_fields` as in the example below. + + @impl Backpex.LiveResource + def fields do + [ + model: %{ + module: Backpex.Fields.Text, + label: "Model", + searchable: true + }, + info: %{ + module: Backpex.Fields.InlineCRUD, + label: "Information", + type: :map, + except: [:index], + child_fields: [ + engine_size: %{ + module: Backpex.Fields.Text, + label: "Engine Size (cc)", + input_type: :integer + }, + colour: %{ + module: Backpex.Fields.Text, + label: "Colour" + }, + year: %{ + module: Backpex.Fields.Text, + label: "Year", + input_type: :integer + } + ], + validate: fn changeset-> + changeset + |> Ecto.Changeset.validate_required([:colour, :year]) + |> Ecto.Changeset.validate_number(:year, + greater_than: 1900, + less_than: Date.utc_today().year + 1, + message: "must be between 1900 and #{Date.utc_today().year + 1}" + ) + end + } + ] + end + + Then use the function `Backpex.Fields.InlineCRUD.changeset` in your schema's changeset to invoke the `validate` function. """ + use Backpex.Field, config_schema: @config_schema require Backpex @@ -101,7 +161,7 @@ defmodule Backpex.Fields.InlineCRUD do assigns |> assign( :value, - if(assigns[:field_options].type == :embed_one, do: [get_value(assigns, :value)], else: assigns[:value]) + if(assigns[:field_options].type in [:embed_one, :map], do: [get_value(assigns, :value)], else: assigns[:value]) ) ~H""" @@ -140,64 +200,92 @@ defmodule Backpex.Fields.InlineCRUD do
- <.inputs_for :let={f_nested} field={@form[@name]}> - + <%= if @field_options.type != :map do %> + <.inputs_for :let={f_nested} field={@form[@name]}> + +
+
+ + {child_field_options.label} + + Atom.to_string()} + field={f_nested[child_field_name]} + aria-labelledby={"inline-crud-header-label-#{@name}-#{child_field_name} inline-crud-label-#{@name}"} + translate_error_fun={Backpex.Field.translate_error_fun(child_field_options, assigns)} + phx-debounce={Backpex.Field.debounce(child_field_options, assigns)} + phx-throttle={Backpex.Field.throttle(child_field_options, assigns)} + /> +
+ <%= if @field_options.type != :embed_one do %> +
+ +
+ <% end %> +
+ + <%= if @field_options.type != :embed_one do %> + + <% end %> + <% else %>
{child_field_options.label} Atom.to_string()} - field={f_nested[child_field_name]} + name={"change[#{Atom.to_string(@name)}][#{Atom.to_string(child_field_name)}]"} + value={changeset_value(assigns, child_field_name)} + errors={changeset_errors(assigns, child_field_name)} aria-labelledby={"inline-crud-header-label-#{@name}-#{child_field_name} inline-crud-label-#{@name}"} translate_error_fun={Backpex.Field.translate_error_fun(child_field_options, assigns)} phx-debounce={Backpex.Field.debounce(child_field_options, assigns)} phx-throttle={Backpex.Field.throttle(child_field_options, assigns)} />
- <%= if @field_options.type != :embed_one do %> -
- -
- <% end %>
- - <%= if @field_options.type != :embed_one do %> - + <% end %> + <%= if @field_options.type in [:emded, :assoc] do %> + + <% end %> + + <%= if help_text = Backpex.Field.help_text(@field_options, assigns) do %> + {help_text} <% end %>
- <%= if @field_options.type != :embed_one do %> - - <% end %> - <%= if help_text = Backpex.Field.help_text(@field_options, assigns) do %> - {help_text} - <% end %>
""" @@ -228,4 +316,66 @@ defmodule Backpex.Fields.InlineCRUD do value -> value end end + + @doc """ + Use this function to create a changeset for your `:map` within + your schema's changeset function. Then you can use the `changeset` + it returns for the `:map` to validate your map fields in detail. + + ## Parameters + + * `form_changeset`: The `changeset` of your `schema` + * `map_field`: The name of the map field in your schema + * `metadata`: The `Backpex` metadata. + + """ + def changeset(form_changeset, map_field, metadata) do + field_info = get_in(metadata, [:assigns, :fields, map_field]) + + if is_nil(field_info) do + form_changeset + else + types = + field_info[:child_fields] + |> Map.new(fn {k, settings} -> + {k, settings |> Map.get(:input_type, :string)} + end) + + case field_info[:validate] do + nil -> + form_changeset + + validator -> + fields_changeset = + {%{}, types} + |> Ecto.Changeset.cast(Ecto.Changeset.get_field(form_changeset, map_field), Map.keys(types)) + |> validator.() + + fields_changeset.errors + |> Enum.reduce(form_changeset, fn {map_field, {msg, _opts}}, form_changeset -> + Ecto.Changeset.add_error(form_changeset, map_field, msg) + end) + |> Ecto.Changeset.put_change( + map_field, + Ecto.Changeset.apply_changes(fields_changeset) + |> Map.new(fn {k, v} -> {Atom.to_string(k), v} end) + ) + end + end + end + + defp changeset_value(assigns, field) when is_atom(field), do: changeset_value(assigns, Atom.to_string(field)) + + defp changeset_value(assigns, field) when is_binary(field) do + Ecto.Changeset.get_field(assigns.changeset, assigns.name) + |> Map.get(field) + end + + defp changeset_errors(assigns, field) when is_binary(field), + do: changeset_errors(assigns, String.to_existing_atom(field)) + + defp changeset_errors(assigns, field) when is_atom(field) do + for {^field, {error, _opts}} <- assigns.changeset.errors, + do: error + end end From aa2c440459ab1f71b11492e57ed60d6bff4c9b75 Mon Sep 17 00:00:00 2001 From: thanos Date: Sun, 19 Oct 2025 23:38:20 -0400 Subject: [PATCH 06/38] adding :map to Backpex.Fields.InlineCRUD and a polymorphic example to demo --- demo/lib/demo_web/live/person_live.ex | 79 +++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/demo/lib/demo_web/live/person_live.ex b/demo/lib/demo_web/live/person_live.ex index e69de29bb..2c11ca60e 100644 --- a/demo/lib/demo_web/live/person_live.ex +++ b/demo/lib/demo_web/live/person_live.ex @@ -0,0 +1,79 @@ +defmodule DemoWeb.PersonLive do +alias Backpex.Fields.InlineCRUD + use Backpex.LiveResource, + adapter_config: [ + schema: Demo.Entity, + repo: Demo.Repo, + update_changeset: &__MODULE__.changeset/3, + create_changeset: &__MODULE__.changeset/3, + item_query: &__MODULE__.item_query/3 + ], + layout: {DemoWeb.Layouts, :admin}, + fluid?: true + + @moduledoc """ + This module just demos the use of a InlineCRUD set for type `:map`. + It also demos how you can use the power of `Backpex` to do a poor man's polymorphism by using `Backpex.LiveResource` + to control the values entered into the generic `:map` field while reusing the same schema + for different entity types. + I + """ + import Ecto.Query, only: [where: 3] + + @impl Backpex.LiveResource + def singular_name, do: "Person" + + @impl Backpex.LiveResource + def plural_name, do: "Persons" + + # + # Added the item_query function to filter entities by type + # + def item_query(query, _, _assigns) do + query + |> where([entity], entity.type=="person") + end + + # + # Added the changeset function to create a changeset for a person entity + # + def changeset(entity, params, metadata \\ []) do + entity + |> Demo.Entity.changeset(params |> Map.put("type", "person")) + end + + @impl Backpex.LiveResource + def fields do + [ + identity: %{ + module: Backpex.Fields.Text, + label: "Name", + searchable: true + }, + fields: %{ + module: Backpex.Fields.InlineCRUD, + label: "Information", + type: :map, + except: [:index], + child_fields: [ + email: %{ + module: Backpex.Fields.Text, + label: "Email" + }, + phone: %{ + module: Backpex.Fields.Text, + label: "Phone" + }, + age: %{ + module: Backpex.Fields.Text, + label: "Age" + }, + weight: %{ + module: Backpex.Fields.Text, + label: "Weight (kg)" + } + ] + }, + ] + end +end From 35bbcf339a7d4f0a77e256f0be7e62dae05c7944 Mon Sep 17 00:00:00 2001 From: thanos Date: Sun, 19 Oct 2025 23:39:02 -0400 Subject: [PATCH 07/38] adding :map to Backpex.Fields.InlineCRUD and a polymorphic example to demo --- demo/lib/demo_web/live/person_live.ex | 63 ++++++++++++++++----------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/demo/lib/demo_web/live/person_live.ex b/demo/lib/demo_web/live/person_live.ex index 2c11ca60e..d93f2a3d6 100644 --- a/demo/lib/demo_web/live/person_live.ex +++ b/demo/lib/demo_web/live/person_live.ex @@ -1,5 +1,4 @@ -defmodule DemoWeb.PersonLive do -alias Backpex.Fields.InlineCRUD +defmodule DemoWeb.CarLive do use Backpex.LiveResource, adapter_config: [ schema: Demo.Entity, @@ -11,35 +10,43 @@ alias Backpex.Fields.InlineCRUD layout: {DemoWeb.Layouts, :admin}, fluid?: true - @moduledoc """ - This module just demos the use of a InlineCRUD set for type `:map`. - It also demos how you can use the power of `Backpex` to do a poor man's polymorphism by using `Backpex.LiveResource` - to control the values entered into the generic `:map` field while reusing the same schema - for different entity types. - I - """ + + @moduledoc """ + This module is similar to `PersonLive` in that it demos the use of a InlineCRUD set for type `:map`. + It also demos how you can use the power of `Backpex` to do a poor man's polymorphism by using `Backpex.LiveResource` + to control the values entered into the generic `:map` field while reusing the same schema + for different entity types. + + It also is an example of how you could validate the values in the `:map` field by using a `validate` + function and calling `Backpex.Fields.InlineCRUD.changeset` inside the `changeset` function. + """ + import Ecto.Query, only: [where: 3] @impl Backpex.LiveResource - def singular_name, do: "Person" + def singular_name, do: "Car" @impl Backpex.LiveResource - def plural_name, do: "Persons" + def plural_name, do: "Cars" + # # Added the item_query function to filter entities by type # def item_query(query, _, _assigns) do query - |> where([entity], entity.type=="person") + |> where([entity], entity.type=="car") end + # - # Added the changeset function to create a changeset for a person entity + # Added the changeset function to create a new entity with type "car", + # and validate the child fields in the entity # def changeset(entity, params, metadata \\ []) do entity - |> Demo.Entity.changeset(params |> Map.put("type", "person")) + |> Demo.Entity.changeset(params |> Map.put("type", "car")) + |> Backpex.Fields.InlineCRUD.changeset(:fields, metadata) end @impl Backpex.LiveResource @@ -47,7 +54,7 @@ alias Backpex.Fields.InlineCRUD [ identity: %{ module: Backpex.Fields.Text, - label: "Name", + label: "Model", searchable: true }, fields: %{ @@ -56,23 +63,27 @@ alias Backpex.Fields.InlineCRUD type: :map, except: [:index], child_fields: [ - email: %{ - module: Backpex.Fields.Text, - label: "Email" - }, - phone: %{ + engine_size: %{ module: Backpex.Fields.Text, - label: "Phone" + label: "Engine Size (cc)", + input_type: :integer }, - age: %{ + colour: %{ module: Backpex.Fields.Text, - label: "Age" + label: "Colour" }, - weight: %{ + year: %{ module: Backpex.Fields.Text, - label: "Weight (kg)" + label: "Year", + input_type: :integer + } - ] + ], + validate: fn changeset-> + changeset + |> Ecto.Changeset.validate_required([:colour, :year]) + |> Ecto.Changeset.validate_number(:year, greater_than: 1900, less_than: Date.utc_today().year, message: "must be between 1900 and #{Date.utc_today().year}") + end }, ] end From 679fae2ad68ffa6b12f4e3f601acbda59b3bbbb0 Mon Sep 17 00:00:00 2001 From: thanos Date: Sun, 19 Oct 2025 23:39:31 -0400 Subject: [PATCH 08/38] adding :map to Backpex.Fields.InlineCRUD and a polymorphic example to demo --- demo/lib/demo_web/router.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/demo/lib/demo_web/router.ex b/demo/lib/demo_web/router.ex index bba4d8c63..0bb31c104 100644 --- a/demo/lib/demo_web/router.ex +++ b/demo/lib/demo_web/router.ex @@ -45,6 +45,9 @@ defmodule DemoWeb.Router do live_resources "/film-reviews", FilmReviewLive live_resources "/short-links", ShortLinkLive live_resources "/tickets", TicketLive + live_resources "/entities", EntityLive + live_resources "/persons", PersonLive + live_resources "/cars", CarLive end end end From 9506298385b4eca46511a62dacd06161e9dc9ef8 Mon Sep 17 00:00:00 2001 From: thanos Date: Sun, 19 Oct 2025 23:40:51 -0400 Subject: [PATCH 09/38] adding :map to Backpex.Fields.InlineCRUD and a polymorphic example to demo --- demo/lib/demo_web/live/person_live.ex | 34 +++++++++---------- .../20251020033241_create_entity.exs | 12 +++---- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/demo/lib/demo_web/live/person_live.ex b/demo/lib/demo_web/live/person_live.ex index d93f2a3d6..0a3eb7896 100644 --- a/demo/lib/demo_web/live/person_live.ex +++ b/demo/lib/demo_web/live/person_live.ex @@ -5,21 +5,20 @@ defmodule DemoWeb.CarLive do repo: Demo.Repo, update_changeset: &__MODULE__.changeset/3, create_changeset: &__MODULE__.changeset/3, - item_query: &__MODULE__.item_query/3 + item_query: &__MODULE__.item_query/3 ], layout: {DemoWeb.Layouts, :admin}, fluid?: true + @moduledoc """ + This module is similar to `PersonLive` in that it demos the use of a InlineCRUD set for type `:map`. + It also demos how you can use the power of `Backpex` to do a poor man's polymorphism by using `Backpex.LiveResource` + to control the values entered into the generic `:map` field while reusing the same schema + for different entity types. - @moduledoc """ - This module is similar to `PersonLive` in that it demos the use of a InlineCRUD set for type `:map`. - It also demos how you can use the power of `Backpex` to do a poor man's polymorphism by using `Backpex.LiveResource` - to control the values entered into the generic `:map` field while reusing the same schema - for different entity types. - - It also is an example of how you could validate the values in the `:map` field by using a `validate` - function and calling `Backpex.Fields.InlineCRUD.changeset` inside the `changeset` function. - """ + It also is an example of how you could validate the values in the `:map` field by using a `validate` + function and calling `Backpex.Fields.InlineCRUD.changeset` inside the `changeset` function. + """ import Ecto.Query, only: [where: 3] @@ -29,16 +28,14 @@ defmodule DemoWeb.CarLive do @impl Backpex.LiveResource def plural_name, do: "Cars" - # # Added the item_query function to filter entities by type # def item_query(query, _, _assigns) do query - |> where([entity], entity.type=="car") + |> where([entity], entity.type == "car") end - # # Added the changeset function to create a new entity with type "car", # and validate the child fields in the entity @@ -76,15 +73,18 @@ defmodule DemoWeb.CarLive do module: Backpex.Fields.Text, label: "Year", input_type: :integer - } ], - validate: fn changeset-> + validate: fn changeset -> changeset |> Ecto.Changeset.validate_required([:colour, :year]) - |> Ecto.Changeset.validate_number(:year, greater_than: 1900, less_than: Date.utc_today().year, message: "must be between 1900 and #{Date.utc_today().year}") + |> Ecto.Changeset.validate_number(:year, + greater_than: 1900, + less_than: Date.utc_today().year, + message: "must be between 1900 and #{Date.utc_today().year}" + ) end - }, + } ] end end diff --git a/demo/priv/repo/migrations/20251020033241_create_entity.exs b/demo/priv/repo/migrations/20251020033241_create_entity.exs index 1252f0f73..3681b8bdc 100644 --- a/demo/priv/repo/migrations/20251020033241_create_entity.exs +++ b/demo/priv/repo/migrations/20251020033241_create_entity.exs @@ -2,11 +2,11 @@ defmodule Demo.Repo.Migrations.CreateEntities do use Ecto.Migration def change do - create table(:entities) do - add :identity, :string, null: false - add :type, :string, null: false - add :fields, :map - timestamps() - end + create table(:entities) do + add :identity, :string, null: false + add :type, :string, null: false + add :fields, :map + timestamps() + end end end From 996016f00de6dac7d4b5ffae2a2616af93e8ed36 Mon Sep 17 00:00:00 2001 From: thanos Date: Sun, 19 Oct 2025 23:41:27 -0400 Subject: [PATCH 10/38] adding :map to Backpex.Fields.InlineCRUD and a polymorphic example to demo --- demo/lib/demo_web/live/person_live.ex | 57 +++++++++++---------------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/demo/lib/demo_web/live/person_live.ex b/demo/lib/demo_web/live/person_live.ex index 0a3eb7896..2c11ca60e 100644 --- a/demo/lib/demo_web/live/person_live.ex +++ b/demo/lib/demo_web/live/person_live.ex @@ -1,49 +1,45 @@ -defmodule DemoWeb.CarLive do +defmodule DemoWeb.PersonLive do +alias Backpex.Fields.InlineCRUD use Backpex.LiveResource, adapter_config: [ schema: Demo.Entity, repo: Demo.Repo, update_changeset: &__MODULE__.changeset/3, create_changeset: &__MODULE__.changeset/3, - item_query: &__MODULE__.item_query/3 + item_query: &__MODULE__.item_query/3 ], layout: {DemoWeb.Layouts, :admin}, fluid?: true @moduledoc """ - This module is similar to `PersonLive` in that it demos the use of a InlineCRUD set for type `:map`. + This module just demos the use of a InlineCRUD set for type `:map`. It also demos how you can use the power of `Backpex` to do a poor man's polymorphism by using `Backpex.LiveResource` to control the values entered into the generic `:map` field while reusing the same schema for different entity types. - - It also is an example of how you could validate the values in the `:map` field by using a `validate` - function and calling `Backpex.Fields.InlineCRUD.changeset` inside the `changeset` function. + I """ - import Ecto.Query, only: [where: 3] @impl Backpex.LiveResource - def singular_name, do: "Car" + def singular_name, do: "Person" @impl Backpex.LiveResource - def plural_name, do: "Cars" + def plural_name, do: "Persons" # # Added the item_query function to filter entities by type # def item_query(query, _, _assigns) do query - |> where([entity], entity.type == "car") + |> where([entity], entity.type=="person") end # - # Added the changeset function to create a new entity with type "car", - # and validate the child fields in the entity + # Added the changeset function to create a changeset for a person entity # def changeset(entity, params, metadata \\ []) do entity - |> Demo.Entity.changeset(params |> Map.put("type", "car")) - |> Backpex.Fields.InlineCRUD.changeset(:fields, metadata) + |> Demo.Entity.changeset(params |> Map.put("type", "person")) end @impl Backpex.LiveResource @@ -51,7 +47,7 @@ defmodule DemoWeb.CarLive do [ identity: %{ module: Backpex.Fields.Text, - label: "Model", + label: "Name", searchable: true }, fields: %{ @@ -60,31 +56,24 @@ defmodule DemoWeb.CarLive do type: :map, except: [:index], child_fields: [ - engine_size: %{ + email: %{ module: Backpex.Fields.Text, - label: "Engine Size (cc)", - input_type: :integer + label: "Email" }, - colour: %{ + phone: %{ module: Backpex.Fields.Text, - label: "Colour" + label: "Phone" }, - year: %{ + age: %{ module: Backpex.Fields.Text, - label: "Year", - input_type: :integer + label: "Age" + }, + weight: %{ + module: Backpex.Fields.Text, + label: "Weight (kg)" } - ], - validate: fn changeset -> - changeset - |> Ecto.Changeset.validate_required([:colour, :year]) - |> Ecto.Changeset.validate_number(:year, - greater_than: 1900, - less_than: Date.utc_today().year, - message: "must be between 1900 and #{Date.utc_today().year}" - ) - end - } + ] + }, ] end end From 3b101cd4f66ea43cf263147121e8d15ee0ba6c2c Mon Sep 17 00:00:00 2001 From: thanos Date: Mon, 20 Oct 2025 00:10:27 -0400 Subject: [PATCH 11/38] adding :map to Backpex.Fields.InlineCRUD and a polymorphic example to demo --- demo/lib/demo/ecto_factory.ex | 42 +++++++++++++++++++ .../components/layouts/admin.html.heex | 25 +++++------ demo/lib/demo_web/live/person_live.ex | 3 +- demo/priv/repo/seeds.exs | 4 ++ lib/backpex/fields/inline_crud.ex | 2 +- 5 files changed, 61 insertions(+), 15 deletions(-) diff --git a/demo/lib/demo/ecto_factory.ex b/demo/lib/demo/ecto_factory.ex index 2d233e388..9317e0e9d 100644 --- a/demo/lib/demo/ecto_factory.ex +++ b/demo/lib/demo/ecto_factory.ex @@ -12,6 +12,7 @@ defmodule Demo.EctoFactory do alias Demo.Supplier alias Demo.Tag alias Demo.User + alias Demo.Entity def user_factory do %User{ @@ -107,6 +108,47 @@ defmodule Demo.EctoFactory do end end + def user_factory do + %Entity{ + identity: Faker.Name.name(), + type: "user", + fields: %{ + "email" => Faker.Internet.email(), + "phone" => Faker.Phone.number(), + "age" => Enum.random(18..65) + } + } + end + + def car_factory do + %Entity{ + identity: Faker.Vehicle.En.make_and_model(), + type: "car", + fields: %{ + "engine_size" => Enum.random([1800,2000,2300,2500,3000,3400,4000,5000]), + "colour" => Faker.Color.En.name(), + "year" => Enum.random(1900..2025) + } + } + end + + def person_factory do + first_name = Faker.Person.first_name() + last_name = Faker.Person.last_name() + %Entity{ + identity: "#{first_name} #{last_name}", + type: "person", + fields: %{ + "email" => "#{first_name}.#{last_name}@#{Faker.Internet.domain_name()}", + "phone" => Faker.Phone.EnUs.phone(), + "age" => Enum.random(18..85), + "weight" => Enum.random(45..130), + } + } + end + + + def entities_factory do Demo.Repo.insert(%Demo.Entity{ identity: "René Descartes", diff --git a/demo/lib/demo_web/components/layouts/admin.html.heex b/demo/lib/demo_web/components/layouts/admin.html.heex index 6d8f331d4..bbc5a61f2 100644 --- a/demo/lib/demo_web/components/layouts/admin.html.heex +++ b/demo/lib/demo_web/components/layouts/admin.html.heex @@ -91,19 +91,20 @@ Tickets + + <:label>Polymorphic Example + + Entities + + + Persons + + + Cars + + - - <:label>Polymorphic Example - - Entities - - - Persons - - - Cars - - + {render_slot(@inner_block)} diff --git a/demo/lib/demo_web/live/person_live.ex b/demo/lib/demo_web/live/person_live.ex index 2c11ca60e..164c7fa17 100644 --- a/demo/lib/demo_web/live/person_live.ex +++ b/demo/lib/demo_web/live/person_live.ex @@ -1,5 +1,4 @@ defmodule DemoWeb.PersonLive do -alias Backpex.Fields.InlineCRUD use Backpex.LiveResource, adapter_config: [ schema: Demo.Entity, @@ -37,7 +36,7 @@ alias Backpex.Fields.InlineCRUD # # Added the changeset function to create a changeset for a person entity # - def changeset(entity, params, metadata \\ []) do + def changeset(entity, params, _metadata \\ []) do entity |> Demo.Entity.changeset(params |> Map.put("type", "person")) end diff --git a/demo/priv/repo/seeds.exs b/demo/priv/repo/seeds.exs index 704854805..d5199ae91 100644 --- a/demo/priv/repo/seeds.exs +++ b/demo/priv/repo/seeds.exs @@ -23,6 +23,10 @@ insert_list(10, :product) insert_list(10, :address) +insert_list(10, :car) + +insert_list(10, :person) + insert!(Demo.Helpdesk.Ticket, count: 10) :code.priv_dir(:demo) diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index 593cea9cc..a275d864d 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -177,7 +177,7 @@ defmodule Backpex.Fields.InlineCRUD do
From f892c0978205a0e3393ce3c2d9ee94e8b70ad2c9 Mon Sep 17 00:00:00 2001 From: thanos Date: Mon, 20 Oct 2025 00:36:26 -0400 Subject: [PATCH 12/38] fixed lint issue --- lib/backpex/fields/inline_crud.ex | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index a275d864d..72983ecba 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -351,19 +351,33 @@ defmodule Backpex.Fields.InlineCRUD do |> Ecto.Changeset.cast(Ecto.Changeset.get_field(form_changeset, map_field), Map.keys(types)) |> validator.() - fields_changeset.errors - |> Enum.reduce(form_changeset, fn {map_field, {msg, _opts}}, form_changeset -> - Ecto.Changeset.add_error(form_changeset, map_field, msg) - end) + form_changeset + |> copy_errors(fields_changeset) + |> copy_values(fields_changeset) |> Ecto.Changeset.put_change( map_field, - Ecto.Changeset.apply_changes(fields_changeset) - |> Map.new(fn {k, v} -> {Atom.to_string(k), v} end) + values ) end end end + def copy_errors(dest_changeset, src_changeset) do + Enum.reduce(src_changeset.errors, form_changeset, fn {field, error}, form_changeset -> + {msg, _opts} = error + Ecto.Changeset.add_error(form_changeset, field, msg) + end) + end + + def copy_values(dest_changeset, src_changeset) do + dest_changeset + |> Ecto.Changeset.put_change( + map_field, + Ecto.Changeset.apply_changes(fields_changeset) + |> Map.new(fn {k, v} -> {Atom.to_string(k), v} end) + ) + end + defp changeset_value(assigns, field) when is_atom(field), do: changeset_value(assigns, Atom.to_string(field)) defp changeset_value(assigns, field) when is_binary(field) do From 0d96b8892583f1626958d5df7b6a92d82b4eceeb Mon Sep 17 00:00:00 2001 From: thanos vassilakis Date: Mon, 20 Oct 2025 21:09:15 -0400 Subject: [PATCH 13/38] Update lib/backpex/fields/inline_crud.ex Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/backpex/fields/inline_crud.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index 72983ecba..d4de7cf6c 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -94,7 +94,7 @@ defmodule Backpex.Fields.InlineCRUD do end ### Map - By using the `:map` type you can use the `InlineCRUD` to control waht fields can be stored in a `map`. + By using the `:map` type you can use the `InlineCRUD` to control what fields can be stored in a `map`. With the `:map` type the InlineCRUD uses the `input_type` option to build out the `types` map needed to create a changeset. If the `input_type` is not set, it defaults to `:string`. Another option is the `validate` callback that you can add to `fields` settings to From fe9429bbf1fb878a5d82ba710336e542a5d4da70 Mon Sep 17 00:00:00 2001 From: thanos vassilakis Date: Mon, 20 Oct 2025 21:09:35 -0400 Subject: [PATCH 14/38] Update lib/backpex/fields/inline_crud.ex Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/backpex/fields/inline_crud.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index d4de7cf6c..6f90874a2 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -129,7 +129,7 @@ defmodule Backpex.Fields.InlineCRUD do input_type: :integer } ], - validate: fn changeset-> + validate: fn changeset -> changeset |> Ecto.Changeset.validate_required([:colour, :year]) |> Ecto.Changeset.validate_number(:year, From 040c6994be5eca8d0150fce69f599f658b0311c6 Mon Sep 17 00:00:00 2001 From: thanos vassilakis Date: Mon, 20 Oct 2025 21:09:59 -0400 Subject: [PATCH 15/38] Update lib/backpex/fields/inline_crud.ex Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/backpex/fields/inline_crud.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index 6f90874a2..5b8cf59f2 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -273,7 +273,7 @@ defmodule Backpex.Fields.InlineCRUD do <% end %> - <%= if @field_options.type in [:emded, :assoc] do %> + <%= if @field_options.type in [:embed, :assoc] do %> Date: Mon, 20 Oct 2025 21:16:16 -0400 Subject: [PATCH 16/38] Update lib/backpex/fields/inline_crud.ex Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/backpex/fields/inline_crud.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index 5b8cf59f2..8bbff1854 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -363,9 +363,9 @@ defmodule Backpex.Fields.InlineCRUD do end def copy_errors(dest_changeset, src_changeset) do - Enum.reduce(src_changeset.errors, form_changeset, fn {field, error}, form_changeset -> + Enum.reduce(src_changeset.errors, dest_changeset, fn {field, error}, acc -> {msg, _opts} = error - Ecto.Changeset.add_error(form_changeset, field, msg) + Ecto.Changeset.add_error(acc, field, msg) end) end From b4feb9872f5d7692bfb8b135f4c75dbec85c7fe5 Mon Sep 17 00:00:00 2001 From: thanos vassilakis Date: Mon, 20 Oct 2025 21:21:27 -0400 Subject: [PATCH 17/38] Update demo/lib/demo_web/components/layouts/admin.html.heex Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- demo/lib/demo_web/components/layouts/admin.html.heex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/lib/demo_web/components/layouts/admin.html.heex b/demo/lib/demo_web/components/layouts/admin.html.heex index bbc5a61f2..91f3888c3 100644 --- a/demo/lib/demo_web/components/layouts/admin.html.heex +++ b/demo/lib/demo_web/components/layouts/admin.html.heex @@ -91,7 +91,7 @@ Tickets - + <:label>Polymorphic Example Entities From df76b2aff60a30cace06fd4158df2fef3d85dc82 Mon Sep 17 00:00:00 2001 From: thanos Date: Mon, 20 Oct 2025 21:22:46 -0400 Subject: [PATCH 18/38] fixed issues around changeset --- .tool-versions | 2 +- lib/backpex/fields/inline_crud.ex | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.tool-versions b/.tool-versions index b2f3ccbc5..bf44d4afd 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ erlang 28.1 -elixir 1.18.4 +elixir 1.18.4-otp-28 diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index 8bbff1854..ce52c3e6d 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -353,11 +353,7 @@ defmodule Backpex.Fields.InlineCRUD do form_changeset |> copy_errors(fields_changeset) - |> copy_values(fields_changeset) - |> Ecto.Changeset.put_change( - map_field, - values - ) + |> copy_values(map_field, fields_changeset) end end end @@ -369,11 +365,11 @@ defmodule Backpex.Fields.InlineCRUD do end) end - def copy_values(dest_changeset, src_changeset) do + def copy_values(dest_changeset, map_field, src_changeset) do dest_changeset |> Ecto.Changeset.put_change( map_field, - Ecto.Changeset.apply_changes(fields_changeset) + Ecto.Changeset.apply_changes(src_changeset) |> Map.new(fn {k, v} -> {Atom.to_string(k), v} end) ) end From da95dcaf1267f2d22b5c40cf975935ce6254c9de Mon Sep 17 00:00:00 2001 From: thanos Date: Mon, 20 Oct 2025 21:25:02 -0400 Subject: [PATCH 19/38] strange issue in factory --- demo/lib/demo/ecto_factory.ex | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/demo/lib/demo/ecto_factory.ex b/demo/lib/demo/ecto_factory.ex index 9317e0e9d..c614e219d 100644 --- a/demo/lib/demo/ecto_factory.ex +++ b/demo/lib/demo/ecto_factory.ex @@ -108,18 +108,6 @@ defmodule Demo.EctoFactory do end end - def user_factory do - %Entity{ - identity: Faker.Name.name(), - type: "user", - fields: %{ - "email" => Faker.Internet.email(), - "phone" => Faker.Phone.number(), - "age" => Enum.random(18..65) - } - } - end - def car_factory do %Entity{ identity: Faker.Vehicle.En.make_and_model(), From 0577294a186ef9e5865b7c44e1ccde6e9c86ce3c Mon Sep 17 00:00:00 2001 From: thanos vassilakis Date: Mon, 20 Oct 2025 21:39:58 -0400 Subject: [PATCH 20/38] Update lib/backpex/fields/inline_crud.ex Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/backpex/fields/inline_crud.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index ce52c3e6d..d82c90bc0 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -1,7 +1,7 @@ defmodule Backpex.Fields.InlineCRUD do @config_schema [ type: [ - doc: "The type of the field. One of `:embed`, `:embed_one` or `:assoc` or `:map`.", + doc: "The type of the field. One of `:embed`, `:embed_one` or `:assoc` or `:map`.", type: {:in, [:embed, :assoc, :embed_one, :map]}, required: true ], From d9b5f4e5d2fa997cf11c1267846a2b5c22c691cb Mon Sep 17 00:00:00 2001 From: thanos vassilakis Date: Mon, 20 Oct 2025 21:40:22 -0400 Subject: [PATCH 21/38] Update lib/backpex/fields/inline_crud.ex Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/backpex/fields/inline_crud.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index d82c90bc0..79746d9ae 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -28,7 +28,7 @@ defmodule Backpex.Fields.InlineCRUD do ] @moduledoc """ - A field to handle inline CRUD operations. It can be used with with columns of type `map`, `embeds_many`, `embeds_one`, or `has_many` (for associations). + A field to handle inline CRUD operations. It can be used with columns of type `map`, `embeds_many`, `embeds_one`, or `has_many` (for associations). ## Field-specific options From 32732a52ca82bb6c829a6a566356932ae791898a Mon Sep 17 00:00:00 2001 From: thanos vassilakis Date: Mon, 20 Oct 2025 21:47:03 -0400 Subject: [PATCH 22/38] Update lib/backpex/fields/inline_crud.ex Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/backpex/fields/inline_crud.ex | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index 79746d9ae..9af946c54 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -177,7 +177,12 @@ defmodule Backpex.Fields.InlineCRUD do From c115c610e67178d5ae93e3f3a0cabf15a47eaa32 Mon Sep 17 00:00:00 2001 From: thanos Date: Mon, 20 Oct 2025 21:56:14 -0400 Subject: [PATCH 23/38] fixed issues around changeset --- demo/lib/demo/ecto_factory.ex | 15 --------------- demo/lib/demo_web/live/car_live.ex | 2 +- demo/lib/demo_web/live/person_live.ex | 2 +- lib/backpex/fields/inline_crud.ex | 7 +++---- 4 files changed, 5 insertions(+), 21 deletions(-) diff --git a/demo/lib/demo/ecto_factory.ex b/demo/lib/demo/ecto_factory.ex index c614e219d..146a35fc9 100644 --- a/demo/lib/demo/ecto_factory.ex +++ b/demo/lib/demo/ecto_factory.ex @@ -135,19 +135,4 @@ defmodule Demo.EctoFactory do } end - - - def entities_factory do - Demo.Repo.insert(%Demo.Entity{ - identity: "René Descartes", - type: "person", - fields: %{"email" => "descartes@philosophers.fr", "phone" => "+33-14325-7123", "age" => 53} - }) - - Demo.Repo.insert(%Demo.Entity{ - identity: "BMW 320i", - type: "car", - fields: %{"engine_size" => 2000, "colour" => "Metalic Sliver", "year" => 1984} - }) - end end diff --git a/demo/lib/demo_web/live/car_live.ex b/demo/lib/demo_web/live/car_live.ex index 0a3eb7896..68a8320c1 100644 --- a/demo/lib/demo_web/live/car_live.ex +++ b/demo/lib/demo_web/live/car_live.ex @@ -80,7 +80,7 @@ defmodule DemoWeb.CarLive do |> Ecto.Changeset.validate_required([:colour, :year]) |> Ecto.Changeset.validate_number(:year, greater_than: 1900, - less_than: Date.utc_today().year, + less_than: Date.utc_today().year+1, message: "must be between 1900 and #{Date.utc_today().year}" ) end diff --git a/demo/lib/demo_web/live/person_live.ex b/demo/lib/demo_web/live/person_live.ex index 164c7fa17..a7b6aaa0f 100644 --- a/demo/lib/demo_web/live/person_live.ex +++ b/demo/lib/demo_web/live/person_live.ex @@ -30,7 +30,7 @@ defmodule DemoWeb.PersonLive do # def item_query(query, _, _assigns) do query - |> where([entity], entity.type=="person") + |> where([entity], entity.type == "person") end # diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index 9af946c54..44717d9e7 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -365,8 +365,8 @@ defmodule Backpex.Fields.InlineCRUD do def copy_errors(dest_changeset, src_changeset) do Enum.reduce(src_changeset.errors, dest_changeset, fn {field, error}, acc -> - {msg, _opts} = error - Ecto.Changeset.add_error(acc, field, msg) + {msg, opts} = error + Ecto.Changeset.add_error(acc, field, msg, opts) end) end @@ -382,8 +382,7 @@ defmodule Backpex.Fields.InlineCRUD do defp changeset_value(assigns, field) when is_atom(field), do: changeset_value(assigns, Atom.to_string(field)) defp changeset_value(assigns, field) when is_binary(field) do - Ecto.Changeset.get_field(assigns.changeset, assigns.name) - |> Map.get(field) + case Ecto.Changeset.get_field(assigns.changeset, assigns.name) do nil -> nil; map -> Map.get(map, field) end end defp changeset_errors(assigns, field) when is_binary(field), From d62709691902da19871c3d1d8c61506508d8ead1 Mon Sep 17 00:00:00 2001 From: thanos Date: Mon, 20 Oct 2025 21:59:41 -0400 Subject: [PATCH 24/38] fixed issues around changeset --- lib/backpex/fields/inline_crud.ex | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index 44717d9e7..9801d0a50 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -180,7 +180,7 @@ defmodule Backpex.Fields.InlineCRUD do {HTML.pretty_value( Map.get( row, - if @field_options.type == :map, do: Atom.to_string(name), else: name + if(@field_options.type == :map, do: Atom.to_string(name), else: name) ) )} @@ -382,7 +382,10 @@ defmodule Backpex.Fields.InlineCRUD do defp changeset_value(assigns, field) when is_atom(field), do: changeset_value(assigns, Atom.to_string(field)) defp changeset_value(assigns, field) when is_binary(field) do - case Ecto.Changeset.get_field(assigns.changeset, assigns.name) do nil -> nil; map -> Map.get(map, field) end + case Ecto.Changeset.get_field(assigns.changeset, assigns.name) do + nil -> nil + map -> Map.get(map, field) + end end defp changeset_errors(assigns, field) when is_binary(field), From 1f906ac111a841f7bcc7e34dfb286dcebb679a88 Mon Sep 17 00:00:00 2001 From: thanos vassilakis Date: Mon, 20 Oct 2025 22:30:20 -0400 Subject: [PATCH 25/38] Update lib/backpex/fields/inline_crud.ex Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/backpex/fields/inline_crud.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index 9801d0a50..d430e2406 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -1,7 +1,7 @@ defmodule Backpex.Fields.InlineCRUD do @config_schema [ type: [ - doc: "The type of the field. One of `:embed`, `:embed_one` or `:assoc` or `:map`.", + doc: "The type of the field. One of `:embed`, `:embed_one`, `:assoc`, or `:map`.", type: {:in, [:embed, :assoc, :embed_one, :map]}, required: true ], From 1b85afba42b6f2eb64ec5dfcd5add1ae740c6d4b Mon Sep 17 00:00:00 2001 From: thanos vassilakis Date: Tue, 21 Oct 2025 21:53:59 -0400 Subject: [PATCH 26/38] Update demo/lib/demo_web/live/person_live.ex Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- demo/lib/demo_web/live/person_live.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/demo/lib/demo_web/live/person_live.ex b/demo/lib/demo_web/live/person_live.ex index a7b6aaa0f..36b0988ea 100644 --- a/demo/lib/demo_web/live/person_live.ex +++ b/demo/lib/demo_web/live/person_live.ex @@ -15,7 +15,6 @@ defmodule DemoWeb.PersonLive do It also demos how you can use the power of `Backpex` to do a poor man's polymorphism by using `Backpex.LiveResource` to control the values entered into the generic `:map` field while reusing the same schema for different entity types. - I """ import Ecto.Query, only: [where: 3] From 80377ab3034e841054ea6dd7febe5ff00e522b83 Mon Sep 17 00:00:00 2001 From: thanos Date: Tue, 21 Oct 2025 21:55:55 -0400 Subject: [PATCH 27/38] made copy_errors and copy_values a defp --- lib/backpex/fields/inline_crud.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index d430e2406..830766dfc 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -363,14 +363,14 @@ defmodule Backpex.Fields.InlineCRUD do end end - def copy_errors(dest_changeset, src_changeset) do + defp copy_errors(dest_changeset, src_changeset) do Enum.reduce(src_changeset.errors, dest_changeset, fn {field, error}, acc -> {msg, opts} = error Ecto.Changeset.add_error(acc, field, msg, opts) end) end - def copy_values(dest_changeset, map_field, src_changeset) do + defp copy_values(dest_changeset, map_field, src_changeset) do dest_changeset |> Ecto.Changeset.put_change( map_field, From cde3e6286e461f443e9a71fb87ee4d7d4994378e Mon Sep 17 00:00:00 2001 From: thanos vassilakis Date: Tue, 21 Oct 2025 21:57:02 -0400 Subject: [PATCH 28/38] Update demo/lib/demo/ecto_factory.ex Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- demo/lib/demo/ecto_factory.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/lib/demo/ecto_factory.ex b/demo/lib/demo/ecto_factory.ex index 146a35fc9..4473ea7cd 100644 --- a/demo/lib/demo/ecto_factory.ex +++ b/demo/lib/demo/ecto_factory.ex @@ -113,7 +113,7 @@ defmodule Demo.EctoFactory do identity: Faker.Vehicle.En.make_and_model(), type: "car", fields: %{ - "engine_size" => Enum.random([1800,2000,2300,2500,3000,3400,4000,5000]), + "engine_size" => Enum.random([1800, 2000, 2300, 2500, 3000, 3400, 4000, 5000]), "colour" => Faker.Color.En.name(), "year" => Enum.random(1900..2025) } From 592dd423106a2556e3245536cb68e27ea02891d5 Mon Sep 17 00:00:00 2001 From: thanos vassilakis Date: Tue, 21 Oct 2025 22:03:58 -0400 Subject: [PATCH 29/38] Update lib/backpex/fields/inline_crud.ex Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/backpex/fields/inline_crud.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index 830766dfc..8ce11476f 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -21,7 +21,7 @@ defmodule Backpex.Fields.InlineCRUD do doc: """ An optional validation function used to validate `:map` child fields. It takes the changeset and returns a changeset. You can use it to validate the `child_fields` - of a `map`, see the examples . + of a `map`, see the examples. """, type: {:fun, 1} ] From be579539d99de051e7bf26e8ff8c1646515243b0 Mon Sep 17 00:00:00 2001 From: thanos vassilakis Date: Tue, 21 Oct 2025 22:04:44 -0400 Subject: [PATCH 30/38] Update demo/lib/demo_web/live/car_live.ex Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- demo/lib/demo_web/live/car_live.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/lib/demo_web/live/car_live.ex b/demo/lib/demo_web/live/car_live.ex index 68a8320c1..a499a6805 100644 --- a/demo/lib/demo_web/live/car_live.ex +++ b/demo/lib/demo_web/live/car_live.ex @@ -80,7 +80,7 @@ defmodule DemoWeb.CarLive do |> Ecto.Changeset.validate_required([:colour, :year]) |> Ecto.Changeset.validate_number(:year, greater_than: 1900, - less_than: Date.utc_today().year+1, + less_than: Date.utc_today().year + 1, message: "must be between 1900 and #{Date.utc_today().year}" ) end From 80280468a93306a52b127926e2b6127fe72d2aad Mon Sep 17 00:00:00 2001 From: thanos vassilakis Date: Tue, 21 Oct 2025 22:13:15 -0400 Subject: [PATCH 31/38] Update demo/lib/demo_web/live/person_live.ex Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- demo/lib/demo_web/live/person_live.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/lib/demo_web/live/person_live.ex b/demo/lib/demo_web/live/person_live.ex index 36b0988ea..bcd5a12e2 100644 --- a/demo/lib/demo_web/live/person_live.ex +++ b/demo/lib/demo_web/live/person_live.ex @@ -71,7 +71,7 @@ defmodule DemoWeb.PersonLive do label: "Weight (kg)" } ] - }, + } ] end end From aa1d255a594c79af4a0c68f9510e6651fff8fcfd Mon Sep 17 00:00:00 2001 From: thanos vassilakis Date: Tue, 21 Oct 2025 22:21:22 -0400 Subject: [PATCH 32/38] Update lib/backpex/fields/inline_crud.ex Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/backpex/fields/inline_crud.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/backpex/fields/inline_crud.ex b/lib/backpex/fields/inline_crud.ex index 8ce11476f..7f1e56d52 100644 --- a/lib/backpex/fields/inline_crud.ex +++ b/lib/backpex/fields/inline_crud.ex @@ -135,7 +135,7 @@ defmodule Backpex.Fields.InlineCRUD do |> Ecto.Changeset.validate_number(:year, greater_than: 1900, less_than: Date.utc_today().year + 1, - message: "must be between 1900 and #{Date.utc_today().year + 1}" + message: "must be between 1901 and #{Date.utc_today().year}" ) end } From 14f294b64899845b42f3b03194efeeb35ab313b1 Mon Sep 17 00:00:00 2001 From: thanos vassilakis Date: Tue, 21 Oct 2025 22:21:50 -0400 Subject: [PATCH 33/38] Update demo/lib/demo/ecto_factory.ex Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- demo/lib/demo/ecto_factory.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/lib/demo/ecto_factory.ex b/demo/lib/demo/ecto_factory.ex index 4473ea7cd..88b731921 100644 --- a/demo/lib/demo/ecto_factory.ex +++ b/demo/lib/demo/ecto_factory.ex @@ -127,7 +127,7 @@ defmodule Demo.EctoFactory do identity: "#{first_name} #{last_name}", type: "person", fields: %{ - "email" => "#{first_name}.#{last_name}@#{Faker.Internet.domain_name()}", + "email" => Faker.Internet.email(), "phone" => Faker.Phone.EnUs.phone(), "age" => Enum.random(18..85), "weight" => Enum.random(45..130), From c1b4fc3d77bc5e3af036a0e941e6d7529509e21f Mon Sep 17 00:00:00 2001 From: thanos Date: Wed, 22 Oct 2025 06:48:52 -0400 Subject: [PATCH 34/38] fixed format issues --- demo/lib/demo/ecto_factory.ex | 12 ++++++------ demo/lib/demo_web/live/car_live.ex | 2 +- demo/lib/demo_web/live/person_live.ex | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/demo/lib/demo/ecto_factory.ex b/demo/lib/demo/ecto_factory.ex index 146a35fc9..481460232 100644 --- a/demo/lib/demo/ecto_factory.ex +++ b/demo/lib/demo/ecto_factory.ex @@ -113,7 +113,7 @@ defmodule Demo.EctoFactory do identity: Faker.Vehicle.En.make_and_model(), type: "car", fields: %{ - "engine_size" => Enum.random([1800,2000,2300,2500,3000,3400,4000,5000]), + "engine_size" => Enum.random([1800, 2000, 2300, 2500, 3000, 3400, 4000, 5000]), "colour" => Faker.Color.En.name(), "year" => Enum.random(1900..2025) } @@ -123,16 +123,16 @@ defmodule Demo.EctoFactory do def person_factory do first_name = Faker.Person.first_name() last_name = Faker.Person.last_name() + %Entity{ identity: "#{first_name} #{last_name}", type: "person", fields: %{ - "email" => "#{first_name}.#{last_name}@#{Faker.Internet.domain_name()}", - "phone" => Faker.Phone.EnUs.phone(), - "age" => Enum.random(18..85), - "weight" => Enum.random(45..130), + "email" => "#{first_name}.#{last_name}@#{Faker.Internet.domain_name()}", + "phone" => Faker.Phone.EnUs.phone(), + "age" => Enum.random(18..85), + "weight" => Enum.random(45..130) } } end - end diff --git a/demo/lib/demo_web/live/car_live.ex b/demo/lib/demo_web/live/car_live.ex index 68a8320c1..a499a6805 100644 --- a/demo/lib/demo_web/live/car_live.ex +++ b/demo/lib/demo_web/live/car_live.ex @@ -80,7 +80,7 @@ defmodule DemoWeb.CarLive do |> Ecto.Changeset.validate_required([:colour, :year]) |> Ecto.Changeset.validate_number(:year, greater_than: 1900, - less_than: Date.utc_today().year+1, + less_than: Date.utc_today().year + 1, message: "must be between 1900 and #{Date.utc_today().year}" ) end diff --git a/demo/lib/demo_web/live/person_live.ex b/demo/lib/demo_web/live/person_live.ex index 36b0988ea..fe26d3de4 100644 --- a/demo/lib/demo_web/live/person_live.ex +++ b/demo/lib/demo_web/live/person_live.ex @@ -5,7 +5,7 @@ defmodule DemoWeb.PersonLive do repo: Demo.Repo, update_changeset: &__MODULE__.changeset/3, create_changeset: &__MODULE__.changeset/3, - item_query: &__MODULE__.item_query/3 + item_query: &__MODULE__.item_query/3 ], layout: {DemoWeb.Layouts, :admin}, fluid?: true @@ -71,7 +71,7 @@ defmodule DemoWeb.PersonLive do label: "Weight (kg)" } ] - }, + } ] end end From 68d10c36d3c42ea9eff2b94c6c8fbd563d2e91f3 Mon Sep 17 00:00:00 2001 From: thanos Date: Wed, 22 Oct 2025 07:14:10 -0400 Subject: [PATCH 35/38] fixed credo warnings --- demo/lib/demo/ecto_factory.ex | 4 ++-- demo/lib/demo_web/live/car_live.ex | 28 ++++++++++++++------------- demo/lib/demo_web/live/person_live.ex | 18 +++++++++-------- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/demo/lib/demo/ecto_factory.ex b/demo/lib/demo/ecto_factory.ex index d46c7819f..6a2391d7f 100644 --- a/demo/lib/demo/ecto_factory.ex +++ b/demo/lib/demo/ecto_factory.ex @@ -5,6 +5,7 @@ defmodule Demo.EctoFactory do alias Demo.Address alias Demo.Category + alias Demo.Entity alias Demo.FilmReview alias Demo.Post alias Demo.Product @@ -12,7 +13,6 @@ defmodule Demo.EctoFactory do alias Demo.Supplier alias Demo.Tag alias Demo.User - alias Demo.Entity def user_factory do %User{ @@ -129,7 +129,7 @@ defmodule Demo.EctoFactory do type: "person", fields: %{ "email" => Faker.Internet.email(), - "phone" => Faker.Phone.EnUs.phone(), + "phone" => Faker.Phone.number(), "age" => Enum.random(18..85), "weight" => Enum.random(45..130) } diff --git a/demo/lib/demo_web/live/car_live.ex b/demo/lib/demo_web/live/car_live.ex index a499a6805..679249aa7 100644 --- a/demo/lib/demo_web/live/car_live.ex +++ b/demo/lib/demo_web/live/car_live.ex @@ -1,4 +1,13 @@ defmodule DemoWeb.CarLive do + @moduledoc """ + This module is similar to `PersonLive` in that it demos the use of a InlineCRUD set for type `:map`. + It also demos how you can use the power of `Backpex` to do a poor man's polymorphism by using `Backpex.LiveResource` + to control the values entered into the generic `:map` field while reusing the same schema + for different entity types. + + It also is an example of how you could validate the values in the `:map` field by using a `validate` + function and calling `Backpex.Fields.InlineCRUD.changeset` inside the `changeset` function. + """ use Backpex.LiveResource, adapter_config: [ schema: Demo.Entity, @@ -10,18 +19,11 @@ defmodule DemoWeb.CarLive do layout: {DemoWeb.Layouts, :admin}, fluid?: true - @moduledoc """ - This module is similar to `PersonLive` in that it demos the use of a InlineCRUD set for type `:map`. - It also demos how you can use the power of `Backpex` to do a poor man's polymorphism by using `Backpex.LiveResource` - to control the values entered into the generic `:map` field while reusing the same schema - for different entity types. - - It also is an example of how you could validate the values in the `:map` field by using a `validate` - function and calling `Backpex.Fields.InlineCRUD.changeset` inside the `changeset` function. - """ - import Ecto.Query, only: [where: 3] + alias Backpex.Fields.InlineCRUD + alias Demo.Entity + @impl Backpex.LiveResource def singular_name, do: "Car" @@ -31,7 +33,7 @@ defmodule DemoWeb.CarLive do # # Added the item_query function to filter entities by type # - def item_query(query, _, _assigns) do + def item_query(query, _view, _assigns) do query |> where([entity], entity.type == "car") end @@ -42,8 +44,8 @@ defmodule DemoWeb.CarLive do # def changeset(entity, params, metadata \\ []) do entity - |> Demo.Entity.changeset(params |> Map.put("type", "car")) - |> Backpex.Fields.InlineCRUD.changeset(:fields, metadata) + |> Entity.changeset(params |> Map.put("type", "car")) + |> InlineCRUD.changeset(:fields, metadata) end @impl Backpex.LiveResource diff --git a/demo/lib/demo_web/live/person_live.ex b/demo/lib/demo_web/live/person_live.ex index fe26d3de4..979723678 100644 --- a/demo/lib/demo_web/live/person_live.ex +++ b/demo/lib/demo_web/live/person_live.ex @@ -1,4 +1,10 @@ defmodule DemoWeb.PersonLive do + @moduledoc """ + This module just demos the use of a InlineCRUD set for type `:map`. + It also demos how you can use the power of `Backpex` to do a poor man's polymorphism by using `Backpex.LiveResource` + to control the values entered into the generic `:map` field while reusing the same schema + for different entity types. + """ use Backpex.LiveResource, adapter_config: [ schema: Demo.Entity, @@ -10,14 +16,10 @@ defmodule DemoWeb.PersonLive do layout: {DemoWeb.Layouts, :admin}, fluid?: true - @moduledoc """ - This module just demos the use of a InlineCRUD set for type `:map`. - It also demos how you can use the power of `Backpex` to do a poor man's polymorphism by using `Backpex.LiveResource` - to control the values entered into the generic `:map` field while reusing the same schema - for different entity types. - """ import Ecto.Query, only: [where: 3] + alias Demo.Entity + @impl Backpex.LiveResource def singular_name, do: "Person" @@ -27,7 +29,7 @@ defmodule DemoWeb.PersonLive do # # Added the item_query function to filter entities by type # - def item_query(query, _, _assigns) do + def item_query(query, _view, _assigns) do query |> where([entity], entity.type == "person") end @@ -37,7 +39,7 @@ defmodule DemoWeb.PersonLive do # def changeset(entity, params, _metadata \\ []) do entity - |> Demo.Entity.changeset(params |> Map.put("type", "person")) + |> Entity.changeset(params |> Map.put("type", "person")) end @impl Backpex.LiveResource From 5f05e454023ef73b87ecb5ad4981d54acd10d8a1 Mon Sep 17 00:00:00 2001 From: thanos Date: Wed, 22 Oct 2025 07:29:17 -0400 Subject: [PATCH 36/38] fixed credo warnings --- demo/lib/demo/ecto_factory.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/lib/demo/ecto_factory.ex b/demo/lib/demo/ecto_factory.ex index 6a2391d7f..808f26154 100644 --- a/demo/lib/demo/ecto_factory.ex +++ b/demo/lib/demo/ecto_factory.ex @@ -129,7 +129,7 @@ defmodule Demo.EctoFactory do type: "person", fields: %{ "email" => Faker.Internet.email(), - "phone" => Faker.Phone.number(), + "phone" => Faker.Phone.Hy.number(), "age" => Enum.random(18..85), "weight" => Enum.random(45..130) } From 7a534aeb68c01e9abd330e6af2bb79ba01eebb1d Mon Sep 17 00:00:00 2001 From: thanos Date: Wed, 22 Oct 2025 07:35:41 -0400 Subject: [PATCH 37/38] fixed credo warnings --- demo/lib/demo/ecto_factory.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/demo/lib/demo/ecto_factory.ex b/demo/lib/demo/ecto_factory.ex index 808f26154..6e65207f4 100644 --- a/demo/lib/demo/ecto_factory.ex +++ b/demo/lib/demo/ecto_factory.ex @@ -14,6 +14,8 @@ defmodule Demo.EctoFactory do alias Demo.Tag alias Demo.User + alias Faker.Phone.EnUs, as: FakerPhone + def user_factory do %User{ username: Faker.Internet.user_name(), @@ -129,7 +131,7 @@ defmodule Demo.EctoFactory do type: "person", fields: %{ "email" => Faker.Internet.email(), - "phone" => Faker.Phone.Hy.number(), + "phone" => FakerPhone.number(), "age" => Enum.random(18..85), "weight" => Enum.random(45..130) } From ebc55994b63194f91c2e9b5927a1145c0928d2bf Mon Sep 17 00:00:00 2001 From: thanos Date: Wed, 22 Oct 2025 07:58:08 -0400 Subject: [PATCH 38/38] fixed credo warnings --- demo/lib/demo/ecto_factory.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/lib/demo/ecto_factory.ex b/demo/lib/demo/ecto_factory.ex index 6e65207f4..159ce73fd 100644 --- a/demo/lib/demo/ecto_factory.ex +++ b/demo/lib/demo/ecto_factory.ex @@ -131,7 +131,7 @@ defmodule Demo.EctoFactory do type: "person", fields: %{ "email" => Faker.Internet.email(), - "phone" => FakerPhone.number(), + "phone" => FakerPhone.phone(), "age" => Enum.random(18..85), "weight" => Enum.random(45..130) }
- {HTML.pretty_value(Map.get(row, name))} + {HTML.pretty_value(Map.get(row, Atom.to_string(name)))}
- {HTML.pretty_value(Map.get(row, Atom.to_string(name)))} + {HTML.pretty_value( + Map.get( + row, + if @field_options.type == :map, do: Atom.to_string(name), else: name + ) + )}