Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

standardize and improve resource and adapter modules #667

Merged
merged 25 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
987e1e6
remove adapter specific bang function
pehbehbeh Nov 14, 2024
451618a
Merge branch 'develop' into feature/adapter-bang
pehbehbeh Jan 17, 2025
40822a0
simplify docs
pehbehbeh Jan 17, 2025
6c8ca66
fix dockerfile casing
pehbehbeh Jan 17, 2025
84505e0
improve ash info
pehbehbeh Jan 17, 2025
0264780
remove usage of bang functions in ash adapter
pehbehbeh Jan 17, 2025
c6a623c
add upgrade guide for 0.10
pehbehbeh Jan 17, 2025
ddb5db8
change parameter order for adapter list and count
pehbehbeh Jan 17, 2025
bd48583
further simplify list and count
pehbehbeh Jan 17, 2025
d134a38
remove fields param from insert
pehbehbeh Jan 17, 2025
127ee03
standardize insert and update
pehbehbeh Jan 17, 2025
de8347a
return result tuple on list and count
pehbehbeh Jan 17, 2025
0c0f9db
improve typespecs
pehbehbeh Jan 17, 2025
1a9287c
standardize change and update_all
pehbehbeh Jan 17, 2025
fe81d4e
remove obsolete item_query callback
pehbehbeh Jan 17, 2025
57310ac
simplify list_query
pehbehbeh Jan 17, 2025
af8f135
simplify record_query
pehbehbeh Jan 17, 2025
cd27831
Merge pull request #785 from naymspace/feature/item-query-improvements
Flo0807 Jan 24, 2025
34e7d5f
Update upgrade guides
Flo0807 Jan 24, 2025
f2bdf4d
Fix warnings in docs
Flo0807 Jan 24, 2025
47c319d
Update v0.8 upgrade guide
Flo0807 Jan 24, 2025
13c5603
Add fallback item_query for user live resource
Flo0807 Jan 24, 2025
b824bd0
Update item query guide
Flo0807 Jan 24, 2025
b798243
Update v0.8 upgrade guide
Flo0807 Jan 24, 2025
facf5c5
Update upgrade guide
Flo0807 Jan 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ EXPOSE 4000
# Stage: release
########################################################################

FROM builder as release
FROM builder AS release

ENV MIX_ENV=prod

Expand Down
2 changes: 1 addition & 1 deletion demo/lib/demo_web/item_actions/soft_delete.ex
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ defmodule DemoWeb.ItemActions.SoftDelete do
socket =
try do
updates = [set: [deleted_at: datetime]]
{:ok, _items} = Backpex.Resource.update_all(items, updates, "deleted", socket.assigns.live_resource)
{:ok, _count} = Backpex.Resource.update_all(items, updates, "deleted", socket.assigns.live_resource)

