Skip to content

Commit 95c4c8b

Browse files
committed
Isolate AGENTS.md content for all phx.new flags
1 parent c3fff01 commit 95c4c8b

10 files changed

Lines changed: 113 additions & 32 deletions

File tree

installer/lib/phx_new/generator.ex

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ defmodule Phx.New.Generator do
123123
# rules specific to new apps
124124
@new_project_rules_files["project.md"],
125125
@new_project_rules_files["phoenix.md"],
126+
project.binding[:html] && @new_project_rules_files["phoenix-html.md"],
127+
project.binding[:live] && @new_project_rules_files["phoenix-live.md"],
126128
# --no-assets is equivalent to --no-tailwind && --no-esbuild;
127129
# we check for both here
128130
project.binding[:javascript] && project.binding[:css] &&
@@ -157,6 +159,13 @@ defmodule Phx.New.Generator do
157159
@rules_files["liveview.md"],
158160
"\n<!-- phoenix:liveview-end -->"
159161
],
162+
# Include ecto-forms when both ecto and html are present
163+
project.binding[:ecto] && project.binding[:html] &&
164+
[
165+
"<!-- phoenix:ecto-forms-start -->\n",
166+
@rules_files["ecto-forms.md"],
167+
"\n<!-- phoenix:ecto-forms-end -->"
168+
],
160169
"<!-- usage-rules-end -->"
161170
]
162171
|> Enum.reject(fn part -> part == nil or part == false end)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
### Phoenix Components
2+
3+
- Out of the box, `core_components.ex` imports an `<.icon name="hero-x-mark" class="w-5 h-5"/>` component for hero icons. **Always** use the `<.icon>` component for icons, **never** use `Heroicons` modules or similar
4+
- **Always** use the imported `<.input>` component for form inputs from `core_components.ex` when available. `<.input>` is imported and using it will save steps and prevent errors
5+
- If you override the default input classes (`<.input class="myclass px-2 py-1 rounded-lg">)`) class with your own values, no default classes are inherited, so your
6+
custom classes must fully style the input
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
### Phoenix LiveView Layout Guidelines
2+
3+
- **Always** begin your LiveView templates with `<Layouts.app flash={@flash} ...>` which wraps all inner content
4+
- Anytime you run into errors with no `current_scope` assign:
5+
- You failed to follow the Authenticated Routes guidelines, or you failed to pass `current_scope` to `<Layouts.app>`
6+
- **Always** fix the `current_scope` error by moving your routes to the proper `live_session` and ensure you pass `current_scope` as needed
Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
11
### Phoenix v1.8 guidelines
22

3-
- **Always** begin your LiveView templates with `<Layouts.app flash={@flash} ...>` which wraps all inner content
43
- The `MyAppWeb.Layouts` module is aliased in the `my_app_web.ex` file, so you can use it without needing to alias it again
5-
- Anytime you run into errors with no `current_scope` assign:
6-
- You failed to follow the Authenticated Routes guidelines, or you failed to pass `current_scope` to `<Layouts.app>`
7-
- **Always** fix the `current_scope` error by moving your routes to the proper `live_session` and ensure you pass `current_scope` as needed
84
- Phoenix v1.8 moved the `<.flash_group>` component to the `Layouts` module. You are **forbidden** from calling `<.flash_group>` outside of the `layouts.ex` module
9-
- Out of the box, `core_components.ex` imports an `<.icon name="hero-x-mark" class="w-5 h-5"/>` component for hero icons. **Always** use the `<.icon>` component for icons, **never** use `Heroicons` modules or similar
10-
- **Always** use the imported `<.input>` component for form inputs from `core_components.ex` when available. `<.input>` is imported and using it will save steps and prevent errors
11-
- If you override the default input classes (`<.input class="myclass px-2 py-1 rounded-lg">)`) class with your own values, no default classes are inherited, so your
12-
custom classes must fully style the input

