### Setup SQL Sandbox for Hound Acceptance Tests Source: https://context7.com/phoenixframework/phoenix_ecto/llms.txt Configure the setup for Hound acceptance tests to start the SQL Sandbox owner and generate metadata, similar to Wallaby, for managing database transactions. ```elixir setup tags do pid = Ecto.Adapters.SQL.Sandbox.start_owner!(MyApp.Repo, shared: not tags[:async]) on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end) metadata = Phoenix.Ecto.SQL.Sandbox.metadata_for(MyApp.Repo, pid) Hound.start_session(metadata: metadata) :ok end ``` -------------------------------- ### Setup SQL Sandbox for Wallaby Acceptance Tests Source: https://context7.com/phoenixframework/phoenix_ecto/llms.txt Configure the AcceptanceCase to start and stop the SQL Sandbox owner and generate metadata for Wallaby to use via user-agent headers, enabling shared database connections. ```elixir use ExUnit.CaseTemplate using do quote do use Wallaby.Feature import Wallaby.Query end end setup tags do pid = Ecto.Adapters.SQL.Sandbox.start_owner!(MyApp.Repo, shared: not tags[:async]) on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end) # Generate metadata that Wallaby passes via user-agent header metadata = Phoenix.Ecto.SQL.Sandbox.metadata_for(MyApp.Repo, pid) {:ok, session} = Wallaby.start_session(metadata: metadata) {:ok, session: session} end ``` ```elixir use MyAppWeb.AcceptanceCase, async: true feature "user can sign up", %{session: session} do session |> visit("/users/new") |> fill_in(Query.text_field("Name"), with: "John Doe") |> fill_in(Query.text_field("Email"), with: "john@example.com") |> click(Query.button("Create")) |> assert_has(Query.text("User created successfully")) end ``` -------------------------------- ### Wallaby Feature Example Source: https://github.com/phoenixframework/phoenix_ecto/blob/main/README.md Example of a Wallaby test using `Wallaby.Feature` which handles Ecto Sandbox setup automatically. ```elixir defmodule MyAppWeb.PageFeature do use ExUnit.Case, async: true use Wallaby.Feature feature "shows some text", %{session: session} do session |> visit("/home") |> assert_text("Hello world!") end end ``` -------------------------------- ### Wallaby Test Case Setup for Ecto Sandbox Source: https://github.com/phoenixframework/phoenix_ecto/blob/main/README.md Manually set up the Ecto SQL sandbox owner and Wallaby session if not using `Wallaby.Feature`. ```elixir use Wallaby.DSL setup tags do pid = Ecto.Adapters.SQL.Sandbox.start_owner!(YourApp.Repo, shared: not tags[:async]) on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end) metadata = Phoenix.Ecto.SQL.Sandbox.metadata_for(YourApp.Repo, pid) {:ok, session} = Wallaby.start_session(metadata: metadata) end ``` -------------------------------- ### Start Hound Application Source: https://github.com/phoenixframework/phoenix_ecto/blob/main/README.md Ensure the Hound application is started at the top of your test/test_helper.exs file. ```elixir {:ok, _} = Application.ensure_all_started(:hound) ``` -------------------------------- ### Hound Test Case Setup for Ecto Sandbox Source: https://github.com/phoenixframework/phoenix_ecto/blob/main/README.md Set up your test case in Hound to manage the Ecto SQL sandbox owner and pass metadata to Hound's session. ```elixir use Hound.Helpers setup tags do pid = Ecto.Adapters.SQL.Sandbox.start_owner!(YourApp.Repo, shared: not tags[:async]) on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end) metadata = Phoenix.Ecto.SQL.Sandbox.metadata_for(YourApp.Repo, pid) Hound.start_session(metadata: metadata) :ok end ``` -------------------------------- ### Configuring Phoenix.Ecto.CheckRepoStatus Plug Source: https://context7.com/phoenixframework/phoenix_ecto/llms.txt Illustrates how to configure the `Phoenix.Ecto.CheckRepoStatus` plug in your Phoenix endpoint to check database connectivity and migration status. This plug provides helpful error pages in development when the database requires setup. ```elixir # lib/my_app_web/endpoint.ex defmodule MyAppWeb.Endpoint do use Phoenix.Endpoint, otp_app: :my_app # Add early in the plug pipeline, before the router plug Phoenix.Ecto.CheckRepoStatus, otp_app: :my_app plug MyAppWeb.Router end # With custom migration paths (useful for umbrella apps) plug Phoenix.Ecto.CheckRepoStatus, otp_app: :my_app, migration_paths: fn repo -> case repo do MyApp.Repo -> ["priv/repo/migrations", "priv/repo/extra_migrations"] MyApp.SecondRepo -> ["priv/second_repo/migrations"] end end # With migration lock disabled (default) for faster checks plug Phoenix.Ecto.CheckRepoStatus, otp_app: :my_app, migration_lock: false # With prefix for multi-tenant applications plug Phoenix.Ecto.CheckRepoStatus, otp_app: :my_app, prefix: "tenant_schema" # Skip table creation (for read-only DB users) plug Phoenix.Ecto.CheckRepoStatus, otp_app: :my_app, skip_table_creation: true # Errors raised when database issues detected: # - Phoenix.Ecto.StorageNotCreatedError (503) - database not created # - Phoenix.Ecto.PendingMigrationError (503) - pending migrations exist # Phoenix dev error page shows "Create database" or "Run migrations" buttons ``` -------------------------------- ### LiveView on_mount for Ecto Sandbox Source: https://context7.com/phoenixframework/phoenix_ecto/llms.txt Implement the on_mount callback in LiveAcceptance to assign and allow the Ecto sandbox connection for LiveView processes. ```elixir defmodule MyAppWeb.LiveAcceptance do import Phoenix.LiveView import Phoenix.Component def on_mount(:default, _params, _session, socket) do socket = assign_new(socket, :phoenix_ecto_sandbox, fn -> if connected?(socket), do: get_connect_info(socket, :user_agent) end) # Allow the LiveView process to access the sandbox Phoenix.Ecto.SQL.Sandbox.allow( socket.assigns.phoenix_ecto_sandbox, Ecto.Adapters.SQL.Sandbox ) {:cont, socket} end end ``` -------------------------------- ### Enable SQL Sandbox in Test Configuration Source: https://github.com/phoenixframework/phoenix_ecto/blob/main/README.md Set a flag in config/test.exs to enable the SQL sandbox for concurrent acceptance tests. ```elixir config :your_app, sql_sandbox: true ``` -------------------------------- ### Configure SQL Sandbox Plug in Endpoint Source: https://context7.com/phoenixframework/phoenix_ecto/llms.txt Enable the SQL Sandbox plug in your application's endpoint to allow concurrent acceptance tests. This plug should be added at the top of your plug pipeline, before other plugs. ```elixir config :my_app, sql_sandbox: true ``` ```elixir defmodule MyAppWeb.Endpoint do use Phoenix.Endpoint, otp_app: :my_app # Add at the TOP of endpoint, before other plugs if Application.compile_env(:my_app, :sql_sandbox) do plug Phoenix.Ecto.SQL.Sandbox end plug Plug.RequestId # ... rest of plugs plug MyAppWeb.Router end ``` -------------------------------- ### Configure LiveView Socket for User Agent and Session Source: https://context7.com/phoenixframework/phoenix_ecto/llms.txt Configure the LiveView socket in endpoint.ex to include user agent and session options in connect_info. ```elixir socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [:user_agent, session: @session_options]] ``` -------------------------------- ### Configure SQL Sandbox for External HTTP Clients Source: https://context7.com/phoenixframework/phoenix_ecto/llms.txt Expose SQL sandbox endpoints for external clients like JavaScript test suites. Configure the plug with a specific path, repository, and optional timeout. ```elixir plug Phoenix.Ecto.SQL.Sandbox, at: "/sandbox", repo: MyApp.Repo, timeout: 15_000 # optional, defaults to 15 seconds ``` ```elixir plug Phoenix.Ecto.SQL.Sandbox, at: "/sandbox", repo: [MyApp.Repo, MyApp.SecondRepo], timeout: 30_000 ``` ```elixir Ecto.Adapters.SQL.Sandbox.mode(MyApp.Repo, :manual) ``` ```bash curl -X POST http://localhost:4000/sandbox \ -H "Content-Type: application/json" \ -H "User-Agent: BeamMetadata (encoded_metadata_string)" \ -d '{"user": {"name": "Test"}}' ``` ```bash curl -X DELETE http://localhost:4000/sandbox \ -H "User-Agent: BeamMetadata (encoded_metadata_string)" ``` ```elixir {:ok, owner_pid, metadata} = Phoenix.Ecto.SQL.Sandbox.start_child(MyApp.Repo) encoded = Phoenix.Ecto.SQL.Sandbox.encode_metadata(metadata) # => "BeamMetadata (base64_encoded_data)" ``` ```elixir :ok = Phoenix.Ecto.SQL.Sandbox.stop(owner_pid) ``` ```elixir decoded = Phoenix.Ecto.SQL.Sandbox.decode_metadata(encoded) # => %{repo: MyApp.Repo, owner: #PID<...>, trap_exit: true} ``` -------------------------------- ### Socket Connect Callback for Sandbox Header Source: https://context7.com/phoenixframework/phoenix_ecto/llms.txt In the socket connect callback, extract the 'x-phoenix-sandbox' header to assign to the socket. ```elixir def connect(_params, socket, connect_info) do sandbox_header = Enum.find_value(connect_info.x_headers, fn {"x-phoenix-sandbox", val} -> val _ -> nil end) {:ok, assign(socket, :phoenix_ecto_sandbox, sandbox_header)} end ``` -------------------------------- ### Configure Socket for User Agent Source: https://context7.com/phoenixframework/phoenix_ecto/llms.txt Configure the socket in endpoint.ex to pass the user agent information during connection. ```elixir socket "/socket", MyAppWeb.UserSocket, websocket: [connect_info: [:user_agent]] ``` -------------------------------- ### Conditional Live Session for Authenticated Routes Source: https://context7.com/phoenixframework/phoenix_ecto/llms.txt Configure the live_session in router.ex to conditionally include the LiveAcceptance hook for authenticated routes when the sql_sandbox is enabled. ```elixir live_session :authenticated, on_mount: if(Application.compile_env(:my_app, :sql_sandbox), do: [MyAppWeb.LiveAcceptance], else: [] ) ++ [{MyAppWeb.Auth, :ensure_authenticated}] do live "/dashboard", DashboardLive end ``` -------------------------------- ### Add LiveView Sandbox Hook in live_view macro Source: https://context7.com/phoenixframework/phoenix_ecto/llms.txt Conditionally add the MyAppWeb.LiveAcceptance hook to the live_view macro in my_app_web.ex, only when the sql_sandbox is enabled in the test environment. ```elixir def live_view do quote do use Phoenix.LiveView # Only mount sandbox hook in test environment if Application.compile_env(:my_app, :sql_sandbox) do on_mount MyAppWeb.LiveAcceptance end end end ``` -------------------------------- ### Configure Socket for Custom Headers Source: https://context7.com/phoenixframework/phoenix_ecto/llms.txt Configure the socket in endpoint.ex to pass custom headers instead of the user agent. ```elixir socket "/socket", MyAppWeb.UserSocket, websocket: [connect_info: [:x_headers]] ``` -------------------------------- ### Add phoenix_ecto Dependency Source: https://github.com/phoenixframework/phoenix_ecto/blob/main/README.md Add the phoenix_ecto library to your project's dependencies in mix.exs. ```elixir def deps do [{:phoenix_ecto, "~> 4.0"}] end ``` -------------------------------- ### Allow Ecto Sandbox in RoomChannel Source: https://context7.com/phoenixframework/phoenix_ecto/llms.txt In RoomChannel, allow access to the Ecto sandbox during channel join for testing purposes. ```elixir defmodule MyAppWeb.RoomChannel do use Phoenix.Channel def join("room:" <> room_id, _payload, socket) do # Allow sandbox access at channel join allow_ecto_sandbox(socket) {:ok, socket} end defp allow_ecto_sandbox(socket) do Phoenix.Ecto.SQL.Sandbox.allow( socket.assigns.phoenix_ecto_sandbox, Ecto.Adapters.SQL.Sandbox ) end end ``` -------------------------------- ### Conditionally Add SQL Sandbox Plug Source: https://github.com/phoenixframework/phoenix_ecto/blob/main/README.md Use the sql_sandbox flag to conditionally add the Phoenix.Ecto.SQL.Sandbox plug to your application's endpoint in lib/your_app/endpoint.ex. Ensure this plug is placed before any plugs that access the database. ```elixir if Application.get_env(:your_app, :sql_sandbox) do plug Phoenix.Ecto.SQL.Sandbox end ``` -------------------------------- ### Add Wallaby Dependency for Acceptance Tests Source: https://github.com/phoenixframework/phoenix_ecto/blob/main/README.md Include Wallaby as a dependency in your mix.exs file, specifying it only for the test environment. ```elixir {:wallaby, "~> 0.25", only: :test} ``` -------------------------------- ### Add Hound Dependency for Acceptance Tests Source: https://github.com/phoenixframework/phoenix_ecto/blob/main/README.md Include Hound as a dependency in your mix.exs file to write concurrent acceptance tests. ```elixir {:hound, "~> 1.0"} ``` -------------------------------- ### Integrate SQL Sandbox with Phoenix Channels Source: https://context7.com/phoenixframework/phoenix_ecto/llms.txt Store sandbox metadata received from connection info in the socket assigns for use in Phoenix Channels, allowing WebSocket connections to share the same database sandbox session. ```elixir defmodule MyAppWeb.UserSocket do use Phoenix.Socket channel "room:*", MyAppWeb.RoomChannel # Store sandbox metadata from connection info @impl true def connect(_params, socket, connect_info) do {:ok, assign(socket, :phoenix_ecto_sandbox, connect_info.user_agent)} end @impl true def id(_socket), do: nil end ``` -------------------------------- ### Safe Rendering of Decimal Types with Phoenix.HTML.Safe Source: https://context7.com/phoenixframework/phoenix_ecto/llms.txt Shows how Decimal types from Ecto are automatically handled by Phoenix.HTML.Safe for safe rendering in templates. This ensures that decimal values are escaped correctly to prevent XSS vulnerabilities. ```elixir # Decimal values can be safely rendered in templates price = Decimal.new("19.99") Phoenix.HTML.html_escape(price) # => {:safe, "19.99"} # In templates, decimals render safely without explicit conversion # product.heex

