Skip to content

Commit

Permalink
Merge branch 'main' into genauth
Browse files Browse the repository at this point in the history
  • Loading branch information
SteffenDE authored Nov 26, 2024
2 parents 505db1e + 394e501 commit 0ad752e
Show file tree
Hide file tree
Showing 83 changed files with 655 additions and 346 deletions.
16 changes: 8 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ jobs:
- elixir: 1.14.5
otp: 25.3.2.9

- elixir: 1.17.2
otp: 27.0
- elixir: 1.17.3
otp: 27.1
lint: true
installer: true

Expand Down Expand Up @@ -111,13 +111,13 @@ jobs:
matrix:
include:
# look for correct alpine image here: https://hub.docker.com/r/hexpm/elixir/tags
- elixir: 1.14.5
otp: 25.3.2.12
suffix: "alpine-3.19.1"
- elixir: 1.15.8
otp: 24.3.4.17
suffix: "alpine-3.20.3"

- elixir: 1.16.2
otp: 26.2.5
suffix: "alpine-3.19.1"
- elixir: 1.17.3
otp: 27.1.2
suffix: "alpine-3.20.3"

container:
image: hexpm/elixir:${{ matrix.elixir }}-erlang-${{ matrix.otp }}-${{ matrix.suffix }}
Expand Down
10 changes: 6 additions & 4 deletions guides/authentication/mix_phx_gen_auth.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# mix phx.gen.auth

> This guide assumes that you have gone through the [introductory guides](overview.html) and have a Phoenix application [up and running](up_and_running.html).
The `mix phx.gen.auth` command generates a flexible, pre-built authentication system into your Phoenix app. This generator allows you to quickly move past the task of adding authentication to your codebase and stay focused on the real-world problem your application is trying to solve.

## Getting started

> Before running this command, consider committing your work as it generates multiple files.
Let's start by running the following command from the root of our app (or `apps/my_app_web` in an umbrella app):
Let's start by running the following command from the root of our app:

```console
$ mix phx.gen.auth Accounts User users
Expand All @@ -28,10 +30,10 @@ Since this generator installed additional dependencies in `mix.exs`, let's fetch
$ mix deps.get
```

Now we need to verify the database connection details for the development and test environments in `config/` so the migrator and tests can run properly. Then run the following to create the database:
Now run the pending repository migrations:

```console
$ mix ecto.setup
$ mix ecto.migrate
```

Let's run the tests to make sure our new authentication system works as expected.
Expand All @@ -40,7 +42,7 @@ Let's run the tests to make sure our new authentication system works as expected
$ mix test
```

And finally, let's start our Phoenix server and try it out.
And finally, let's start our Phoenix server and try it out (note the new `Register` and `Log in` links at the top right of the default page).

```console
$ mix phx.server
Expand Down
27 changes: 13 additions & 14 deletions guides/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,31 +62,30 @@ Next we need to update `show.html.heex`:
</section>
```

When we reload `http://localhost:4000/hello/Frank`, we should see the same content as before.

Since templates are embedded inside the `HelloHTML` module, we were able to invoke the view function simply as `<.greet messenger="..." />`.

If the component was defined elsewhere, we can also type `<HelloWeb.HelloHTML.greet messenger="..." />`.
When we reload `http://localhost:4000/hello/Frank`, we should see the same content as before. Since the `show.html.heex` template is embedded within the `HelloHTML` module, we were able to invoke the function component directly as `<.greet messenger="..." />`. If the component was defined elsewhere, we would need to give its full name: `<HelloWeb.HelloHTML.greet messenger="..." />`.

By declaring attributes as required, Phoenix will warn at compile time if we call the `<.greet />` component without passing attributes. If an attribute is optional, you can specify the `:default` option with a value:

```
attr :messenger, :string, default: nil
```

Although this is a quick example, it shows the different roles function components play in Phoenix:

* Function components can be defined as functions that receive `assigns` as argument and call the `~H` sigil, as we did in `greet/1`

* Function components can be embedded from template files, that's how we load `show.html.heex` into `HelloWeb.HelloHTML`
Overall, function components are the essential building block of Phoenix rendering stack. The majority of the times, they are functions that receive a single argument called `assigns` and call the `~H` sigil, as we did in `greet/1`. They can also be invoked from templates, with compile-time validation of its attributes declared via `attr`.

* Function components can declare which attributes are expected, which are validated at compilation time
In fact, every template embedded into `HelloHTML` is a function component in itself. `show.html.heex` simply becomes a function component named `show`. This also means you can directly render function components directly from the controller, skipping the `show.html.heex` template:

* Function components can be directly rendered from controllers
```elixir
def HelloWeb.HelloController do
use HelloWeb, :controller

* Function components can be directly rendered from other function components, as we called `<.greet messenger={@messenger} />` from `show.html.heex`
def show(conn, %{"messenger" => messenger}) do
# Render the HelloWeb.HelloHTML.greet/1 component
render(conn, :greet, messenger: messenger)
end
end
```