installer/test/phx_new_test.exs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,40 @@ defmodule Mix.Tasks.Phx.NewTest do
518518
assert_file("phx_blog/config/test.exs", fn file ->
519519
refute file =~ ~s|config :phoenix_live_view|
520520
end)
521+
522+
assert_file("phx_blog/AGENTS.md", fn file ->
523+
refute file =~ "<.form"
524+
refute file =~ "<.input"
525+
refute file =~ "LiveView"
526+
end)
527+
end)
528+
end
529+
530+
test "new with --no-live" do
531+
in_tmp("new with no_live", fn ->
532+
Mix.Tasks.Phx.New.run([@app_name, "--no-live"])
533+
534+
assert_file("phx_blog/AGENTS.md", fn file ->
535+
refute file =~ "## Phoenix LiveView guidelines"
536+
refute file =~ "LiveView streams"
537+
refute file =~ "push_event"
538+
refute file =~ "handle_event"
539+
refute file =~ "phx-hook"
540+
end)
541+
end)
542+
end
543+
544+
test "new with --no-ecto --no-live" do
545+
in_tmp("new with no_ecto and no_live", fn ->
546+
Mix.Tasks.Phx.New.run([@app_name, "--no-ecto", "--no-live"])
547+
548+
assert_file("phx_blog/AGENTS.md", fn file ->
549+
refute file =~ "Ecto"
550+
refute file =~ "changeset"
551+
refute file =~ "## Phoenix LiveView guidelines"
552+
refute file =~ "LiveView streams"
553+
refute file =~ "phx-hook"
554+
end)
521555
end)
522556
end
523557

@@ -560,6 +594,7 @@ defmodule Mix.Tasks.Phx.NewTest do
560594
refute file =~ "Ecto"
561595
refute file =~ "changeset"
562596
refute file =~ "Ecto.Schema"
597+
refute file =~ "Ecto.Changeset"
563598
end)
564599
end)
565600
end

usage-rules/ecto-forms.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
## Ecto Forms
2+
3+
### Creating forms from changesets
4+
5+
When using changesets, the underlying data, form params, and errors are retrieved from it. The `:as` option is automatically computed too. E.g. if you have a user schema:
6+
7+
defmodule MyApp.Users.User do
8+
use Ecto.Schema
9+
...
10+
end
11+
12+
And then you create a changeset that you pass to `to_form`:
13+
14+
%MyApp.Users.User{}
15+
|> Ecto.Changeset.change()
16+
|> to_form()
17+
18+
Once the form is submitted, the params will be available under `%{"user" => user_params}`.
19+
20+
In the template, the form assign can be passed to the `<.form>` function component:
21+
22+
<.form for={@form} id="todo-form" phx-submit="save">
23+
<.input field={@form[:field]} type="text" />
24+
</.form>
25+
26+
Always give the form an explicit, unique DOM ID, like `id="todo-form"`.
27+
28+
### Avoiding form errors with changesets
29+
30+
**Always** use a form assigned via `to_form/1` or `to_form/2`, and the `<.input>` component in the template. In the template **always access forms this way**:
31+
32+
<%!-- ALWAYS do this (valid) --%>
33+
<.form for={@form} id="my-form">
34+
<.input field={@form[:field]} type="text" />
35+
</.form>
36+
37+
And **never** do this:
38+
39+
<%!-- NEVER do this (invalid) --%>
40+
<.form for={@changeset} id="my-form">
41+
<.input field={@changeset[:field]} type="text" />
42+
</.form>
43+
44+
- You are FORBIDDEN from accessing the changeset in the template as it will cause errors
45+
- **Never** use `<.form let={f} ...>` in the template, instead **always use `<.form for={@form} ...>`**, then drive all form references from the form assign as in `@form[:field]`. The UI should **always** be driven by a `to_form/1` or `to_form/2` that is derived from a changeset