socket
|> clear_flash()
Expand Down
4 changes: 2 additions & 2 deletions demo/lib/demo_web/live/ticket_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ defmodule DemoWeb.TicketLive do
<Backpex.HTML.CoreComponents.icon name="hero-information-circle" class="h-5 w-5" />
<p>
This resource uses the <strong>Ash adapter</strong>, which is currently in a very early alpha stage.
Currently, only <strong>index</strong> and <strong>show</strong> are functional in a very basic form.
We are working on supporting more Backpex features in the future.
Currently, only <strong>index</strong>, <strong>show</strong> and <strong>delete</strong> are functional in a
very basic form. We are working on supporting more Backpex features in the future.
</p>
</div>
"""
Expand Down
4 changes: 2 additions & 2 deletions guides/actions/item-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ defmodule DemoWeb.ItemAction.SoftDelete do

socket =
try do
{:ok, _items} =
{:ok, _count_} =
Backpex.Resource.update_all(
socket.assigns,
items,
Expand Down Expand Up @@ -201,4 +201,4 @@ The `c:Backpex.ItemAction.handle/3` function is called when the item action is t

By default an item action is triggered immediately when the user clicks on the corresponding icon in the resource table or in the show view, but an item actions also supports a confirmation dialog. To enable the confirmation dialog you need to implement the `c:Backpex.ItemAction.confirm/1` function and return a string that will be displayed in the confirmation dialog. The confirmation dialog will be displayed when the user clicks on the icon in the resource table.

You might want to use the `c:Backpex.ItemAction.cancel_label/0` (defaults to "Cancel") and `c:Backpex.ItemAction.confirm_label/0` (defaults to "Apply") functions to set the labels of the buttons in the dialog.
You might want to use the `c:Backpex.ItemAction.cancel_label/0` (defaults to "Cancel") and `c:Backpex.ItemAction.confirm_label/0` (defaults to "Apply") functions to set the labels of the buttons in the dialog.
5 changes: 5 additions & 0 deletions guides/upgrading/v0.10.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Upgrading to v0.10

## LiveView 1.0

See [phoenix_live_view changelog](https://github.com/phoenixframework/phoenix_live_view/blob/main/CHANGELOG.md) for info on how to upgrade to `1.0`.
7 changes: 7 additions & 0 deletions guides/upgrading/v0.11.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Upgrading to v0.11

## Parameter changes in core modules

In case you are using `Backpex.Resource` or one of the `Backpex.Adapter` modules (`Backpex.Adapters.Ecto` or
`Backpex.Adapters.Ash`) directly check out the updated function definitions. This will also apply in case you built your
own adapter.
6 changes: 3 additions & 3 deletions guides/upgrading/v0.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ defmodule MyAppWeb.Admin.ItemActions.Delete do

socket =
try do
{:ok, _items} =
{:ok, _count} =
Backpex.Resource.update_all(
socket.assigns,
items,
Expand Down Expand Up @@ -100,7 +100,7 @@ defmodule MyAppWeb.Admin.ItemActions.Delete do

socket =
try do
{:ok, _items} =
{:ok, _count} =
Backpex.Resource.update_all(
socket.assigns,
items,
Expand All @@ -126,4 +126,4 @@ Note that the data is now casted. Therefore you now have atom keys instead of st

## Refactor the use of icons

We have refactored the way we use icons in Backpex. Previously, we installed [the Heroicons hex.pm package](https://hex.pm/packages/heroicons). We now require a Tailwind plugin that generates the styles for a new `Backpex.HTML.CoreComponents.icon/1` component. This is the default way of using heroicons in new Phoenix projects. We documented the new way in the [installation guide](get_started/installation.md#provide-tailwind-plugin-for-icons).
We have refactored the way we use icons in Backpex. Previously, we installed [the Heroicons hex.pm package](https://hex.pm/packages/heroicons). We now require a Tailwind plugin that generates the styles for a new `Backpex.HTML.CoreComponents.icon/1` component. This is the default way of using heroicons in new Phoenix projects. We documented the new way in the [installation guide](get_started/installation.md#provide-tailwind-plugin-for-icons).
23 changes: 8 additions & 15 deletions lib/backpex/adapter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,48 +27,41 @@ defmodule Backpex.Adapter do

Should return `nil` if no result was found.
"""
@callback get(term(), term(), term()) :: term()

@doc """
Gets a database record with the given primary key value.

Should raise an exception if no result was found.
"""
@callback get!(term(), term(), term()) :: term()
@callback get(term(), map(), module()) :: {:ok, struct() | nil} | {:error, term()}

@doc """
Returns a list of items by given criteria.
"""
@callback list(term(), term(), term(), term()) :: term()
@callback list(keyword(), map(), module()) :: {:ok, list()}

@doc """
Gets the total count of the current live_resource.
Possibly being constrained the item query and the search- and filter options.
"""
@callback count(term(), term(), term(), term()) :: term()
@callback count(keyword(), map(), module()) :: {:ok, non_neg_integer()}

@doc """
Inserts given item.
"""
@callback insert(term(), term()) :: term()
@callback insert(struct(), module()) :: {:ok, struct()} | {:error, term()}

@doc """
Updates given item.
"""
@callback update(term(), term()) :: term()
@callback update(struct(), module()) :: {:ok, struct()} | {:error, term()}

@doc """
Updates given items.
"""
@callback update_all(term(), term(), term()) :: term()
@callback update_all(list(struct()), keyword(), module()) :: {:ok, non_neg_integer()}

@doc """
Applies a change to a given item.
"""
@callback change(term(), term(), term(), term(), term(), term()) :: term()
@callback change(struct(), map(), term(), list(), module(), keyword()) :: Ecto.Changeset.t()

