diff --git a/CHANGELOG.md b/CHANGELOG.md
index 995099a8..38f83dac 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,19 @@
# Changelog
+## 0.1.0-rc.1
+
+### Enhancements
+ * Added Shared Event Handlers which are global event handlers shared among all pages.
+ That's a simple model to work with where a layout, component, or multiple pages may share the same event handler,
+ for example a newsletter subscription form in a component called in a layout doesn't need to duplicate the same
+ event handler in all pages.
+
+### Breaking Changes
+ * Remove Page Event Handlers in favor of Shared Event Handlers.
+ With Shared Event Handlers, it doesn't make sense to have page event handlers unless overriding becomes a neccessity.
+ The data is automatically migrated in a best-effort way, duplicated event handler names (from multiple pages) are
+ consolidated into a single shared event handler. See the migration `V002` for more info.
+
## 0.1.0-rc.0 (2024-08-02)
### Enhancements
diff --git a/lib/beacon/boot.ex b/lib/beacon/boot.ex
index c85cb656..208fbc33 100644
--- a/lib/beacon/boot.ex
+++ b/lib/beacon/boot.ex
@@ -55,7 +55,8 @@ defmodule Beacon.Boot do
Task.Supervisor.async(task_supervisor, fn -> Beacon.Loader.reload_live_data_module(config.site) end),
Task.Supervisor.async(task_supervisor, fn -> Beacon.Loader.reload_layouts_modules(config.site) end),
Task.Supervisor.async(task_supervisor, fn -> Beacon.Loader.reload_error_page_module(config.site) end),
- Task.Supervisor.async(task_supervisor, fn -> Beacon.Loader.reload_pages_modules(config.site, per_page: 20) end)
+ Task.Supervisor.async(task_supervisor, fn -> Beacon.Loader.reload_pages_modules(config.site, per_page: 20) end),
+ Task.Supervisor.async(task_supervisor, fn -> Beacon.Loader.reload_event_handlers_module(config.site) end)
# TODO: load main pages (order_by: path, per_page: 10) to avoid SEO issues
]
diff --git a/lib/beacon/content.ex b/lib/beacon/content.ex
index 652d5f21..a25cd795 100644
--- a/lib/beacon/content.ex
+++ b/lib/beacon/content.ex
@@ -35,6 +35,7 @@ defmodule Beacon.Content do
alias Beacon.Content.ComponentSlot
alias Beacon.Content.ComponentSlotAttr
alias Beacon.Content.ErrorPage
+ alias Beacon.Content.EventHandler
alias Beacon.Content.Layout
alias Beacon.Content.LayoutEvent
alias Beacon.Content.LayoutSnapshot
@@ -42,7 +43,6 @@ defmodule Beacon.Content do
alias Beacon.Content.LiveDataAssign
alias Beacon.Content.Page
alias Beacon.Content.PageEvent
- alias Beacon.Content.PageEventHandler
alias Beacon.Content.PageField
alias Beacon.Content.PageSnapshot
alias Beacon.Content.PageVariant
@@ -702,7 +702,7 @@ defmodule Beacon.Content do
@doc false
def create_page_snapshot(page, event) do
- page = repo(page).preload(page, [:variants, :event_handlers])
+ page = repo(page).preload(page, :variants)
attrs = %{
"site" => page.site,
@@ -1025,14 +1025,14 @@ defmodule Beacon.Content do
defp extract_page_snapshot(%{schema_version: 1, page: %Page{} = page}) do
page
|> repo(page).reload()
- |> repo(page).preload([:variants, :event_handlers], force: true)
+ |> repo(page).preload([:variants], force: true)
|> maybe_add_leading_slash()
end
defp extract_page_snapshot(%{schema_version: 2, page: %Page{} = page}) do
page
|> repo(page).reload()
- |> repo(page).preload([:variants, :event_handlers], force: true)
+ |> repo(page).preload([:variants], force: true)
|> maybe_add_leading_slash()
end
@@ -3521,77 +3521,73 @@ defmodule Beacon.Content do
## Example
- iex> change_page_event_handler(page_event_handler, %{name: "form-submit"})
- %Ecto.Changeset{data: %PageEventHandler{}}
+ iex> change_event_handler(event_handler, %{name: "form-submit"})
+ %Ecto.Changeset{data: %EventHandler{}}
"""
- @doc type: :page_event_handlers
- @spec change_page_event_handler(PageEventHandler.t(), map()) :: Changeset.t()
- def change_page_event_handler(%PageEventHandler{} = event_handler, attrs \\ %{}) do
- PageEventHandler.changeset(event_handler, attrs)
+ @doc type: :event_handlers
+ @spec change_event_handler(EventHandler.t(), map()) :: Changeset.t()
+ def change_event_handler(%EventHandler{} = event_handler, attrs \\ %{}) do
+ EventHandler.changeset(event_handler, attrs)
end
@doc """
- Creates a new page event handler and returns the page with updated `:event_handlers` association.
+ Lists all event handlers for a given Beacon site.
"""
- @doc type: :page_event_handlers
- @spec create_event_handler_for_page(Page.t(), %{name: binary(), code: binary()}) :: {:ok, Page.t()} | {:error, Changeset.t()}
- def create_event_handler_for_page(page, attrs) do
- changeset =
- page
- |> Ecto.build_assoc(:event_handlers)
- |> PageEventHandler.changeset(attrs)
- |> validate_page_event_handler(page)
-
- transact(repo(page), fn ->
- with {:ok, %PageEventHandler{}} <- repo(page).insert(changeset),
- %Page{} = page <- repo(page).preload(page, :event_handlers, force: true),
- %Page{} = page <- Lifecycle.Page.after_update_page(page) do
- {:ok, page}
- end
- end)
+ @spec list_event_handlers(Site.t()) :: [EventHandler.t()]
+ def list_event_handlers(site) do
+ repo(site).all(from eh in EventHandler, where: [site: ^site])
end
@doc """
- Updates a page event handler and returns the page with updated `:event_handlers` association.
+ Creates a new event handler.
"""
- @doc type: :page_event_handlers
- @spec update_event_handler_for_page(Page.t(), PageEventHandler.t(), map()) :: {:ok, Page.t()} | {:error, Changeset.t()}
- def update_event_handler_for_page(page, event_handler, attrs) do
+ @doc type: :event_handlers
+ @spec create_event_handler(%{name: binary(), code: binary(), site: Site.t()}) ::
+ {:ok, EventHandler.t()} | {:error, Changeset.t()}
+ def create_event_handler(attrs) do
changeset =
- event_handler
- |> PageEventHandler.changeset(attrs)
- |> validate_page_event_handler(page)
+ %EventHandler{}
+ |> EventHandler.changeset(attrs)
+ |> validate_event_handler()
- transact(repo(page), fn ->
- with {:ok, %PageEventHandler{}} <- repo(page).update(changeset),
- %Page{} = page <- repo(page).preload(page, :event_handlers, force: true),
- %Page{} = page <- Lifecycle.Page.after_update_page(page) do
- {:ok, page}
- end
- end)
+ site = Changeset.get_field(changeset, :site)
+
+ changeset
+ |> repo(site).insert()
+ |> tap(&maybe_broadcast_updated_content_event(&1, :event_handler))
+ end
+
+ @doc """
+ Updates an event handler with the given attrs.
+ """
+ @doc type: :event_handlers
+ @spec update_event_handler(EventHandler.t(), map()) :: {:ok, EventHandler.t()} | {:error, Changeset.t()}
+ def update_event_handler(event_handler, attrs) do
+ event_handler
+ |> EventHandler.changeset(attrs)
+ |> validate_event_handler()
+ |> repo(event_handler).update()
+ |> tap(&maybe_broadcast_updated_content_event(&1, :event_handler))
end
- defp validate_page_event_handler(changeset, page) do
+ defp validate_event_handler(changeset) do
code = Changeset.get_field(changeset, :code)
- metadata = %Beacon.Template.LoadMetadata{site: page.site, path: page.path}
variable_names = ["socket", "event_params"]
imports = ["Phoenix.Socket"]
- do_validate_template(changeset, :code, :elixir, code, metadata, variable_names, imports)
+ do_validate_template(changeset, :code, :elixir, code, nil, variable_names, imports)
end
@doc """
- Deletes a page event handler and returns the page with updated `:event_handlers` association.
+ Deletes an event handler.
"""
- @doc type: :page_event_handlers
- @spec delete_event_handler_from_page(Page.t(), PageEventHandler.t()) :: {:ok, Page.t()} | {:error, Changeset.t()}
- def delete_event_handler_from_page(page, event_handler) do
- with {:ok, %PageEventHandler{}} <- repo(page).delete(event_handler),
- %Page{} = page <- repo(page).preload(page, :event_handlers, force: true),
- %Page{} = page <- Lifecycle.Page.after_update_page(page) do
- {:ok, page}
- end
+ @doc type: :event_handlers
+ @spec delete_event_handler(EventHandler.t()) :: {:ok, EventHandler.t()} | {:error, Changeset.t()}
+ def delete_event_handler(event_handler) do
+ event_handler
+ |> repo(event_handler).delete()
+ |> tap(&maybe_broadcast_updated_content_event(&1, :event_handler))
end
# PAGE VARIANTS
diff --git a/lib/beacon/content/page_event_handler.ex b/lib/beacon/content/event_handler.ex
similarity index 82%
rename from lib/beacon/content/page_event_handler.ex
rename to lib/beacon/content/event_handler.ex
index 5b290fa7..67990c7e 100644
--- a/lib/beacon/content/page_event_handler.ex
+++ b/lib/beacon/content/event_handler.ex
@@ -1,4 +1,4 @@
-defmodule Beacon.Content.PageEventHandler do
+defmodule Beacon.Content.EventHandler do
@moduledoc """
Beacon's representation of a LiveView [handle_event/3](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#c:handle_event/3).
@@ -17,31 +17,29 @@ defmodule Beacon.Content.PageEventHandler do
import Ecto.Changeset
- alias Beacon.Content.Page
+ alias Beacon.Types.Site
alias Ecto.UUID
@type t :: %__MODULE__{
id: UUID.t(),
name: binary(),
code: binary(),
- page_id: UUID.t(),
- page: Page.t(),
+ site: Site.t(),
inserted_at: DateTime.t(),
updated_at: DateTime.t()
}
- schema "beacon_page_event_handlers" do
+ schema "beacon_event_handlers" do
field :name, :string
field :code, :string
-
- belongs_to :page, Page
+ field :site, Site
timestamps()
end
@doc false
def changeset(%__MODULE__{} = event_handler, attrs) do
- fields = ~w(name code)a
+ fields = ~w(name code site)a
event_handler
|> cast(attrs, fields)
diff --git a/lib/beacon/content/live_data_assign.ex b/lib/beacon/content/live_data_assign.ex
index e5763d91..004d8409 100644
--- a/lib/beacon/content/live_data_assign.ex
+++ b/lib/beacon/content/live_data_assign.ex
@@ -1,6 +1,6 @@
defmodule Beacon.Content.LiveDataAssign do
@moduledoc """
- Dynamic key/value assigns to be used by `Beacon.Content.Page` templates and updated with `Beacon.Content.PageEventHandler`s.
+ Dynamic key/value assigns to be used by `Beacon.Content.Page` templates and updated with `Beacon.Content.EventHandler`s.
LiveDataAssigns don't exist on their own, but exist as part of a `Beacon.Content.LiveData` struct.
diff --git a/lib/beacon/content/page.ex b/lib/beacon/content/page.ex
index b4f67919..ba199884 100644
--- a/lib/beacon/content/page.ex
+++ b/lib/beacon/content/page.ex
@@ -45,7 +45,6 @@ defmodule Beacon.Content.Page do
belongs_to :layout, Content.Layout
has_many :variants, Content.PageVariant
- has_many :event_handlers, Content.PageEventHandler
embeds_many :helpers, Helper
diff --git a/lib/beacon/loader.ex b/lib/beacon/loader.ex
index 846668ef..dd019bed 100644
--- a/lib/beacon/loader.ex
+++ b/lib/beacon/loader.ex
@@ -141,6 +141,10 @@ defmodule Beacon.Loader do
Loader.Stylesheet.module_name(site)
end
+ def fetch_event_handlers_module(site) do
+ Loader.EventHandlers.module_name(site)
+ end
+
def fetch_layouts_modules(site) do
Enum.map(Content.list_published_layouts(site), fn layout ->
fetch_layout_module(layout.site, layout.id)
@@ -189,6 +193,10 @@ defmodule Beacon.Loader do
GenServer.call(worker(site), :reload_stylesheet_module, @timeout)
end
+ def reload_event_handlers_module(site) do
+ GenServer.call(worker(site), :reload_event_handlers_module, @timeout)
+ end
+
def reload_layouts_modules(site) do
Enum.map(Content.list_published_layouts(site), &reload_layout_module(&1.site, &1.id))
end
@@ -288,6 +296,11 @@ defmodule Beacon.Loader do
{:noreply, config}
end
+ def handle_info({:content_updated, :event_handler, %{site: site}}, config) do
+ reload_event_handlers_module(site)
+ {:noreply, config}
+ end
+
def handle_info(msg, config) do
raise inspect(msg)
Logger.warning("Beacon.Loader can not handle the message: #{inspect(msg)}")
diff --git a/lib/beacon/loader/event_handlers.ex b/lib/beacon/loader/event_handlers.ex
new file mode 100644
index 00000000..fdaebeeb
--- /dev/null
+++ b/lib/beacon/loader/event_handlers.ex
@@ -0,0 +1,30 @@
+defmodule Beacon.Loader.EventHandlers do
+ @moduledoc false
+ alias Beacon.Loader
+
+ def module_name(site), do: Loader.module_name(site, "EventHandlers")
+
+ def build_ast(site, event_handlers) do
+ module = module_name(site)
+ functions = Enum.map(event_handlers, &build_fn/1)
+
+ quote do
+ defmodule unquote(module) do
+ import Beacon.Web, only: [assign: 2, assign: 3, assign_new: 3]
+
+ (unquote_splicing(functions))
+ end
+ end
+ end
+
+ defp build_fn(event_handler) do
+ %{site: site, name: name, code: code} = event_handler
+ Beacon.safe_code_check!(site, code)
+
+ quote do
+ def handle_event(unquote(name), var!(event_params), var!(socket)) do
+ unquote(Code.string_to_quoted!(code))
+ end
+ end
+ end
+end
diff --git a/lib/beacon/loader/page.ex b/lib/beacon/loader/page.ex
index e8262cdf..9d5a934e 100644
--- a/lib/beacon/loader/page.ex
+++ b/lib/beacon/loader/page.ex
@@ -1,11 +1,12 @@
defmodule Beacon.Loader.Page do
@moduledoc false
-
- require Logger
+ alias Beacon.Content
alias Beacon.Lifecycle
alias Beacon.Loader
alias Beacon.Template.HEEx
+ require Logger
+
def module_name(site, page_id), do: Loader.module_name(site, "Page#{page_id}")
def build_ast(site, page) do
@@ -96,7 +97,7 @@ defmodule Beacon.Loader.Page do
defp interpolate_raw_schema_record(schema, page) when is_map(schema) do
render = fn key, value, page ->
- case Beacon.Content.render_snippet(value, %{page: page, live_data: %{}}) do
+ case Content.render_snippet(value, %{page: page, live_data: %{}}) do
{:ok, new_value} ->
{key, new_value}
@@ -124,10 +125,10 @@ defmodule Beacon.Loader.Page do
end
defp handle_event(page) do
- %{site: site, event_handlers: event_handlers} = page
+ event_handlers = Content.list_event_handlers(page.site)
Enum.map(event_handlers, fn event_handler ->
- Beacon.safe_code_check!(site, event_handler.code)
+ Beacon.safe_code_check!(page.site, event_handler.code)
quote do
def handle_event(unquote(event_handler.name), var!(event_params), var!(socket)) do
diff --git a/lib/beacon/loader/worker.ex b/lib/beacon/loader/worker.ex
index fcb67466..e1b21a13 100644
--- a/lib/beacon/loader/worker.ex
+++ b/lib/beacon/loader/worker.ex
@@ -381,6 +381,14 @@ defmodule Beacon.Loader.Worker do
stop(result, config)
end
+ def handle_call(:reload_event_handlers_module, _from, config) do
+ %{site: site} = config
+ event_handlers = Content.list_event_handlers(site)
+ ast = Loader.EventHandlers.build_ast(site, event_handlers)
+ result = compile_module(site, ast, "event_handlers")
+ stop(result, config)
+ end
+
def handle_call({:reload_layout_module, layout_id}, _from, config) do
%{site: site} = config
layout = Beacon.Content.get_published_layout(site, layout_id)
diff --git a/lib/beacon/migration.ex b/lib/beacon/migration.ex
index 379240bb..f2042dc1 100644
--- a/lib/beacon/migration.ex
+++ b/lib/beacon/migration.ex
@@ -7,10 +7,10 @@ defmodule Beacon.Migration do
To install Beacon, you'll need to generate an `Ecto.Migration` that wraps calls to `Beacon.Migration`:
```
- mix ecto.gen.migration create_beacon_tables
+ $ mix ecto.gen.migration create_beacon_tables
```
- Open the generated migration in your editor and either call or delegate to `up/0` and `down/0`:
+ Open the generated migration in your editor and either call or delegate to `up/1` and `down/1`:
```elixir
defmodule MyApp.Repo.Migrations.CreateBeaconTables do
@@ -23,30 +23,96 @@ defmodule Beacon.Migration do
Then, run the migrations for your app to create the necessary Beacon tables in your database:
```
- mix ecto.migrate
+ $ mix ecto.migrate
```
- Note that `up/0` will always execute all migration steps from the initial version to the latest version,
- and those migration are idempotent.
+ By calling `up()` with no arguments, this will execute all migration steps from the initial version to
+ the latest version. As new versions are released, you may need to repeat this process, by first
+ generating a new migration:
- Check out the [your first site](https://hexdocs.pm/beacon/your-first-site.html) guide for a full example.
+ ```
+ $ mix ecto.gen.migration upgrade_beacon_tables_to_v2
+ ```
+ Then in the generated migration, you could simply call `up()` again, because the migrations are
+ idempotent, but you can be safer and more efficient by specifying the migration version to execute:
+
+ ```elixir
+ defmodule MyApp.Repo.Migrations.UpgradeBeaconTables do
+ use Ecto.Migration
+ def up, do: Beacon.Migration.up(version: 2)
+ def down, do: Beacon.Migration.down(version: 2)
+ end
+ ```
+
+ Now this migration will update to v2, but if rolled back, will only roll back the v2 changes,
+ leaving v1 tables in-place.
+
+ To see this step within the larger context of installing Beacon, check out the [your first site](your-first-site.html) guide.
"""
- # TODO: `up/1` should execute all migrations from v001 up to `@latest`
- @latest Beacon.Migrations.V001
+ @initial_version 1
+ @current_version 2
@doc """
- Run the `up` changes for all migrations between the initial version and the current version.
+ Upgrades Beacon database schemas.
+
+ If a specific version number is provided, Beacon will only upgrade to that version.
+ Otherwise, it will bring you fully up-to-date with the current version.
+
+ ## Example
+
+ Run all migrations up to the current version:
+
+ Beacon.Migration.up()
+
+ Run migrations up to a specified version:
+
+ Beacon.Migration.down(version: 2)
+
"""
- def up do
- @latest.up()
+ def up(opts \\ []) do
+ versions_to_run =
+ case opts[:version] do
+ nil -> @initial_version..@current_version//1
+ version -> @initial_version..version//1
+ end
+
+ Enum.each(versions_to_run, fn version ->
+ padded = String.pad_leading("#{version}", 3, "0")
+ module = Module.concat([Beacon.Migrations, "V#{padded}"])
+ module.up()
+ end)
end
@doc """
- Run the `down` changes for all migrations between the initial version and the current version.
+ Downgrades Beacon database schemas.
+
+ If a specific version number is provided, Beacon will only downgrade to that version (inclusive).
+ Otherwise, it will completely uninstall Beacon from your app's database.
+
+ ## Example
+
+ Run all migrations from current version down to the first:
+
+ Beacon.Migration.down()
+
+ Run migrations down to and including a specified version:
+
+ Beacon.Migration.down(version: 2)
+
"""
- def down do
- @latest.down()
+ def down(opts \\ []) do
+ versions_to_run =
+ case opts[:version] do
+ nil -> @current_version..@initial_version//-1
+ version -> @current_version..version//-1
+ end
+
+ Enum.each(versions_to_run, fn version ->
+ padded = String.pad_leading("#{version}", 3, "0")
+ module = Module.concat([Beacon.Migrations, "V#{padded}"])
+ module.down()
+ end)
end
end
diff --git a/lib/beacon/migrations/v002.ex b/lib/beacon/migrations/v002.ex
new file mode 100644
index 00000000..571f6082
--- /dev/null
+++ b/lib/beacon/migrations/v002.ex
@@ -0,0 +1,54 @@
+defmodule Beacon.Migrations.V002 do
+ @moduledoc false
+ use Ecto.Migration
+
+ import Ecto.Query
+
+ def up do
+ create_if_not_exists table(:beacon_event_handlers, primary_key: false) do
+ add :id, :binary_id, primary_key: true
+ add :name, :text, null: false
+ add :code, :text, null: false
+ add :site, :text, null: false
+
+ timestamps(type: :utc_datetime_usec)
+ end
+
+ flush()
+
+ repo().all(
+ from(peh in "beacon_page_event_handlers",
+ join: p in "beacon_pages",
+ on: peh.page_id == p.id,
+ select: %{site: p.site, name: peh.name, code: peh.code},
+ # distinct saves us memory in cases of duplicate code
+ distinct: true
+ )
+ )
+ # we still need to avoid duplicates where the code is different
+ |> Enum.group_by(&{&1.site, &1.name}, & &1.code)
+ |> Enum.map(fn {{site, name}, [code | _]} ->
+ now = DateTime.utc_now()
+ %{id: Ecto.UUID.generate() |> Ecto.UUID.dump!(), name: name, code: code, site: site, inserted_at: now, updated_at: now}
+ end)
+ |> then(&repo().insert_all("beacon_event_handlers", &1, []))
+
+ drop_if_exists table(:beacon_page_event_handlers)
+ end
+
+ def down do
+ create_if_not_exists table(:beacon_page_event_handlers, primary_key: false) do
+ add :id, :binary_id, primary_key: true
+ add :name, :text, null: false
+ add :code, :text, null: false
+
+ add :page_id, references(:beacon_pages, on_delete: :delete_all, type: :binary_id), null: false
+
+ timestamps(type: :utc_datetime_usec)
+ end
+
+ # global event handlers can't be converted back into page event handlers
+
+ drop_if_exists table(:beacon_event_handlers)
+ end
+end
diff --git a/lib/beacon/web/beacon_assigns.ex b/lib/beacon/web/beacon_assigns.ex
index 11eb99c6..f757fb49 100644
--- a/lib/beacon/web/beacon_assigns.ex
+++ b/lib/beacon/web/beacon_assigns.ex
@@ -39,6 +39,7 @@ defmodule Beacon.Web.BeaconAssigns do
private: %{
page_module: nil,
components_module: nil,
+ event_handlers_module: nil,
live_data_keys: [],
live_path: []
}
@@ -65,6 +66,7 @@ defmodule Beacon.Web.BeaconAssigns do
path_params = Beacon.Router.path_params(page.path, path_info)
page_title = Beacon.Web.DataSource.page_title(site, page.id, live_data)
components_module = Beacon.Loader.Components.module_name(site)
+ event_handlers_module = Beacon.Loader.EventHandlers.module_name(site)
%__MODULE__{
site: page.site,
@@ -74,6 +76,7 @@ defmodule Beacon.Web.BeaconAssigns do
private: %{
page_module: page_module,
components_module: components_module,
+ event_handlers_module: event_handlers_module,
live_data_keys: Map.keys(live_data),
live_path: path_info
}
diff --git a/lib/beacon/web/live/page_live.ex b/lib/beacon/web/live/page_live.ex
index 242dc242..c4b481de 100644
--- a/lib/beacon/web/live/page_live.ex
+++ b/lib/beacon/web/live/page_live.ex
@@ -64,12 +64,12 @@ defmodule Beacon.Web.PageLive do
end
def handle_event(event_name, event_params, socket) do
- %{beacon: %{private: %{page_module: page_module, live_path: live_path}}} = socket.assigns
+ %{page_module: page_module, live_path: live_path, event_handlers_module: event_handlers_module} = socket.assigns.beacon.private
%{site: site, id: page_id} = Beacon.apply_mfa(page_module, :page_assigns, [[:site, :id]])
result =
Beacon.apply_mfa(
- page_module,
+ event_handlers_module,
:handle_event,
[event_name, event_params, socket],
context: %{site: site, page_id: page_id, live_path: live_path}
diff --git a/lib/errors.ex b/lib/errors.ex
index 5ba00cde..7e666bce 100644
--- a/lib/errors.ex
+++ b/lib/errors.ex
@@ -83,7 +83,7 @@ end
defmodule Beacon.Web.ServerError do
@moduledoc """
- Raised when a `Beacon.Content.PageEventHandler` returns an invalid response.
+ Raised when a `Beacon.Content.EventHandler` returns an invalid response.
If you're seeing this error, check the code in your site's event handlers, and
ensure that each one returns `{:noreply, socket}`.
diff --git a/mix.exs b/mix.exs
index 1033cbb7..dd0c9c0a 100644
--- a/mix.exs
+++ b/mix.exs
@@ -129,7 +129,7 @@ defmodule Beacon.MixProject do
"Functions: Stylesheets": &(&1[:type] == :stylesheets),
"Functions: Components": &(&1[:type] == :components),
"Functions: Snippets": &(&1[:type] == :snippets),
- "Functions: Page Event Handlers": &(&1[:type] == :page_event_handlers),
+ "Functions: Event Handlers": &(&1[:type] == :event_handlers),
"Functions: Error Pages": &(&1[:type] == :error_pages),
"Functions: Live Data": &(&1[:type] == :live_data)
],
@@ -164,6 +164,7 @@ defmodule Beacon.MixProject do
Beacon.Content.ComponentSlot,
Beacon.Content.ComponentSlotAttr,
Beacon.Content.ErrorPage,
+ Beacon.Content.EventHandler,
Beacon.Content.Layout,
Beacon.Content.LayoutEvent,
Beacon.Content.LayoutSnapshot,
@@ -173,7 +174,6 @@ defmodule Beacon.MixProject do
Beacon.Content.Page.Event,
Beacon.Content.Page.Helper,
Beacon.Content.PageEvent,
- Beacon.Content.PageEventHandler,
Beacon.Content.PageSnapshot,
Beacon.Content.PageVariant,
Beacon.Content.Stylesheet,
diff --git a/test/beacon/content_test.exs b/test/beacon/content_test.exs
index 49b1456a..c8a2db1e 100644
--- a/test/beacon/content_test.exs
+++ b/test/beacon/content_test.exs
@@ -6,13 +6,13 @@ defmodule Beacon.ContentTest do
alias Beacon.Content
alias Beacon.Content.Component
alias Beacon.Content.ErrorPage
+ alias Beacon.Content.EventHandler
alias Beacon.Content.Layout
alias Beacon.Content.LayoutEvent
alias Beacon.Content.LayoutSnapshot
alias Beacon.Content.LiveData
alias Beacon.Content.Page
alias Beacon.Content.PageEvent
- alias Beacon.Content.PageEventHandler
alias Beacon.Content.PageSnapshot
alias Beacon.Content.PageVariant
alias Beacon.BeaconTest.Repo
@@ -597,33 +597,29 @@ defmodule Beacon.ContentTest do
end
describe "event_handlers" do
- test "create event handler OK" do
- page = page_fixture()
- attrs = %{name: "Foo", code: "{:noreply, socket}"}
+ test "list_event_handlers/1" do
+ event_handlers = for _ <- 1..3, do: event_handler_fixture(site: :my_site)
- assert {:ok, %Page{event_handlers: [event_handler]}} = Content.create_event_handler_for_page(page, attrs)
- assert %PageEventHandler{name: "Foo", code: "{:noreply, socket}"} = event_handler
- end
+ result = Content.list_event_handlers(:my_site)
- test "create triggers after_update_page lifecycle" do
- page = page_fixture(site: :lifecycle_test)
- attrs = %{name: "Foo", code: "{:noreply, socket}"}
+ assert Enum.sort(event_handlers) == Enum.sort(result)
+ end
- {:ok, %Page{}} = Content.create_event_handler_for_page(page, attrs)
+ test "create event handler OK" do
+ attrs = %{name: "Foo", code: "{:noreply, socket}", site: :my_site}
- assert_receive :lifecycle_after_update_page
+ assert {:ok, event_handler} = Content.create_event_handler(attrs)
+ assert %EventHandler{name: "Foo", code: "{:noreply, socket}"} = event_handler
end
test "create validates elixir code" do
- page = page_fixture(%{format: :heex})
-
- attrs = %{name: "test", code: "[1)"}
- assert {:error, %{errors: [error]}} = Content.create_event_handler_for_page(page, attrs)
+ attrs = %{name: "test", code: "[1)", site: :my_site}
+ assert {:error, %{errors: [error]}} = Content.create_event_handler(attrs)
{:code, {_, [compilation_error: compilation_error]}} = error
assert compilation_error =~ "unexpected token: )"
- attrs = %{name: "test", code: "if true, do false"}
- assert {:error, %{errors: [error]}} = Content.create_event_handler_for_page(page, attrs)
+ attrs = %{name: "test", code: "if true, do false", site: :my_site}
+ assert {:error, %{errors: [error]}} = Content.create_event_handler(attrs)
{:code, {_, [compilation_error: compilation_error]}} = error
assert compilation_error =~ "unexpected reserved word: do"
@@ -633,39 +629,28 @@ defmodule Beacon.ContentTest do
{:noreply, assign(socket, res: res)}
|
- attrs = %{name: "test", code: code}
- assert {:ok, _} = Content.create_event_handler_for_page(page, attrs)
+ attrs = %{name: "test", code: code, site: :my_site}
+ assert {:ok, _} = Content.create_event_handler(attrs)
end
test "update event handler OK" do
- page = page_fixture(%{format: :heex})
- event_handler = page_event_handler_fixture(%{page: page})
+ event_handler = event_handler_fixture()
attrs = %{name: "Changed Name", code: "{:noreply, assign(socket, foo: :bar)}"}
- assert {:ok, %Page{event_handlers: [updated_event_handler]}} = Content.update_event_handler_for_page(page, event_handler, attrs)
- assert %PageEventHandler{name: "Changed Name", code: "{:noreply, assign(socket, foo: :bar)}"} = updated_event_handler
- end
-
- test "update triggers after_update_page lifecycle" do
- page = page_fixture(site: :lifecycle_test)
- event_handler = page_event_handler_fixture(%{page: page})
-
- {:ok, %Page{}} = Content.update_event_handler_for_page(page, event_handler, %{name: "Changed"})
-
- assert_receive :lifecycle_after_update_page
+ assert {:ok, updated_event_handler} = Content.update_event_handler(event_handler, attrs)
+ assert %EventHandler{name: "Changed Name", code: "{:noreply, assign(socket, foo: :bar)}"} = updated_event_handler
end
test "update validates elixir code" do
- page = page_fixture(%{format: :heex})
- page_event_handler = page_event_handler_fixture(%{page: page})
+ event_handler = event_handler_fixture()
attrs = %{code: "[1)"}
- assert {:error, %{errors: [error]}} = Content.update_event_handler_for_page(page, page_event_handler, attrs)
+ assert {:error, %{errors: [error]}} = Content.update_event_handler(event_handler, attrs)
{:code, {_, [compilation_error: compilation_error]}} = error
assert compilation_error =~ "unexpected token: )"
attrs = %{code: "if true, do false"}
- assert {:error, %{errors: [error]}} = Content.update_event_handler_for_page(page, page_event_handler, attrs)
+ assert {:error, %{errors: [error]}} = Content.update_event_handler(event_handler, attrs)
{:code, {_, [compilation_error: compilation_error]}} = error
assert compilation_error =~ "unexpected reserved word: do"
@@ -676,25 +661,13 @@ defmodule Beacon.ContentTest do
|
attrs = %{code: code}
- assert {:ok, _} = Content.update_event_handler_for_page(page, page_event_handler, attrs)
+ assert {:ok, _} = Content.update_event_handler(event_handler, attrs)
end
test "delete event handler OK" do
- page = page_fixture(%{format: :heex})
- event_handler_1 = page_event_handler_fixture(%{page: page})
- event_handler_2 = page_event_handler_fixture(%{page: page})
+ %{id: id} = event_handler = event_handler_fixture()
- assert {:ok, %Page{event_handlers: [^event_handler_2]}} = Content.delete_event_handler_from_page(page, event_handler_1)
- assert {:ok, %Page{event_handlers: []}} = Content.delete_event_handler_from_page(page, event_handler_2)
- end
-
- test "delete triggers after_update_page lifecycle" do
- page = page_fixture(site: :lifecycle_test)
- event_handler = page_event_handler_fixture(%{page: page})
-
- {:ok, %Page{}} = Content.delete_event_handler_from_page(page, event_handler)
-
- assert_receive :lifecycle_after_update_page
+ assert {:ok, %{id: ^id}} = Content.delete_event_handler(event_handler)
end
end
diff --git a/test/beacon/lifecycle/template_test.exs b/test/beacon/lifecycle/template_test.exs
index b41fd0bd..ad799fba 100644
--- a/test/beacon/lifecycle/template_test.exs
+++ b/test/beacon/lifecycle/template_test.exs
@@ -16,7 +16,7 @@ defmodule Beacon.Lifecycle.TemplateTest do
end
test "render_template" do
- page = published_page_fixture(site: "my_site") |> Repo.preload([:event_handlers, :variants])
+ page = published_page_fixture(site: "my_site") |> Repo.preload(:variants)
Beacon.Loader.reload_page_module(page.site, page.id)
env = Beacon.Web.PageLive.make_env(:my_site)
diff --git a/test/beacon/loader/page_test.exs b/test/beacon/loader/page_test.exs
index 6bd0ce27..bb9231ce 100644
--- a/test/beacon/loader/page_test.exs
+++ b/test/beacon/loader/page_test.exs
@@ -69,7 +69,7 @@ defmodule Beacon.Loader.PageTest do
describe "render" do
test "render primary template" do
- page = published_page_fixture(site: "my_site", path: "/1") |> Repo.preload([:event_handlers, :variants])
+ page = published_page_fixture(site: "my_site", path: "/1") |> Repo.preload(:variants)
{:ok, module} = Loader.reload_page_module(page.site, page.id)
assert %Phoenix.LiveView.Rendered{static: ["my_site#home
\n