Price: <%= @product.price %>

# Renders:

Price: 19.99

# Also works with error messages containing decimals changeset = %MyApp.Product{} |> Ecto.Changeset.cast(%{}, []) |> Ecto.Changeset.add_error(:price, "must be greater than %{count}", count: Decimal.new(18)) # Error message renders correctly with decimal interpolated ``` -------------------------------- ### Inferring Input Types and Validations with Ecto Source: https://context7.com/phoenixframework/phoenix_ecto/llms.txt Demonstrates how Phoenix.HTML.FormData infers HTML input types and validation attributes from Ecto schema field types and changeset validations. Use this to automatically generate form elements that match your Ecto schema. ```elixir defmodule MyApp.Product do use Ecto.Schema import Ecto.Changeset schema "products" do field :name, :string field :price, :decimal field :quantity, :integer field :available, :boolean field :release_date, :date field :created_at, :naive_datetime end def changeset(product, attrs) do product |> cast(attrs, [:name, :price, :quantity, :available, :release_date]) |> validate_required([:name, :quantity]) |> validate_length(:name, min: 2, max: 100) |> validate_number(:quantity, greater_than: 0, less_than: 10000) |> validate_number(:price, greater_than_or_equal_to: 0) end end changeset = MyApp.Product.changeset(%MyApp.Product{}, %{}) form = Phoenix.HTML.FormData.to_form(changeset, []) # Input type inference based on Ecto field types Phoenix.HTML.Form.input_type(form, :quantity) # => :number_input Phoenix.HTML.Form.input_type(form, :available) # => :checkbox Phoenix.HTML.Form.input_type(form, :release_date) # => :date_select Phoenix.HTML.Form.input_type(form, :created_at) # => :datetime_select Phoenix.HTML.Form.input_type(form, :name) # => :text_input Phoenix.HTML.Form.input_type(form, :price) # => :text_input (decimals/floats) # Validation attributes for HTML5 form validation Phoenix.HTML.Form.input_validations(form, :name) # => [required: true, maxlength: 100, minlength: 2] Phoenix.HTML.Form.input_validations(form, :quantity) # => [required: true, step: 1, min: 1, max: 9999] Phoenix.HTML.Form.input_validations(form, :price) # => [required: false, step: "any", min: 0] # Input value retrieval respects changes > params > data precedence changeset = %MyApp.Product{name: "Original", quantity: 10} |> MyApp.Product.changeset(%{"quantity" => "50"}) |> Ecto.Changeset.put_change(:quantity, 100) form = Phoenix.HTML.FormData.to_form(changeset, []) Phoenix.HTML.Form.input_value(form, :quantity) # => 100 (from changes) Phoenix.HTML.Form.input_value(form, :name) # => "Original" (from data) ``` -------------------------------- ### Phoenix.HTML.FormData for Ecto.Changeset Source: https://context7.com/phoenixframework/phoenix_ecto/llms.txt Allows Ecto changesets to be used directly with Phoenix HTML forms, automatically generating form fields, IDs, and validation attributes. ```elixir defmodule MyApp.User do use Ecto.Schema import Ecto.Changeset schema "users" do field :name, :string field :email, :string field :age, :integer end def changeset(user, attrs) do user |> cast(attrs, [:name, :email, :age]) |> validate_required([:name, :email]) |> validate_length(:name, min: 3, max: 100) |> validate_number(:age, greater_than: 0, less_than: 150) end end ``` ```elixir def new(conn, _params) do changeset = MyApp.User.changeset(%MyApp.User{}, %{}) render(conn, "new.html", changeset: changeset) end ``` ```elixir def create(conn, %{"user" => user_params}) do changeset = MyApp.User.changeset(%MyApp.User{}, user_params) case MyApp.Repo.insert(changeset) do {:ok, user} -> redirect(conn, to: ~p"/users/#{user}") {:error, changeset} -> render(conn, "new.html", changeset: changeset) end end ``` ```html <.form :let={f} for={@changeset} action={~p"/users"}> <.input field={f[:name]} label="Name" /> <.input field={f[:email]} type="email" label="Email" /> <.input field={f[:age]} type="number" label="Age" /> ``` ```elixir form = Phoenix.HTML.FormData.to_form(changeset, as: "user") ``` ```elixir # form.options => [method: "put"] # form.hidden => [id: 1] ``` ```elixir loaded_changeset = MyApp.User.changeset(%MyApp.User{__meta__: %{state: :loaded}, id: 1}, %{}) form = Phoenix.HTML.FormData.to_form(loaded_changeset, []) ``` -------------------------------- ### Nested Forms with inputs_for Source: https://context7.com/phoenixframework/phoenix_ecto/llms.txt Handle nested data structures for associations and embeds using `inputs_for` to generate sub-forms. This is useful for `has_one`, `has_many`, `embeds_one`, and `embeds_many` relationships. ```elixir defmodule MyApp.Post do use Ecto.Schema import Ecto.Changeset schema "posts" do field :title, :string has_many :comments, MyApp.Comment embeds_one :metadata, MyApp.Metadata end def changeset(post, attrs) do post |> cast(attrs, [:title]) |> cast_assoc(:comments, with: &MyApp.Comment.changeset/2) |> cast_embed(:metadata) end end ``` ```elixir defmodule MyApp.Comment do use Ecto.Schema import Ecto.Changeset schema "comments" do field :body, :string belongs_to :post, MyApp.Post end def changeset(comment, attrs) do comment |> cast(attrs, [:body]) |> validate_required([:body]) |> validate_length(:body, min: 3) end end ``` ```html <.form :let={f} for={@changeset} action={~p"/posts"}> <.input field={f[:title]} label="Title" />