@doc """
Deletes multiple items.
"""
@callback delete_all(list(), term()) :: {:ok, term()} | {:error, term()}
@callback delete_all(list(struct()), module()) :: {:ok, term()} | {:error, term()}
end
33 changes: 11 additions & 22 deletions lib/backpex/adapters/ash.ex
Original file line number Diff line number Diff line change
Expand Up @@ -39,36 +39,25 @@ if Code.ensure_loaded?(Ash) do
end

@doc """
Gets a database record with the given primary key value.

Raises an error if no record was found.
Returns a list of items by given criteria.
"""
@impl Backpex.Adapter
def get!(primary_value, _assigns, live_resource) do
def list(_criteria, _assigns, live_resource) do
config = live_resource.config(:adapter_config)
primary_key = live_resource.config(:primary_key)

config[:resource]
|> Ash.Query.filter(^Ash.Expr.ref(primary_key) == ^primary_value)
|> Ash.read_one!()
end

@doc """
Returns a list of items by given criteria.
"""
@impl Backpex.Adapter
def list(_fields, _assigns, config, _criteria \\ []) do
config[:resource]
|> Ash.read!()
|> Ash.read()
end

@doc """
Returns the number of items matching the given criteria.
"""
@impl Backpex.Adapter
def count(_fields, _assigns, config, _criteria \\ []) do
def count(_criteria, _assigns, live_resource) do
config = live_resource.config(:adapter_config)

config[:resource]
|> Ash.count!()
|> Ash.count()
end

@doc """
Expand All @@ -93,31 +82,31 @@ if Code.ensure_loaded?(Ash) do
Inserts given item.
"""
@impl Backpex.Adapter
def insert(_item, _config) do
def insert(_item, _live_resource) do
raise "not implemented yet"
end

@doc """
Updates given item.
"""
@impl Backpex.Adapter
def update(_item, _config) do
def update(_item, _live_resource) do
raise "not implemented yet"
end

@doc """
Updates given items.
"""
@impl Backpex.Adapter
def update_all(_items, _updates, _config) do
def update_all(_items, _updates, _live_resource) do
raise "not implemented yet"
end

@doc """
Applies a change to a given item.
"""
@impl Backpex.Adapter
def change(_item, _attrs, _fields, _assigns, _config, _opts) do
def change(_item, _attrs, _fields, _assigns, _live_resource, _opts) do
raise "not implemented yet"
end
end
Expand Down
65 changes: 30 additions & 35 deletions lib/backpex/adapters/ecto.ex
Original file line number Diff line number Diff line change
Expand Up @@ -66,63 +66,50 @@ defmodule Backpex.Adapters.Ecto do

@doc """
Gets a database record with the given primary key value.

Returns `nil` if no result was found.
"""
@impl Backpex.Adapter
def get(primary_value, assigns, live_resource) do
config = live_resource.config(:adapter_config)
item_query = prepare_item_query(config, assigns)

record_query(primary_value, config[:schema], item_query, live_resource)
record_query(primary_value, assigns, live_resource)
|> config[:repo].one()
end

@doc """
Gets a database record with the given primary key value.

Raises `Ecto.NoResultsError` if no record was found.
"""
@impl Backpex.Adapter
def get!(primary_value, assigns, live_resource) do
config = live_resource.config(:adapter_config)
item_query = prepare_item_query(config, assigns)

record_query(primary_value, config[:schema], item_query, live_resource)
|> config[:repo].one!()
|> then(fn result -> {:ok, result} end)
end

@doc """
Returns a list of items by given criteria.
"""
@impl Backpex.Adapter
def list(fields, assigns, config, criteria \\ []) do
item_query = prepare_item_query(config, assigns)

list_query(assigns, item_query, fields, criteria)
def list(criteria, assigns, live_resource) do
list_query(criteria, assigns, live_resource)
|> assigns.repo.all()
|> then(fn items -> {:ok, items} end)
end

@doc """
Returns the number of items matching the given criteria.
"""
@impl Backpex.Adapter
def count(fields, assigns, config, criteria \\ []) do
item_query = prepare_item_query(config, assigns)
def count(criteria, assigns, live_resource) do
config = live_resource.config(:adapter_config)

list_query(assigns, item_query, fields, criteria)
list_query(criteria, assigns, live_resource)
|> exclude(:preload)
|> subquery()
|> config[:repo].aggregate(:count)
|> then(fn count -> {:ok, count} end)
end