And there's more. Before we go deeper, let's fully understand the expressive power behind the HEEx template language.
Next, let's fully understand the expressive power behind the HEEx template language.

## HEEx

Expand Down
14 changes: 8 additions & 6 deletions guides/contexts.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ By giving modules that expose and group related functionality the name **context

In Phoenix, contexts often encapsulate data access and data validation. They often talk to a database or APIs. Overall, think of them as boundaries to decouple and isolate parts of your application. Let's use these ideas to build out our web application. Our goal is to build an ecommerce system where we can showcase products, allow users to add products to their cart, and complete their orders.

> How to read this guide: Using the context generators is a great way for beginners and intermediate Elixir programmers alike to get up and running quickly while thoughtfully writing their applications. This guide focuses on those readers.
### Adding a Catalog Context

An ecommerce platform has wide-reaching coupling across a codebase so it's important to think about writing well-defined modules. With that in mind, our goal is to build a product catalog API that handles creating, updating, and deleting the products available in our system. We'll start off with the basic features of showcasing our products, and we will add shopping cart features later. We'll see how starting with a solid foundation with isolated boundaries allows us to grow our application naturally as we add functionality.
Expand All @@ -30,9 +28,6 @@ Phoenix includes the `mix phx.gen.html`, `mix phx.gen.json`, `mix phx.gen.live`,

In order to run the context generators, we need to come up with a module name that groups the related functionality that we're building. In the [Ecto guide](ecto.html), we saw how we can use Changesets and Repos to validate and persist user schemas, but we didn't integrate this with our application at large. In fact, we didn't think about where a "user" in our application should live at all. Let's take a step back and think about the different parts of our system. We know that we'll have products to showcase on pages for sale, along with descriptions, pricing, etc. Along with selling products, we know we'll need to support carting, order checkout, and so on. While the products being purchased are related to the cart and checkout processes, showcasing a product and managing the *exhibition* of our products is distinctly different than tracking what a user has placed in their cart or how an order is placed. A `Catalog` context is a natural place for the management of our product details and the showcasing of those products we have for sale.

> #### Naming things is hard {: .tip}
> If you're stuck when trying to come up with a context name when the grouped functionality in your system isn't yet clear, you can simply use the plural form of the resource you're creating. For example, a `Products` context for managing products. As you grow your application and the parts of your system become clear, you can simply rename the context to a more refined one.
To jump-start our catalog context, we'll use `mix phx.gen.html` which creates a context module that wraps up Ecto access for creating, updating, and deleting products, along with web files like controllers and templates for the web interface into our context. Run the following command at your project root:

```console
Expand Down Expand Up @@ -60,7 +55,6 @@ Add the resource to your browser scope in lib/hello_web/router.ex:

resources "/products", ProductController


Remember to update your repository by running migrations:

$ mix ecto.migrate
Expand Down Expand Up @@ -123,6 +117,14 @@ Views: 0

If we follow the "Back" link, we get a list of all products, which should contain the one we just created. Likewise, we can update this record or delete it. Now that we've seen how it works in the browser, it's time to take a look at the generated code.

> #### Naming things is hard {: .tip}
>
> When starting a web application, it may be hard to draw lines or name its different contexts, especially when the domain you are working with is not as well established as e-commerce.
>
> If you're stuck when defining or naming a context, you can simply create a new context using the plural form of the resource you're creating. For example, a `Products` context for managing products. You will find that, even in such cases, you will organically discover other resources that belong to the `Products` context, such as categories or image galleries.
>
> As your applications grows and the different parts of your system become clear, you can simply rename the context or move resources around. The beauty of Elixir modules is that they are stateless, so moving them around should be simply a matter of renaming the module names (and renaming the files for consistency).
## Starting with generators

That little `mix phx.gen.html` command packed a surprising punch. We got a lot of functionality out-of-the-box for creating, updating, and deleting products in our catalog. This is far from a full-featured app, but remember, generators are first and foremost learning tools and a starting point for you to begin building real features. Code generation can't solve all your problems, but it will teach you the ins and outs of Phoenix and nudge you towards the proper mindset when designing your application.
Expand Down
2 changes: 1 addition & 1 deletion guides/controllers.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ defmodule HelloWeb.PageController do
...

def index(conn, _params) do
render(conn, :index)
render(conn, :home)
end
end
```
Expand Down
4 changes: 2 additions & 2 deletions guides/deployment/heroku.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,11 @@ The buildpack uses a predefined Elixir and Erlang version, but to avoid surprise