usage-rules/ecto.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
- Remember `import Ecto.Query` and other supporting modules when you write `seeds.exs`
55
- `Ecto.Schema` fields always use the `:string` type, even for `:text`, columns, ie: `field :name, :string`
66
- `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
7-
- You **must** use `Ecto.Changeset.get_field(changeset, :field)` to access changeset fields
7+
- **Never** use map access syntax (`changeset[:field]`) on changesets. You **must** use `Ecto.Changeset.get_field(changeset, :field)` to access changeset fields
88
- 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
99
- **Always** invoke `mix ecto.gen.migration migration_name_using_underscores` when generating migration files, so the correct timestamp and conventions are applied
1010

usage-rules/elixir.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,18 @@
1818
you *must* bind the result of the expression to a variable if you want to use it and you CANNOT rebind the result inside the expression, ie:
1919

2020
# INVALID: we are rebinding inside the `if` and the result never gets assigned
21-
if connected?(socket) do
22-
socket = assign(socket, :val, val)
21+
if some_condition?(data) do
22+
data = transform(data)
2323
end
2424

2525
# VALID: we rebind the result of the `if` to a new variable
26-
socket =
27-
if connected?(socket) do
28-
assign(socket, :val, val)
26+
data =
27+
if some_condition?(data) do
28+
transform(data)
2929
end
3030

3131
- **Never** nest multiple modules in the same file as it can cause cyclic dependencies and compilation errors
32-
- **Never** use map access syntax on structs as they do not implement the Access behaviour by default. For structs, you **must** access the fields directly, such as `my_struct.field` or use higher level APIs that are available on the struct if they exist
32+
- **Never** use map access syntax (`my_struct[:field]`) on structs as they do not implement the Access behaviour by default. For structs, you **must** access the fields directly, such as `my_struct.field` or use higher level APIs that are available on the struct if they exist
3333
- Elixir's standard library has everything necessary for date and time manipulation. Familiarize yourself with the common `Time`, `Date`, `DateTime`, and `Calendar` interfaces by accessing their documentation as necessary. **Never** install additional dependencies unless asked or for date/time parsing (which you can use the `date_time_parser` package)
3434
- Don't use `String.to_atom/1` on user input (memory leak risk)
3535
- Predicate function names should not start with `is_` and should end in a question mark. Names like `is_thing` should be reserved for guards

usage-rules/html.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
- Phoenix templates **always** use `~H` or .html.heex files (known as HEEx), **never** use `~E`
44
- **Always** use the imported `Phoenix.Component.form/1` and `Phoenix.Component.inputs_for/1` function to build forms. **Never** use `Phoenix.HTML.form_for` or `Phoenix.HTML.inputs_for` as they are outdated
5-
- When building forms **always** use the already imported `Phoenix.Component.to_form/2` (`assign(socket, form: to_form(...))` and `<.form for={@form} id="msg-form">`), then access those forms in the template via `@form[:field]`
5+
- When building forms **always** use the already imported `Phoenix.Component.to_form/2` (`assign(:form, to_form(...))` and `<.form for={@form} id="msg-form">`), then access those forms in the template via `@form[:field]`
66
- **Always** add unique DOM IDs to key elements (like forms, buttons, etc) when writing templates, these IDs can later be used in tests (`<.form for={@form} id="product-form">`)
7-
- For "app wide" template imports, you can import/alias into the `my_app_web.ex`'s `html_helpers` block, so they will be available to all LiveViews, LiveComponent's, and all modules that do `use MyAppWeb, :html` (replace "my_app" by the actual app name)
7+
- For "app wide" template imports, you can import/alias into the `my_app_web.ex`'s `html_helpers` block, so they will be available to all templates and all modules that do `use MyAppWeb, :html` (replace "my_app" by the actual app name)
88

99
- Elixir supports `if/else` but **does NOT support `if/else if` or `if/elsif`**. **Never use `else if` or `elseif` in Elixir**, **always** use `cond` or `case` for multiple conditionals.
1010

usage-rules/liveview.md

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -186,22 +186,10 @@ You can also specify a name to nest the params:
186186
{:noreply, assign(socket, form: to_form(user_params, as: :user))}
187187
end
188188

189-
Always give the form an explicit, unique DOM ID, like `id="todo-form"`.
190-
191-
#### Avoiding form errors
189+
In the template, the form assign can be passed to the `<.form>` function component:
192190

193-
**Always** use a form assigned via `to_form/2` in the LiveView, and the `<.input>` component in the template. In the template **always access forms this**:
194-
195-
<%!-- ALWAYS do this (valid) --%>
196-
<.form for={@form} id="my-form">
191+
<.form for={@form} id="todo-form" phx-change="validate" phx-submit="save">
197192
<.input field={@form[:field]} type="text" />
198193
</.form>
199194

200-
And **never** do this:
201-
202-
<%!-- NEVER do this (invalid) --%>
203-
<.form for={@data} id="my-form">
204-
<.input field={@data[:field]} type="text" />
205-
</.form>
206-
207-
- **Never** use `<.form let={f} ...>` in the template, instead **always use `<.form for={@form} ...>`**, then drive all form references from the form assign as in `@form[:field]`. The UI should **always** be driven by a `to_form/1` or `to_form/2` assigned in the LiveView module
195+
Always give the form an explicit, unique DOM ID, like `id="todo-form"`.

0 commit comments

Comments
 (0)