@doc """
Returns the main database query for selecting a list of items by given criteria.

TODO: Should be private.
"""
def list_query(assigns, item_query, fields, criteria \\ []) do
def list_query(criteria, assigns, live_resource) do
%{schema: schema, full_text_search: full_text_search} = assigns
config = live_resource.config(:adapter_config)
item_query = prepare_item_query(config, assigns)
fields = live_resource.validated_fields()
associations = associations(fields, schema)

schema
Expand Down Expand Up @@ -265,7 +252,9 @@ defmodule Backpex.Adapters.Ecto do
Inserts given item.
"""
@impl Backpex.Adapter
def insert(item, config) do
def insert(item, live_resource) do
config = live_resource.config(:adapter_config)

item
|> config[:repo].insert()
end
Expand All @@ -274,7 +263,9 @@ defmodule Backpex.Adapters.Ecto do
Updates given item.
"""
@impl Backpex.Adapter
def update(item, config) do
def update(item, live_resource) do
config = live_resource.config(:adapter_config)

item
|> config[:repo].update()
end
Expand All @@ -296,7 +287,8 @@ defmodule Backpex.Adapters.Ecto do
Applies a change to a given item.
"""
@impl Backpex.Adapter
def change(item, attrs, fields, assigns, config, opts) do
def change(item, attrs, fields, assigns, live_resource, opts) do
config = live_resource.config(:adapter_config)
assocs = Keyword.get(opts, :assocs, [])
target = Keyword.get(opts, :target, nil)
action = Keyword.get(opts, :action, :validate)
Expand Down Expand Up @@ -352,19 +344,22 @@ defmodule Backpex.Adapters.Ecto do
&query_fun.(&1, assigns.live_action, assigns)
end

defp record_query(id, schema, item_query, live_resource) do
defp record_query(primary_value, assigns, live_resource) do
config = live_resource.config(:adapter_config)
item_query = prepare_item_query(config, assigns)

fields = live_resource.validated_fields()
schema_name = name_by_schema(schema)
schema_name = name_by_schema(config[:schema])
primary_key = live_resource.config(:primary_key)
primary_type = schema.__schema__(:type, primary_key)
associations = associations(fields, schema)
primary_type = config[:schema].__schema__(:type, primary_key)
associations = associations(fields, config[:schema])

from(item in schema, as: ^schema_name, distinct: field(item, ^primary_key))
from(item in config[:schema], as: ^schema_name, distinct: field(item, ^primary_key))
|> item_query.()
|> maybe_join(associations)
|> maybe_preload(associations, fields)
|> maybe_merge_dynamic_fields(fields)
|> where_id(schema_name, primary_key, primary_type, id)
|> where_id(schema_name, primary_key, primary_type, primary_value)
end

defp where_id(query, schema_name, id_field, :id, id) do
Expand Down
13 changes: 3 additions & 10 deletions lib/backpex/exceptions.ex
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
defmodule Backpex.NoResourceError do
defmodule Backpex.NoResultsError do
@moduledoc """
Raised when resource can not be found.

If you are seeing this error, you should check if you provided the correct identifier for the requested resource.
Raised when no results can be found.
"""

defexception message: "Resource not found", plug_status: 404

def exception(opts) do
name = Keyword.fetch!(opts, :name)
%__MODULE__{message: "no resource found for:\n\n#{inspect(name)}"}
end
defexception message: "No results could be found", plug_status: 404
end

defmodule Backpex.ForbiddenError do
Expand Down
4 changes: 2 additions & 2 deletions lib/backpex/live_components/form_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ defmodule Backpex.FormComponent do
defp handle_save(socket, key, params, save_type \\ "save")

defp handle_save(socket, :new, params, save_type) do
%{assigns: %{live_resource: live_resource, fields: fields, item: item} = assigns} = socket
%{assigns: %{live_resource: live_resource, item: item} = assigns} = socket

opts = [
assocs: Map.get(assigns, :assocs, []),
Expand All @@ -222,7 +222,7 @@ defmodule Backpex.FormComponent do
end
]

case Resource.insert(item, params, fields, socket.assigns, live_resource, opts) do
case Resource.insert(item, params, socket.assigns, live_resource, opts) do
{:ok, item} ->
return_to = return_to_path(save_type, live_resource, socket, socket.assigns, item, :new)

Expand Down
Loading
Loading