Comments

<.inputs_for :let={comment_form} field={f[:comments]}> <.input field={comment_form[:body]} label="Comment" /> ``` ```elixir changeset = MyApp.Post.changeset( %MyApp.Post{comments: [%MyApp.Comment{body: "existing"}]}, %{}) parent_form = Phoenix.HTML.FormData.to_form(changeset, []) comment_forms = Phoenix.HTML.FormData.to_form( changeset, parent_form, :comments, prepend: [%MyApp.Comment{body: "new at start"}], append: [%MyApp.Comment{body: "new at end"}] ) ``` ```elixir # Returns list of forms: # [ # %Phoenix.HTML.Form{name: "post[comments][0]", ...}, # prepended # %Phoenix.HTML.Form{name: "post[comments][1]", ...}, # existing ``` -------------------------------- ### Exclude Ecto Exceptions from Plug Configuration Source: https://github.com/phoenixframework/phoenix_ecto/blob/main/README.md Configure phoenix_ecto to exclude specific Ecto exceptions from being handled by the Plug.Exception implementations. ```elixir config :phoenix_ecto, exclude_ecto_exceptions_from_plug: [Ecto.NoResultsError] ``` -------------------------------- ### Ecto Exception to HTTP Status Code Mapping Source: https://context7.com/phoenixframework/phoenix_ecto/llms.txt Details the automatic mapping of common Ecto exceptions to HTTP status codes within Phoenix controllers. This ensures that API clients and users receive appropriate error responses. ```elixir # Automatic HTTP status code mapping for Ecto exceptions: # Ecto.CastError => 400 Bad Request # Ecto.Query.CastError => 400 Bad Request ``` -------------------------------- ### Handle Ecto Errors in Controllers Source: https://context7.com/phoenixframework/phoenix_ecto/llms.txt Controllers automatically handle Ecto.NoResultsError and Ecto.StaleEntryError, mapping them to 404 and 409 responses respectively. You can exclude specific exceptions from this automatic mapping in your configuration. ```elixir def show(conn, %{"id" => id}) do # Raises Ecto.NoResultsError if not found => 404 response user = Repo.get!(User, id) render(conn, :show, user: user) end def update(conn, %{"id" => id, "user" => params}) do user = Repo.get!(User, id) # Raises Ecto.StaleEntryError on concurrent modification => 409 response user |> User.changeset(params) |> Repo.update!(stale_error_field: :lock_version) redirect(conn, to: ~p"/users/#{user}") end ``` ```elixir config :phoenix_ecto, exclude_ecto_exceptions_from_plug: [Ecto.NoResultsError] ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.