-
Couldn't load subscription status.
- Fork 3k
Add deprecation warning for pipe_through called after routes #6297
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
490eb87
5ca2d08
7c8792b
dde338d
5b5848d
dc48afd
0313434
acad965
efc5889
2645f7d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,7 +14,8 @@ defmodule Phoenix.Router.Scope do | |
| private: %{}, | ||
| assigns: %{}, | ||
| log: :debug, | ||
| trailing_slash?: false | ||
| trailing_slash?: false, | ||
| has_routes?: false | ||
|
|
||
| @doc """ | ||
| Initializes the scope. | ||
|
|
@@ -33,6 +34,10 @@ defmodule Phoenix.Router.Scope do | |
| raise ArgumentError, "routes expect a module plug as second argument, got: #{inspect(plug)}" | ||
| end | ||
|
|
||
| # Mark that we have routes defined in this scope | ||
| top = get_top(module) | ||
| put_top(module, %{top | has_routes?: true}) | ||
|
|
||
| top = get_top(module) | ||
| path = validate_path(path) | ||
| private = Keyword.get(opts, :private, %{}) | ||
|
|
@@ -120,7 +125,35 @@ defmodule Phoenix.Router.Scope do | |
| """ | ||
| def pipe_through(module, new_pipes) do | ||
| new_pipes = List.wrap(new_pipes) | ||
| %{pipes: pipes} = top = get_top(module) | ||
| %{pipes: pipes, has_routes?: has_routes?} = top = get_top(module) | ||
|
|
||
| # Issue deprecation warning if routes have already been defined | ||
| if has_routes? do | ||
| require Logger | ||
|
|
||
| Logger.warning(""" | ||
|
Comment on lines
+132
to
+134
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aren't deprecation warnings usually done with |
||
| Calling pipe_through/1 after defining routes is deprecated and may not work as expected. | ||
|
|
||
| Routes defined before pipe_through/1 will not have the specified pipelines applied. | ||
| Move all pipe_through/1 calls to the beginning of the scope block. | ||
|
|
||
| Current scope has routes defined, but pipe_through #{inspect(new_pipes)} was called after them. | ||
|
|
||
| Instead of: | ||
| scope "/" do | ||
| get "/", HomeController, :index # This route won't get :browser pipeline | ||
| pipe_through [:browser] # Called too late | ||
| get "/users", UserController, :index # This route gets :browser pipeline | ||
| end | ||
|
|
||
| Do this: | ||
| scope "/" do | ||
| pipe_through [:browser] # Call first | ||
| get "/", HomeController, :index # Both routes get :browser pipeline | ||
| get "/users", UserController, :index | ||
| end | ||
| """) | ||
| end | ||
|
|
||
| if pipe = Enum.find(new_pipes, &(&1 in pipes)) do | ||
| raise ArgumentError, | ||
|
|
@@ -171,7 +204,8 @@ defmodule Phoenix.Router.Scope do | |
| private: Map.merge(top.private, private), | ||
| assigns: Map.merge(top.assigns, assigns), | ||
| log: Keyword.get(opts, :log, top.log), | ||
| trailing_slash?: deprecated_trailing_slash(opts, top) | ||
| trailing_slash?: deprecated_trailing_slash(opts, top), | ||
| has_routes?: false | ||
| }) | ||
| end | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # Phoenix Router pipe_through Ordering Issue #6139 | ||
|
|
||
| ## Problem Description | ||
| Currently, Phoenix allows `pipe_through` to be called after routes are defined within a scope, which can lead to confusing behavior where some routes don't get the expected pipelines applied. | ||
|
|
||
| ## Current Behavior (Confusing) | ||
| ```elixir | ||
| scope "/" do | ||
| get "/", HomeController, :index # Only gets default scope pipelines | ||
| pipe_through [:browser] # This affects routes AFTER it | ||
| get "/settings", UserController, :edit # Gets browser pipeline | ||
| end | ||
| ``` | ||
|
|
||
| ## Expected Behavior (What we want) | ||
| - `pipe_through` should only be allowed at the beginning of a scope | ||
| - If any routes are defined before `pipe_through`, it should raise an error | ||
| - This prevents the common pitfall of thinking all routes get the pipeline | ||
|
|
||
| ## Implementation Plan | ||
| 1. Track in the router scope when routes have been defined | ||
| 2. Raise an error if `pipe_through` is called after routes exist | ||
| 3. Add test cases for both valid and invalid scenarios | ||
| 4. Update error message to guide users to proper usage | ||
|
|
||
| ## Files to Modify | ||
| - `lib/phoenix/router.ex` - Add validation logic | ||
| - `test/phoenix/router/routing_test.exs` - Add test cases | ||
|
|
||
| ## Test Cases Needed | ||
| - ✅ Valid: `pipe_through` before any routes | ||
| - ❌ Invalid: `pipe_through` after routes defined | ||
| - ❌ Invalid: Multiple `pipe_through` calls with routes in between | ||
|
Comment on lines
+1
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's remove that file? |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| Code.require_file "../../../installer/test/mix_helper.exs", __DIR__ | ||
| Code.require_file("../../../installer/test/mix_helper.exs", __DIR__) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Those changes are unrelated, can we undo them? Same with phx.gen.presence itself. We should mix format the whole repository at some point. |
||
|
|
||
| defmodule Mix.Tasks.Phx.Gen.PresenceTest do | ||
| use ExUnit.Case | ||
|
|
@@ -10,36 +10,41 @@ defmodule Mix.Tasks.Phx.Gen.PresenceTest do | |
| end | ||
|
|
||
| test "generates presence" do | ||
| in_tmp_project "generates presence", fn -> | ||
| in_tmp_project("generates presence", fn -> | ||
| Mix.Tasks.Phx.Gen.Presence.run(["MyPresence"]) | ||
|
|
||
| assert_file "lib/phoenix_web/channels/my_presence.ex", fn file -> | ||
| assert_file("lib/phoenix_web/channels/my_presence.ex", fn file -> | ||
| assert file =~ ~S|defmodule PhoenixWeb.MyPresence do| | ||
| assert file =~ ~S|use Phoenix.Presence| | ||
| assert file =~ ~S|otp_app: :phoenix| | ||
| assert file =~ ~S|pubsub_server: Phoenix.PubSub| | ||
| end | ||
| end | ||
| end) | ||
| end) | ||
| end | ||
|
|
||
| test "passing no args defaults to Presence" do | ||
| in_tmp_project "generates presence", fn -> | ||
| in_tmp_project("generates presence", fn -> | ||
| Mix.Tasks.Phx.Gen.Presence.run([]) | ||
|
|
||
| assert_file "lib/phoenix_web/channels/presence.ex", fn file -> | ||
| assert_file("lib/phoenix_web/channels/presence.ex", fn file -> | ||
| assert file =~ ~S|defmodule PhoenixWeb.Presence do| | ||
| assert file =~ ~S|use Phoenix.Presence| | ||
| assert file =~ ~S|otp_app: :phoenix| | ||
| assert file =~ ~S|pubsub_server: Phoenix.PubSub| | ||
| end | ||
| end | ||
| end) | ||
| end) | ||
| end | ||
|
|
||
| test "in an umbrella with a context_app, the file goes in lib/app/channels" do | ||
| in_tmp_umbrella_project "generates presences", fn -> | ||
| in_tmp_umbrella_project("generates presences", fn -> | ||
| Application.put_env(:phoenix, :generators, context_app: {:another_app, "another_app"}) | ||
| Mix.Tasks.Phx.Gen.Presence.run([]) | ||
| assert_file "lib/phoenix/channels/presence.ex" | ||
| end | ||
| assert_file("lib/phoenix/channels/presence.ex", fn file -> | ||
| assert file =~ ~S|defmodule PhoenixWeb.Presence do| | ||
| assert file =~ ~S|use Phoenix.Presence| | ||
| assert file =~ ~S|otp_app: :phoenix| | ||
| assert file =~ ~S|pubsub_server: AnotherApp.PubSub| | ||
| end) | ||
| end) | ||
| end | ||
| end | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume we can skip the second get_top call in line 41?