Update dependencies
phoenix 1.8.1 => 1.8.2
This commit is contained in:
@@ -461,6 +461,7 @@ mix usage_rules.search_docs "Enum.zip" --query-by title
|
|||||||
- `Ecto.Changeset.validate_number/2` **DOES NOT SUPPORT the `:allow_nil` option**. By default, Ecto validations only run if a change for the given field exists and the change value is not nil, so such as option is never needed
|
- `Ecto.Changeset.validate_number/2` **DOES NOT SUPPORT the `:allow_nil` option**. By default, Ecto validations only run if a change for the given field exists and the change value is not nil, so such as option is never needed
|
||||||
- You **must** use `Ecto.Changeset.get_field(changeset, :field)` to access changeset fields
|
- You **must** use `Ecto.Changeset.get_field(changeset, :field)` to access changeset fields
|
||||||
- Fields which are set programatically, such as `user_id`, must not be listed in `cast` calls or similar for security purposes. Instead they must be explicitly set when creating the struct
|
- Fields which are set programatically, such as `user_id`, must not be listed in `cast` calls or similar for security purposes. Instead they must be explicitly set when creating the struct
|
||||||
|
- **Always** invoke `mix ecto.gen.migration migration_name_using_underscores` when generating migration files, so the correct timestamp and conventions are applied
|
||||||
|
|
||||||
<!-- phoenix:ecto-end -->
|
<!-- phoenix:ecto-end -->
|
||||||
<!-- phoenix:elixir-start -->
|
<!-- phoenix:elixir-start -->
|
||||||
@@ -509,6 +510,18 @@ mix usage_rules.search_docs "Enum.zip" --query-by title
|
|||||||
- To debug test failures, run tests in a specific file with `mix test test/my_test.exs` or run all previously failed tests with `mix test --failed`
|
- To debug test failures, run tests in a specific file with `mix test test/my_test.exs` or run all previously failed tests with `mix test --failed`
|
||||||
- `mix deps.clean --all` is **almost never needed**. **Avoid** using it unless you have good reason
|
- `mix deps.clean --all` is **almost never needed**. **Avoid** using it unless you have good reason
|
||||||
|
|
||||||
|
## Test guidelines
|
||||||
|
|
||||||
|
- **Always use `start_supervised!/1`** to start processes in tests as it guarantees cleanup between tests
|
||||||
|
- **Avoid** `Process.sleep/1` and `Process.alive?/1` in tests
|
||||||
|
- Instead of sleeping to wait for a process to finish, **always** use `Process.monitor/1` and assert on the DOWN message:
|
||||||
|
|
||||||
|
ref = Process.monitor(pid)
|
||||||
|
assert_receive {:DOWN, ^ref, :process, ^pid, :normal}
|
||||||
|
|
||||||
|
- Instead of sleeping to synchronize before the next call, **always** use `_ = :sys.get_state/1` to ensure the process has handled prior messages
|
||||||
|
|
||||||
|
|
||||||
<!-- phoenix:elixir-end -->
|
<!-- phoenix:elixir-end -->
|
||||||
<!-- phoenix:html-start -->
|
<!-- phoenix:html-start -->
|
||||||
## phoenix:html usage
|
## phoenix:html usage
|
||||||
@@ -597,8 +610,6 @@ mix usage_rules.search_docs "Enum.zip" --query-by title
|
|||||||
- **Never** use the deprecated `live_redirect` and `live_patch` functions, instead **always** use the `<.link navigate={href}>` and `<.link patch={href}>` in templates, and `push_navigate` and `push_patch` functions LiveViews
|
- **Never** use the deprecated `live_redirect` and `live_patch` functions, instead **always** use the `<.link navigate={href}>` and `<.link patch={href}>` in templates, and `push_navigate` and `push_patch` functions LiveViews
|
||||||
- **Avoid LiveComponent's** unless you have a strong, specific need for them
|
- **Avoid LiveComponent's** unless you have a strong, specific need for them
|
||||||
- LiveViews should be named like `AppWeb.WeatherLive`, with a `Live` suffix. When you go to add LiveView routes to the router, the default `:browser` scope is **already aliased** with the `AppWeb` module, so you can just do `live "/weather", WeatherLive`
|
- LiveViews should be named like `AppWeb.WeatherLive`, with a `Live` suffix. When you go to add LiveView routes to the router, the default `:browser` scope is **already aliased** with the `AppWeb` module, so you can just do `live "/weather", WeatherLive`
|
||||||
- Remember anytime you use `phx-hook="MyHook"` and that js hook manages its own DOM, you **must** also set the `phx-update="ignore"` attribute
|
|
||||||
- **Never** write embedded `<script>` tags in HEEx. Instead always write your scripts and hooks in the `assets/js` directory and integrate them with the `assets/js/app.js` file
|
|
||||||
|
|
||||||
### LiveView streams
|
### LiveView streams
|
||||||
|
|
||||||
@@ -623,10 +634,10 @@ mix usage_rules.search_docs "Enum.zip" --query-by title
|
|||||||
messages = list_messages(filter)
|
messages = list_messages(filter)
|
||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|> assign(:messages_empty?, messages == [])
|
|> assign(:messages_empty?, messages == [])
|
||||||
# reset the stream with the new messages
|
# reset the stream with the new messages
|
||||||
|> stream(:messages, messages, reset: true)}
|
|> stream(:messages, messages, reset: true)}
|
||||||
end
|
end
|
||||||
|
|
||||||
- LiveView streams *do not support counting or empty states*. If you need to display a count, you must track it using a separate assign. For empty states, you can use Tailwind classes:
|
- LiveView streams *do not support counting or empty states*. If you need to display a count, you must track it using a separate assign. For empty states, you can use Tailwind classes:
|
||||||
@@ -640,8 +651,113 @@ mix usage_rules.search_docs "Enum.zip" --query-by title
|
|||||||
|
|
||||||
The above only works if the empty state is the only HTML block alongside the stream for-comprehension.
|
The above only works if the empty state is the only HTML block alongside the stream for-comprehension.
|
||||||
|
|
||||||
|
- When updating an assign that should change content inside any streamed item(s), you MUST re-stream the items
|
||||||
|
along with the updated assign:
|
||||||
|
|
||||||
|
def handle_event("edit_message", %{"message_id" => message_id}, socket) do
|
||||||
|
message = Chat.get_message!(message_id)
|
||||||
|
edit_form = to_form(Chat.change_message(message, %{content: message.content}))
|
||||||
|
|
||||||
|
# re-insert message so @editing_message_id toggle logic takes effect for that stream item
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> stream_insert(:messages, message)
|
||||||
|
|> assign(:editing_message_id, String.to_integer(message_id))
|
||||||
|
|> assign(:edit_form, edit_form)}
|
||||||
|
end
|
||||||
|
|
||||||
|
And in the template:
|
||||||
|
|
||||||
|
<div id="messages" phx-update="stream">
|
||||||
|
<div :for={{id, message} <- @streams.messages} id={id} class="flex group">
|
||||||
|
{message.username}
|
||||||
|
<%= if @editing_message_id == message.id do %>
|
||||||
|
<%!-- Edit mode --%>
|
||||||
|
<.form for={@edit_form} id="edit-form-#{message.id}" phx-submit="save_edit">
|
||||||
|
...
|
||||||
|
</.form>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
- **Never** use the deprecated `phx-update="append"` or `phx-update="prepend"` for collections
|
- **Never** use the deprecated `phx-update="append"` or `phx-update="prepend"` for collections
|
||||||
|
|
||||||
|
### LiveView JavaScript interop
|
||||||
|
|
||||||
|
- Remember anytime you use `phx-hook="MyHook"` and that JS hook manages its own DOM, you **must** also set the `phx-update="ignore"` attribute
|
||||||
|
- **Always** provide an unique DOM id alongside `phx-hook` otherwise a compiler error will be raised
|
||||||
|
|
||||||
|
LiveView hooks come in two flavors, 1) colocated js hooks for "inline" scripts defined inside HEEx,
|
||||||
|
and 2) external `phx-hook` annotations where JavaScript object literals are defined and passed to the `LiveSocket` constructor.
|
||||||
|
|
||||||
|
#### Inline colocated js hooks
|
||||||
|
|
||||||
|
**Never** write raw embedded `<script>` tags in heex as they are incompatible with LiveView.
|
||||||
|
Instead, **always use a colocated js hook script tag (`:type={Phoenix.LiveView.ColocatedHook}`)
|
||||||
|
when writing scripts inside the template**:
|
||||||
|
|
||||||
|
<input type="text" name="user[phone_number]" id="user-phone-number" phx-hook=".PhoneNumber" />
|
||||||
|
<script :type={Phoenix.LiveView.ColocatedHook} name=".PhoneNumber">
|
||||||
|
export default {
|
||||||
|
mounted() {
|
||||||
|
this.el.addEventListener("input", e => {
|
||||||
|
let match = this.el.value.replace(/\D/g, "").match(/^(\d{3})(\d{3})(\d{4})$/)
|
||||||
|
if(match) {
|
||||||
|
this.el.value = `${match[1]}-${match[2]}-${match[3]}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
- colocated hooks are automatically integrated into the app.js bundle
|
||||||
|
- colocated hooks names **MUST ALWAYS** start with a `.` prefix, i.e. `.PhoneNumber`
|
||||||
|
|
||||||
|
#### External phx-hook
|
||||||
|
|
||||||
|
External JS hooks (`<div id="myhook" phx-hook="MyHook">`) must be placed in `assets/js/` and passed to the
|
||||||
|
LiveSocket constructor:
|
||||||
|
|
||||||
|
const MyHook = {
|
||||||
|
mounted() { ... }
|
||||||
|
}
|
||||||
|
let liveSocket = new LiveSocket("/live", Socket, {
|
||||||
|
hooks: { MyHook }
|
||||||
|
});
|
||||||
|
|
||||||
|
#### Pushing events between client and server
|
||||||
|
|
||||||
|
Use LiveView's `push_event/3` when you need to push events/data to the client for a phx-hook to handle.
|
||||||
|
**Always** return or rebind the socket on `push_event/3` when pushing events:
|
||||||
|
|
||||||
|
# re-bind socket so we maintain event state to be pushed
|
||||||
|
socket = push_event(socket, "my_event", %{...})
|
||||||
|
|
||||||
|
# or return the modified socket directly:
|
||||||
|
def handle_event("some_event", _, socket) do
|
||||||
|
{:noreply, push_event(socket, "my_event", %{...})}
|
||||||
|
end
|
||||||
|
|
||||||
|
Pushed events can then be picked up in a JS hook with `this.handleEvent`:
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.handleEvent("my_event", data => console.log("from server:", data));
|
||||||
|
}
|
||||||
|
|
||||||
|
Clients can also push an event to the server and receive a reply with `this.pushEvent`:
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.el.addEventListener("click", e => {
|
||||||
|
this.pushEvent("my_event", { one: 1 }, reply => console.log("got reply from server:", reply));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Where the server handled it via:
|
||||||
|
|
||||||
|
def handle_event("my_event", %{"one" => 1}, socket) do
|
||||||
|
{:reply, %{two: 2}, socket}
|
||||||
|
end
|
||||||
|
|
||||||
### LiveView tests
|
### LiveView tests
|
||||||
|
|
||||||
- `Phoenix.LiveViewTest` module and `LazyHTML` (included) for making your assertions
|
- `Phoenix.LiveViewTest` module and `LazyHTML` (included) for making your assertions
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
"oban_met": {:hex, :oban_met, "1.0.4", "c5b47b51aa23da0c81591ca7bf01fcf2f8d41e62436fa1af6a1af868dd20e2b1", [:mix], [{:oban, "~> 2.19", [hex: :oban, repo: "hexpm", optional: false]}], "hexpm", "fb60eec6000a285ef91dfcd6fef91a16cd34846be5dc604f00fc445fe47e3e53"},
|
"oban_met": {:hex, :oban_met, "1.0.4", "c5b47b51aa23da0c81591ca7bf01fcf2f8d41e62436fa1af6a1af868dd20e2b1", [:mix], [{:oban, "~> 2.19", [hex: :oban, repo: "hexpm", optional: false]}], "hexpm", "fb60eec6000a285ef91dfcd6fef91a16cd34846be5dc604f00fc445fe47e3e53"},
|
||||||
"oban_web": {:hex, :oban_web, "2.11.6", "53933cb4253c4d9f1098ee311c06f07935259f0e564dcf2d66bae4cc98e317fe", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, "~> 2.19", [hex: :oban, repo: "hexpm", optional: false]}, {:oban_met, "~> 1.0", [hex: :oban_met, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "576d94b705688c313694c2c114ca21aa0f8f2ad1b9ca45c052c5ba316d3e8d10"},
|
"oban_web": {:hex, :oban_web, "2.11.6", "53933cb4253c4d9f1098ee311c06f07935259f0e564dcf2d66bae4cc98e317fe", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, "~> 2.19", [hex: :oban, repo: "hexpm", optional: false]}, {:oban_met, "~> 1.0", [hex: :oban_met, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "576d94b705688c313694c2c114ca21aa0f8f2ad1b9ca45c052c5ba316d3e8d10"},
|
||||||
"owl": {:hex, :owl, "0.13.0", "26010e066d5992774268f3163506972ddac0a7e77bfe57fa42a250f24d6b876e", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "59bf9d11ce37a4db98f57cb68fbfd61593bf419ec4ed302852b6683d3d2f7475"},
|
"owl": {:hex, :owl, "0.13.0", "26010e066d5992774268f3163506972ddac0a7e77bfe57fa42a250f24d6b876e", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "59bf9d11ce37a4db98f57cb68fbfd61593bf419ec4ed302852b6683d3d2f7475"},
|
||||||
"phoenix": {:hex, :phoenix, "1.8.1", "865473a60a979551a4879db79fbfb4503e41cd809e77c85af79716578b6a456d", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "84d77d2b2e77c3c7e7527099bd01ef5c8560cd149c036d6b3a40745f11cd2fb2"},
|
"phoenix": {:hex, :phoenix, "1.8.2", "75aba5b90081d88a54f2fc6a26453d4e76762ab095ff89be5a3e7cb28bff9300", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "19ea65b4064f17b1ab0515595e4d0ea65742ab068259608d5d7b139a73f47611"},
|
||||||
"phoenix_ecto": {:hex, :phoenix_ecto, "4.7.0", "75c4b9dfb3efdc42aec2bd5f8bccd978aca0651dbcbc7a3f362ea5d9d43153c6", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "1d75011e4254cb4ddf823e81823a9629559a1be93b4321a6a5f11a5306fbf4cc"},
|
"phoenix_ecto": {:hex, :phoenix_ecto, "4.7.0", "75c4b9dfb3efdc42aec2bd5f8bccd978aca0651dbcbc7a3f362ea5d9d43153c6", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "1d75011e4254cb4ddf823e81823a9629559a1be93b4321a6a5f11a5306fbf4cc"},
|
||||||
"phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"},
|
"phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"},
|
||||||
"phoenix_html_helpers": {:hex, :phoenix_html_helpers, "1.0.1", "7eed85c52eff80a179391036931791ee5d2f713d76a81d0d2c6ebafe1e11e5ec", [:mix], [{:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cffd2385d1fa4f78b04432df69ab8da63dc5cf63e07b713a4dcf36a3740e3090"},
|
"phoenix_html_helpers": {:hex, :phoenix_html_helpers, "1.0.1", "7eed85c52eff80a179391036931791ee5d2f713d76a81d0d2c6ebafe1e11e5ec", [:mix], [{:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cffd2385d1fa4f78b04432df69ab8da63dc5cf63e07b713a4dcf36a3740e3090"},
|
||||||
|
|||||||
Reference in New Issue
Block a user