```console
# Elixir version
elixir_version=1.14.0
elixir_version=1.15.0

# Erlang version
# https://github.com/HashNuke/heroku-buildpack-elixir-otp-builds/blob/master/otp-versions
erlang_version=24.3
erlang_version=25.3

# Invoke assets.deploy defined in your mix.exs to deploy assets with esbuild
# Note we nuke the esbuild executable from the image
Expand Down
8 changes: 4 additions & 4 deletions guides/deployment/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,11 @@ If you call `mix phx.gen.release --docker` you'll see a new file with these cont
# - https://hub.docker.com/r/hexpm/elixir/tags - for the build image
# - https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye-20230612-slim - for the release image
# - https://pkgs.org/ - resource for finding needed packages
# - Ex: hexpm/elixir:1.14.5-erlang-25.3.2.4-debian-bullseye-20230612-slim
# - Ex: hexpm/elixir:1.15.8-erlang-25.3.2.15-debian-bookworm-20241016-slim
#
ARG ELIXIR_VERSION=1.14.5
ARG OTP_VERSION=25.3.2.4
ARG DEBIAN_VERSION=bullseye-20230612-slim
ARG ELIXIR_VERSION=1.15.8
ARG OTP_VERSION=25.3.2.15
ARG DEBIAN_VERSION=bookworm-20241016-slim

ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"
Expand Down
6 changes: 3 additions & 3 deletions guides/introduction/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ In order to build a Phoenix application, we will need a few dependencies install

Please take a look at this list and make sure to install anything necessary for your system. Having dependencies installed in advance can prevent frustrating problems later on.

## Elixir 1.14 or later
## Elixir 1.15 or later

Phoenix is written in Elixir, and our application code will also be written in Elixir. We won't get far in a Phoenix app without it! The Elixir site maintains a great [Installation Page](https://elixir-lang.org/install.html) to help.

Expand All @@ -28,13 +28,13 @@ When we install Elixir using instructions from the Elixir [Installation Page](ht

## Phoenix

To check that we are on Elixir 1.14 and Erlang 24 or later, run:
To check that we are on Elixir 1.15 and Erlang 24 or later, run:

```console
elixir -v
Erlang/OTP 24 [erts-12.0] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Elixir 1.14.0
Elixir 1.15.0
```

Once we have Elixir and Erlang, we are ready to install the Phoenix application generator:
Expand Down
2 changes: 1 addition & 1 deletion guides/introduction/up_and_running.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ And finally, we'll start the Phoenix server:

```console
$ mix phx.server
[info] Running HelloWeb.Endpoint with cowboy 2.9.0 at 127.0.0.1:4000 (http)
[info] Running HelloWeb.Endpoint with Bandit 1.5.7 at 127.0.0.1:4000 (http)
[info] Access HelloWeb.Endpoint at http://localhost:4000
[watch] build finished, watching for changes...
...
Expand Down
2 changes: 1 addition & 1 deletion guides/json_and_apis.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ end

This view is very simple. The `index` function receives all URLs, and converts them into a list of maps. Those maps are placed inside the data key at the root, exactly as we saw when interfacing with our application from `cURL`. In other words, our JSON view converts our complex data into simple Elixir data-structures. Once our view layer returns, Phoenix uses the `Jason` library to encode JSON and send the response to the client.

If you explore the remaining the controller, you will learn the `show` action is similar to the `index` one. For `create`, `update`, and `delete` actions, Phoenix uses one other important feature, called "Action fallback".
If you explore the remaining controller, you will learn the `show` action is similar to the `index` one. For `create`, `update`, and `delete` actions, Phoenix uses one other important feature, called "Action fallback".

## Action fallback

Expand Down
19 changes: 4 additions & 15 deletions installer/lib/mix/tasks/phx.new.ex
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,7 @@ defmodule Mix.Tasks.Phx.New do
Task.async(fn -> cmd(project, cmd, log: false, cd: project.web_path) end)
end)

if rebar_available?() do
cmd(project, "mix deps.compile")
end
cmd(project, "mix deps.compile")

Task.await_many(tasks, :infinity)
end
Expand Down Expand Up @@ -254,22 +252,13 @@ defmodule Mix.Tasks.Phx.New do
defp maybe_cd(path, func), do: path && File.cd!(path, func)

defp install_mix(project, install?) do
if install? && hex_available?() do
if install? do
cmd(project, "mix deps.get")
else
["$ mix deps.get"]
end
end

# TODO: Elixir v1.15 automatically installs Hex/Rebar if missing, so we can simplify this.
defp hex_available? do
Code.ensure_loaded?(Hex)
end

defp rebar_available? do
Mix.Rebar.rebar_cmd(:rebar3)
end

defp print_missing_steps(steps) do
Mix.shell().info("""
Expand Down Expand Up @@ -389,9 +378,9 @@ defmodule Mix.Tasks.Phx.New do
end

defp elixir_version_check! do
unless Version.match?(System.version(), "~> 1.14") do
unless Version.match?(System.version(), "~> 1.15") do
Mix.raise(
"Phoenix v#{@version} requires at least Elixir v1.14\n " <>
"Phoenix v#{@version} requires at least Elixir v1.15\n " <>
"You have #{System.version()}. Please update accordingly"
)
end
Expand Down
2 changes: 1 addition & 1 deletion installer/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defmodule Phx.New.MixProject do
# 4. test/test_helper.exs at the root
# 5. installer/lib/mix/tasks/phx.new.ex
#
@elixir_requirement "~> 1.14"
@elixir_requirement "~> 1.15"

def project do
[
Expand Down
2 changes: 1 addition & 1 deletion installer/templates/phx_single/config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ config :tailwind,
]<% end %>

# Configures Elixir's Logger
config :logger, :console,
config :logger, :default_formatter,
format: "$time $metadata[$level] $message\n",
metadata: [:request_id]

Expand Down
2 changes: 1 addition & 1 deletion installer/templates/phx_single/config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ config :<%= @app_name %>, <%= @endpoint_module %>,
config :<%= @app_name %>, dev_routes: true

# Do not include metadata nor timestamps in development logs
config :logger, :console, format: "[$level] $message\n"
config :logger, :default_formatter, format: "[$level] $message\n"

# Set a higher stacktrace during development. Avoid configuring such
# in production as building large stacktraces may be expensive.
Expand Down
5 changes: 3 additions & 2 deletions installer/templates/phx_single/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ defmodule <%= @app_module %>.MixProject do
config_path: "../../config/config.exs",
deps_path: "../../deps",
lockfile: "../../mix.lock",<% end %>
elixir: "~> 1.14",
elixir: "~> 1.15",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
aliases: aliases(),
deps: deps()
deps: deps(),
listeners: [Phoenix.CodeReloader]
]
end

Expand Down
2 changes: 1 addition & 1 deletion installer/templates/phx_umbrella/apps/app_name/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ defmodule <%= @app_module %>.MixProject do
config_path: "../../config/config.exs",
deps_path: "../../deps",
lockfile: "../../mix.lock",
elixir: "~> 1.14",
elixir: "~> 1.15",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
aliases: aliases(),
Expand Down
5 changes: 3 additions & 2 deletions installer/templates/phx_umbrella/apps/app_name_web/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ defmodule <%= @web_namespace %>.MixProject do
config_path: "../../config/config.exs",
deps_path: "../../deps",
lockfile: "../../mix.lock",
elixir: "~> 1.14",
elixir: "~> 1.15",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
aliases: aliases(),
deps: deps()
deps: deps(),
listeners: [Phoenix.CodeReloader]
]
end

Expand Down
2 changes: 1 addition & 1 deletion installer/templates/phx_umbrella/config/dev.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Config

# Do not include metadata nor timestamps in development logs
config :logger, :console, format: "[$level] $message\n"
config :logger, :default_formatter, format: "[$level] $message\n"

# Initialize plugs at runtime for faster development compilation
config :phoenix, :plug_init_mode, :runtime<%= if @html do %>
Expand Down
2 changes: 1 addition & 1 deletion installer/templates/phx_umbrella/config/extra_config.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Config

# Configures Elixir's Logger
config :logger, :console,
config :logger, :default_formatter,
format: "$time $metadata[$level] $message\n",
metadata: [:request_id]

Expand Down
4 changes: 2 additions & 2 deletions installer/templates/phx_web/components/core_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ defmodule <%= @web_namespace %>.CoreComponents do
<select
id={@id}
name={@name}
class="mt-2 block w-full rounded-md border border-gray-300 bg-white shadow-sm focus:border-zinc-400 focus:ring-0 sm:text-sm"
class="mt-2 block w-full rounded-md border border-zinc-300 bg-white shadow-sm focus:border-zinc-400 focus:ring-0 sm:text-sm"
multiple={@multiple}
{@rest}
>
Expand Down Expand Up @@ -339,7 +339,7 @@ defmodule <%= @web_namespace %>.CoreComponents do

def error(assigns) do
~H"""
<p class="mt-3 flex gap-3 text-sm leading-6 text-rose-600">
<p class="mt-1.5 flex gap-1.5 text-sm leading-6 text-rose-600">
<.icon name="hero-exclamation-circle-mini" class="mt-0.5 h-5 w-5 flex-none" />
<%%= render_slot(@inner_block) %>
</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
</header>
<main class="px-4 py-20 sm:px-6 lg:px-8">
<div class="mx-auto max-w-2xl">
<.flash_group flash={@flash} />
<%%= @inner_content %>
</div>
</main>
<.flash_group flash={@flash} />
Loading

0 comments on commit 0ad752e

Please sign in to comment.