Repository: gobuffalo/buffalo
Pull request #2239 (2022-04-16 15:11:39)

v0.18.6


Patch:
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index 2b50ddbc..e1daa4d7 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -13,7 +13,7 @@ jobs:
       pull-requests: write  # for actions/stale to close stale PRs
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/stale@v4
+      - uses: actions/stale@v5
         with:
           repo-token: ${{ secrets.GITHUB_TOKEN }}
           stale-issue-message: "This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days."
diff --git a/app.go b/app.go
index 804061e4..12a4dc34 100644
--- a/app.go
+++ b/app.go
@@ -14,15 +14,13 @@ import (
 // Without an App you can't do much!
 type App struct {
 	Options
-	// Middleware returns the current MiddlewareStack for the App/Group.
-	Middleware    *MiddlewareStack `json:"-"`
-	ErrorHandlers ErrorHandlers    `json:"-"`
-	router        *mux.Router
-	moot          *sync.RWMutex
-	routes        RouteList
-	root          *App
-	children      []*App
-	filepaths     []string
+	// Middleware, ErrorHandlers, router, and filepaths are moved to Home.
+	Home
+	moot   *sync.RWMutex
+	routes RouteList
+	// TODO: to be deprecated #road-to-v1
+	root     *App
+	children []*App
 
 	// Routenamer for the app. This field provides the ability to override the
 	// base route namer for something more specific to the app.
@@ -44,17 +42,24 @@ func New(opts Options) *App {
 
 	a := &App{
 		Options: opts,
-		ErrorHandlers: ErrorHandlers{
-			http.StatusNotFound:            defaultErrorHandler,
-			http.StatusInternalServerError: defaultErrorHandler,
+		Home: Home{
+			name:   opts.Name,
+			host:   opts.Host,
+			prefix: opts.Prefix,
+			ErrorHandlers: ErrorHandlers{
+				http.StatusNotFound:            defaultErrorHandler,
+				http.StatusInternalServerError: defaultErrorHandler,
+			},
+			router: mux.NewRouter(),
 		},
-		router:   mux.NewRouter(),
 		moot:     &sync.RWMutex{},
 		routes:   RouteList{},
 		children: []*App{},
 
 		RouteNamer: baseRouteNamer{},
 	}
+	a.Home.app = a     // replace root.
+	a.Home.appSelf = a // temporary, reverse reference to the group app.
 
 	dem := a.defaultErrorMiddleware
 	a.Middleware = newMiddlewareStack(dem)
diff --git a/errors.go b/errors.go
index f9859541..228bd386 100644
--- a/errors.go
+++ b/errors.go
@@ -30,7 +30,10 @@ func (h HTTPError) Unwrap() error {
 
 // Error returns the cause of the error as string.
 func (h HTTPError) Error() string {
-	return h.Cause.Error()
+	if h.Cause != nil {
+		return h.Cause.Error()
+	}
+	return "unknown cause"
 }
 
 // ErrorHandler interface for handling an error for a
diff --git a/go.mod b/go.mod
index b5909ca7..d62525b1 100644
--- a/go.mod
+++ b/go.mod
@@ -3,20 +3,20 @@ module github.com/gobuffalo/buffalo
 go 1.16
 
 require (
-	github.com/BurntSushi/toml v1.0.0
+	github.com/BurntSushi/toml v1.1.0
 	github.com/dustin/go-humanize v1.0.0
 	github.com/fatih/color v1.13.0
 	github.com/gobuffalo/envy v1.10.1
 	github.com/gobuffalo/events v1.4.2
-	github.com/gobuffalo/flect v0.2.4
+	github.com/gobuffalo/flect v0.2.5
 	github.com/gobuffalo/github_flavored_markdown v1.1.1
 	github.com/gobuffalo/helpers v0.6.4
 	github.com/gobuffalo/httptest v1.5.1
 	github.com/gobuffalo/logger v1.0.6
 	github.com/gobuffalo/meta v0.3.1
 	github.com/gobuffalo/nulls v0.4.1
-	github.com/gobuffalo/plush/v4 v4.1.9
-	github.com/gobuffalo/pop/v6 v6.0.1
+	github.com/gobuffalo/plush/v4 v4.1.10
+	github.com/gobuffalo/pop/v6 v6.0.2
 	github.com/gobuffalo/tags/v3 v3.1.2
 	github.com/google/go-cmp v0.5.6 // indirect
 	github.com/gorilla/handlers v1.5.1
diff --git a/go.sum b/go.sum
index a0f31d79..c753f91c 100644
--- a/go.sum
+++ b/go.sum
@@ -39,8 +39,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
-github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU=
-github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
+github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
@@ -111,9 +111,11 @@ github.com/gobuffalo/fizz v1.10.0/go.mod h1:J2XGPO0AfJ1zKw7+2BA+6FEGAkyEsdCOLvN9
 github.com/gobuffalo/fizz v1.14.0 h1:hicZBYSwSWITXEDUR77tqrLU1/vScXHddd02IaFkkPI=
 github.com/gobuffalo/fizz v1.14.0/go.mod h1:0aF1kAZYCfKqbLM/lmZ3jXFyqqWE/kY/nIOKnNdAYXQ=
 github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=
-github.com/gobuffalo/flect v0.2.4 h1:BSYA8+T60cdyq+vynaSUjqSVI9mDEg9ZfQUXKmfjo4I=
 github.com/gobuffalo/flect v0.2.4/go.mod h1:1ZyCLIbg0YD7sDkzvFdPoOydPtD8y9JQnrOROolUcM8=
+github.com/gobuffalo/flect v0.2.5 h1:H6vvsv2an0lalEaCDRThvtBfmg44W/QHXBCYUXf/6S4=
+github.com/gobuffalo/flect v0.2.5/go.mod h1:1ZyCLIbg0YD7sDkzvFdPoOydPtD8y9JQnrOROolUcM8=
 github.com/gobuffalo/genny/v2 v2.0.8/go.mod h1:R45scCyQfff2HysNJHNanjrpvPw4Qu+rM1MOMDBB5oU=
+github.com/gobuffalo/genny/v2 v2.0.9/go.mod h1:R45scCyQfff2HysNJHNanjrpvPw4Qu+rM1MOMDBB5oU=
 github.com/gobuffalo/github_flavored_markdown v1.1.0/go.mod h1:TSpTKWcRTI0+v7W3x8dkSKMLJSUpuVitlptCkpeY8ic=
 github.com/gobuffalo/github_flavored_markdown v1.1.1 h1:kUf8ginyBOTRXcKSTPsPAqlA25vQ80+xAspLIYaxmTU=
 github.com/gobuffalo/github_flavored_markdown v1.1.1/go.mod h1:yU32Pen+eorS58oxh/bNZx76zUOCJwmvyV5FBrvzOKQ=
@@ -131,11 +133,12 @@ github.com/gobuffalo/nulls v0.4.1 h1:k7QVCJfMplv9VRQQLb4N1d8tXUdGvcdMNfp4BfMnG2M
 github.com/gobuffalo/nulls v0.4.1/go.mod h1:pp8e1hWTRJZFpMl4fj/CVbSMlaxjeGKkFq4RuBZi3w8=
 github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY=
 github.com/gobuffalo/plush/v4 v4.0.0/go.mod h1:ErFS3UxKqEb8fpFJT7lYErfN/Nw6vHGiDMTjxpk5bQ0=
-github.com/gobuffalo/plush/v4 v4.1.9 h1:u9rQBuYCeHC0ppKxsZljk5vb1oT8PQa5EMNTAN2337s=
 github.com/gobuffalo/plush/v4 v4.1.9/go.mod h1:9OOII9uAM5pZnhWu1OkQnboXJjaWMQ7kcTl3zNcxvTM=
+github.com/gobuffalo/plush/v4 v4.1.10 h1:sBTmJJ1Hpj4ExqC4NFmCvAgH6Iq6t58JaWvAl8NEAFc=
+github.com/gobuffalo/plush/v4 v4.1.10/go.mod h1:9OOII9uAM5pZnhWu1OkQnboXJjaWMQ7kcTl3zNcxvTM=
 github.com/gobuffalo/pop/v6 v6.0.0/go.mod h1:5rd3OnViLhjteR8+0i/mT9Q4CzkTzCoR7tm/9mmAic4=
-github.com/gobuffalo/pop/v6 v6.0.1 h1:4YhzxW4hVvf0xLW9zVkhPZFuH5VmBc4ffIIP/C++SLQ=
-github.com/gobuffalo/pop/v6 v6.0.1/go.mod h1:5NO7ehmyRjRctnbMDhIqKkkg6zvdueufYltxErfp9BU=
+github.com/gobuffalo/pop/v6 v6.0.2 h1:NuIMTwZ54aHK92ertwv3R2SepAvhTRlQj9/MLqO9Qdo=
+github.com/gobuffalo/pop/v6 v6.0.2/go.mod h1:1g+AV0EDe0rUoy1I3yA3aa9N5+/uC5RxtGbxI0KNe4c=
 github.com/gobuffalo/tags/v3 v3.0.2/go.mod h1:ZQeN6TCTiwAFnS0dNcbDtSgZDwNKSpqajvVtt6mlYpA=
 github.com/gobuffalo/tags/v3 v3.1.2 h1:68sHcwFFDstXyfbk5ovbGcQFDsupgVLs+lw1XZinHJw=
 github.com/gobuffalo/tags/v3 v3.1.2/go.mod h1:o3ldUfKv50jxWAC8eZHXMm8dnKW3YvyZUMr0xqUcZTI=
@@ -145,8 +148,9 @@ github.com/gobuffalo/validate/v3 v3.3.1/go.mod h1:Ehu8ieNJQuUM4peDDr/0VapzdGA7Rg
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
-github.com/gofrs/uuid v4.1.0+incompatible h1:sIa2eCvUTwgjbqXrPLfNwUf9S3i3mpH1O1atV+iL/Wk=
 github.com/gofrs/uuid v4.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
+github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
+github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -259,8 +263,9 @@ github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfG
 github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
 github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
 github.com/jackc/pgconn v1.10.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
-github.com/jackc/pgconn v1.10.1 h1:DzdIHIjG1AxGwoEEqS+mGsURyjt4enSmqzACXvVzOT8=
 github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
+github.com/jackc/pgconn v1.11.0 h1:HiHArx4yFbwl91X3qqIHtUFoiIfLNJXCQRsnzkiwwaQ=
+github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
 github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
 github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
 github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
@@ -276,25 +281,29 @@ github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod
 github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
 github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
 github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
-github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI=
 github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns=
+github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
 github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
 github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
 github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
 github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
 github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
-github.com/jackc/pgtype v1.8.1 h1:9k0IXtdJXHJbyAWQgbWr1lU+MEhPXZz6RIXxfR5oxXs=
 github.com/jackc/pgtype v1.8.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
+github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38=
+github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
 github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
 github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
 github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
 github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
-github.com/jackc/pgx/v4 v4.13.0 h1:JCjhT5vmhMAf/YwBHLvrBn4OGdIQBiFG6ym8Zmdx570=
 github.com/jackc/pgx/v4 v4.13.0/go.mod h1:9P4X524sErlaxj0XSGZk7s+LD0eOyu1ZDUrrpznYDF0=
+github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w=
+github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw=
 github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w=
 github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
 github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
@@ -323,8 +332,9 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
 github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.10.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ=
+github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/luna-duclos/instrumentedsql v1.1.3 h1:t7mvC0z1jUt5A0UQ6I/0H31ryymuQRnJcWCiqV3lSAA=
 github.com/luna-duclos/instrumentedsql v1.1.3/go.mod h1:9J1njvFds+zN7y85EDhN9XNQLANWwZt2ULeIC8yMNYs=
 github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
diff --git a/grifts.go b/grifts.go
index 84af4a77..1cd5a28c 100644
--- a/grifts.go
+++ b/grifts.go
@@ -44,22 +44,24 @@ func printMiddleware(a *App) {
 
 func printMiddlewareByRoute(a *App) {
 	mws := map[string]string{}
+	// TODO: middleware is 'per App' can it be a loop for Apps?
 	for _, r := range a.Routes() {
-		if mws[r.App.Name] == "" {
-			pname := ""
+		key := r.App.host + r.App.name
+		if mws[key] == "" {
+			pname := r.App.host
 			if parent := getParentApp(r.App.root, r.App.Name); parent != nil {
-				pname = parent.Name
+				pname += parent.Name
 			}
 
-			mws[r.App.Name] = r.App.Middleware.String()
-			if mws[pname] != mws[r.App.Name] {
-				fmt.Printf("-> %s\n", r.App.Name)
-				printMiddlewareStackWithIndent(mws[r.App.Name])
+			mws[key] = r.App.Middleware.String()
+			if pname == key || mws[pname] != mws[key] {
+				fmt.Printf("-> %s\n", key)
+				printMiddlewareStackWithIndent(mws[key])
 			} else {
-				fmt.Printf("-> %s (see: %v)\n", r.App.Name, pname)
+				fmt.Printf("-> %s (see: %v)\n", key, pname)
 			}
 		}
-		s := "\n" + mws[r.App.Name]
+		s := "\n" + mws[key]
 		for k := range r.App.Middleware.skips {
 			mw := strings.Split(k, funcKeyDelimeter)[0]
 			h := strings.Split(k, funcKeyDelimeter)[1]
@@ -67,10 +69,10 @@ func printMiddlewareByRoute(a *App) {
 				s = strings.Replace(s, "\n"+mw, "", 1)
 			}
 		}
-		if "\n"+mws[r.App.Name] != s {
+		if "\n"+mws[key] != s {
 			ahn := strings.Split(r.HandlerName, "/")
 			hn := ahn[len(ahn)-1]
-			fmt.Printf("-> %s %s (by %s)\n", r.Method, r.Path, hn)
+			fmt.Printf("-> %s %s (by %s)\n", r.Method, r.App.host+r.Path, hn)
 			printMiddlewareStackWithIndent(s)
 		}
 	}
@@ -106,10 +108,10 @@ func routesGrift(a *App) {
 	grift.Add("routes", func(c *grift.Context) error {
 		routes := a.Routes()
 		w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.Debug)
-		fmt.Fprintln(w, "METHOD\t PATH\t ALIASES\t NAME\t HANDLER")
-		fmt.Fprintln(w, "------\t ----\t -------\t ----\t -------")
+		fmt.Fprintln(w, "METHOD\t HOST\t PATH\t ALIASES\t NAME\t HANDLER")
+		fmt.Fprintln(w, "------\t ----\t ----\t -------\t ----\t -------")
 		for _, r := range routes {
-			fmt.Fprintf(w, "%s\t %s\t %s\t %s\t %s\n", r.Method, r.Path, strings.Join(r.Aliases, " "), r.PathName, r.HandlerName)
+			fmt.Fprintf(w, "%s\t %s\t %s\t %s\t %s\t %s\n", r.Method, r.App.host, r.Path, strings.Join(r.Aliases, " "), r.PathName, r.HandlerName)
 		}
 		w.Flush()
 		return nil
diff --git a/home.go b/home.go
new file mode 100644
index 00000000..79255e54
--- /dev/null
+++ b/home.go
@@ -0,0 +1,39 @@
+package buffalo
+
+import (
+	"github.com/gorilla/mux"
+)
+
+/* TODO: consider to split out Home (or Router, whatever) from App #road-to-v1
+   Group and Domain based multi-homing are actually not an App if the concept
+   of the App represents the application. The App should be only one for whole
+   application.
+
+   For an extreme example, App.Group().Stop() or even App.Group().Serve() are
+   still valid function calls while they should not be allowed and the result
+   could be strage.
+*/
+
+// Home is a container for Domains and Groups that independantly serves a
+// group of pages with its own Middleware and ErrorHandlers. It is usually
+// a multi-homed server domain or group of paths under a certain prefix.
+//
+// While the App is for managing whole application life cycle along with its
+// default Home, including initializing and stopping its all components such
+// as listeners and long-running jobs, Home is only for a specific group of
+// services to serve its service logic efficiently.
+type Home struct {
+	app     *App // will replace App.root
+	appSelf *App // temporary while the App is in action.
+	// replace Options' Name, Host, and Prefix
+	name   string
+	host   string
+	prefix string
+
+	// moved from App
+	// Middleware returns the current MiddlewareStack for the App/Group.
+	Middleware    *MiddlewareStack `json:"-"`
+	ErrorHandlers ErrorHandlers    `json:"-"`
+	router        *mux.Router
+	filepaths     []string
+}
diff --git a/route.go b/route.go
index b5f7b311..cab38c42 100644
--- a/route.go
+++ b/route.go
@@ -11,6 +11,7 @@ import (
 // Routes returns a list of all of the routes defined
 // in this application.
 func (a *App) Routes() RouteList {
+	// CHKME: why this function is exported? can we deprecate it?
 	if a.root != nil {
 		return a.root.routes
 	}
@@ -64,11 +65,20 @@ type RouteHelperFunc func(opts map[string]interface{}) (template.HTML, error)
 // and the name of the Handler defined to process that route.
 type RouteList []*RouteInfo
 
+var methodOrder = map[string]string{
+	"GET":    "1",
+	"POST":   "2",
+	"PUT":    "3",
+	"DELETE": "4",
+}
+
 func (a RouteList) Len() int      { return len(a) }
 func (a RouteList) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
 func (a RouteList) Less(i, j int) bool {
-	x := a[i].Path // + a[i].Method
-	y := a[j].Path // + a[j].Method
+	// NOTE: it was used for sorting of app.routes but we don't sort the routes anymore.
+	// keep it for compatibility but could be deprecated.
+	x := a[i].App.host + a[i].Path + methodOrder[a[i].Method]
+	y := a[j].App.host + a[j].Path + methodOrder[a[j].Method]
 	return x < y
 }
 
diff --git a/route_info.go b/route_info.go
index b5e20d51..c6be5a63 100644
--- a/route_info.go
+++ b/route_info.go
@@ -49,7 +49,7 @@ func (ri *RouteInfo) Alias(aliases ...string) *RouteInfo {
 func (ri *RouteInfo) Name(name string) *RouteInfo {
 	routeIndex := -1
 	for index, route := range ri.App.Routes() {
-		if route.Path == ri.Path && route.Method == ri.Method {
+		if route.App.host == ri.App.host && route.Path == ri.Path && route.Method == ri.Method {
 			routeIndex = index
 			break
 		}
diff --git a/route_mappings.go b/route_mappings.go
index 4f42b055..c0f7d4d8 100644
--- a/route_mappings.go
+++ b/route_mappings.go
@@ -7,7 +7,6 @@ import (
 	"os"
 	"path"
 	"reflect"
-	"sort"
 	"strings"
 
 	"github.com/gobuffalo/envy"
@@ -20,6 +19,8 @@ const (
 	AssetsAgeVarName = "ASSETS_MAX_AGE"
 )
 
+// These method functions will be moved to Home structure.
+
 // GET maps an HTTP "GET" request to the path and the specified handler.
 func (a *App) GET(p string, h Handler) *RouteInfo {
 	return a.addRoute("GET", p, h)
@@ -84,29 +85,6 @@ func (a *App) Mount(p string, h http.Handler) {
 	a.ANY(path, WrapHandler(http.StripPrefix(prefix, h)))
 }
 
-// Host creates a new "*App" group that matches the domain passed.
-// This is useful for creating groups of end-points for different domains.
-/*
-	a.Host("www.example.com")
-	a.Host("{subdomain}.example.com")
-	a.Host("{subdomain:[a-z]+}.example.com")
-*/
-func (a *App) Host(h string) *App {
-	g := New(a.Options)
-
-	g.router = a.router.Host(h).Subrouter()
-	g.RouteNamer = a.RouteNamer
-	g.Middleware = a.Middleware.clone()
-	g.ErrorHandlers = a.ErrorHandlers
-	g.root = a
-
-	if a.root != nil {
-		g.root = a.root
-	}
-
-	return g
-}
-
 // ServeFiles maps an path to a directory on disk to serve static files.
 // Useful for JavaScript, images, CSS, etc...
 /*
@@ -156,6 +134,11 @@ type editable interface {
 // to the appropriate RESTful mappings. Resource returns the *App
 // associated with this group of mappings so you can set middleware, etc...
 // on that group, just as if you had used the a.Group functionality.
+//
+// Resource automatically creates a URL `/resources/new` if the resource
+// has a function `New()`. So it could act as a restriction for the value
+// of `resource_id`. URL `/resources/new` will always show the resource
+// creation page instead of showing the resource called `new`.
 /*
 	a.Resource("/users", &UsersResource{})
 
@@ -164,12 +147,12 @@ type editable interface {
 	ur := &UsersResource{}
 	g := a.Group("/users")
 	g.GET("/", ur.List) // GET /users => ur.List
+	g.POST("/", ur.Create) // POST /users => ur.Create
 	g.GET("/new", ur.New) // GET /users/new => ur.New
 	g.GET("/{user_id}", ur.Show) // GET /users/{user_id} => ur.Show
+	g.PUT("/{user_id}", ur.Update) // PUT /users/{user_id} => ur.Update
+	g.DELETE("/{user_id}", ur.Destroy) // DELETE /users/{user_id} => ur.Destroy
 	g.GET("/{user_id}/edit", ur.Edit) // GET /users/{user_id}/edit => ur.Edit
-	g.POST("/", ur.Create) // POST /users => ur.Create
-	g.PUT("/{user_id}", ur.Update) PUT /users/{user_id} => ur.Update
-	g.DELETE("/{user_id}", ur.Destroy) DELETE /users/{user_id} => ur.Destroy
 */
 func (a *App) Resource(p string, r Resource) *App {
 	g := a.Group(p)
@@ -202,9 +185,14 @@ func (a *App) Resource(p string, r Resource) *App {
 
 	spath := path.Join(p, "{"+paramName+"}")
 
+	// This order will become the order of route evaluation too.
 	setFuncKey(r.List, fmt.Sprintf(handlerName, "List"))
 	g.GET(p, r.List).ResourceName = resourceName
 
+	setFuncKey(r.Create, fmt.Sprintf(handlerName, "Create"))
+	g.POST(p, r.Create).ResourceName = resourceName
+
+	// NOTE: it makes restriction that resource id cannot be 'new'.
 	if n, ok := r.(newable); ok {
 		setFuncKey(n.New, fmt.Sprintf(handlerName, "New"))
 		g.GET(path.Join(p, "new"), n.New).ResourceName = resourceName
@@ -213,21 +201,19 @@ func (a *App) Resource(p string, r Resource) *App {
 	setFuncKey(r.Show, fmt.Sprintf(handlerName, "Show"))
 	g.GET(path.Join(spath), r.Show).ResourceName = resourceName
 
-	if n, ok := r.(editable); ok {
-		setFuncKey(n.Edit, fmt.Sprintf(handlerName, "Edit"))
-		g.GET(path.Join(spath, "edit"), n.Edit).ResourceName = resourceName
-	}
-
-	setFuncKey(r.Create, fmt.Sprintf(handlerName, "Create"))
-	g.POST(p, r.Create).ResourceName = resourceName
-
 	setFuncKey(r.Update, fmt.Sprintf(handlerName, "Update"))
 	g.PUT(path.Join(spath), r.Update).ResourceName = resourceName
 
 	setFuncKey(r.Destroy, fmt.Sprintf(handlerName, "Destroy"))
 	g.DELETE(path.Join(spath), r.Destroy).ResourceName = resourceName
 
+	if n, ok := r.(editable); ok {
+		setFuncKey(n.Edit, fmt.Sprintf(handlerName, "Edit"))
+		g.GET(path.Join(spath, "edit"), n.Edit).ResourceName = resourceName
+	}
+
 	g.Prefix = path.Join(g.Prefix, spath)
+	g.prefix = g.Prefix
 
 	return g
 }
@@ -254,22 +240,54 @@ func (a *App) ANY(p string, h Handler) {
 	g.GET("/users/:user_id, APIUserShowHandler)
 */
 func (a *App) Group(groupPath string) *App {
+	// TODO: move this function to app.go or home.go eventually.
 	g := New(a.Options)
+	// keep them for v0 compatibility
 	g.Prefix = path.Join(a.Prefix, groupPath)
 	g.Name = g.Prefix
 
+	// for Home structure
+	g.prefix = path.Join(a.prefix, groupPath)
+	g.host = a.host
+	g.name = g.prefix
+
 	g.router = a.router
 	g.RouteNamer = a.RouteNamer
 	g.Middleware = a.Middleware.clone()
 	g.ErrorHandlers = a.ErrorHandlers
-	g.root = a
-	if a.root != nil {
-		g.root = a.root
-	}
+
+	g.app = a.app  // will replace g.root
+	g.root = g.app // will be deprecated
+
+	// to be replaced with child Homes. currently, only used in grifts.
 	a.children = append(a.children, g)
 	return g
 }
 
+// VirtualHost creates a new `*App` that inherits from it's parent `*App`.
+// All pre-configured things on the parent App such as middlewares will be
+// applied, and can be modified only for this child App.
+//
+// This is a multi-homing feature similar to the `VirtualHost` in Apache
+// or multiple `server`s in nginx. One important different behavior is that
+// there is no concept of the `default` host in buffalo (at least for now)
+// and the routing decision will be made with the "first match" manner.
+// (e.g. if you have already set the route for '/' for the root App before
+// setting up a virualhost, the route of the root App will be picked up
+// even if the client makes a request to the specified domain.)
+/*
+	a.VirtualHost("www.example.com")
+	a.VirtualHost("{subdomain}.example.com")
+	a.VirtualHost("{subdomain:[a-z]+}.example.com")
+*/
+func (a *App) VirtualHost(h string) *App {
+	g := a.Group("/")
+	g.host = h
+	g.router = a.router.Host(h).Subrouter()
+
+	return g
+}
+
 // RouteHelpers returns a map of BuildPathHelper() for each route available in the app.
 func (a *App) RouteHelpers() map[string]RouteHelperFunc {
 	rh := map[string]RouteHelperFunc{}
@@ -280,13 +298,15 @@ func (a *App) RouteHelpers() map[string]RouteHelperFunc {
 	return rh
 }
 
-func (a *App) addRoute(method string, url string, h Handler) *RouteInfo {
-	a.moot.Lock()
-	defer a.moot.Unlock()
+func (e *Home) addRoute(method string, url string, h Handler) *RouteInfo {
+	// NOTE: lock the root app (not this app). only the root has the affective
+	// routes list.
+	e.app.moot.Lock()
+	defer e.app.moot.Unlock()
 
-	url = path.Join(a.Prefix, url)
-	url = a.normalizePath(url)
-	name := a.RouteNamer.NameRoute(url)
+	url = path.Join(e.prefix, url)
+	url = e.app.normalizePath(url)
+	name := e.app.RouteNamer.NameRoute(url)
 
 	hs := funcKey(h)
 	r := &RouteInfo{
@@ -294,22 +314,23 @@ func (a *App) addRoute(method string, url string, h Handler) *RouteInfo {
 		Path:        url,
 		HandlerName: hs,
 		Handler:     h,
-		App:         a,
+		App:         e.appSelf, // CHKME: to be replaced with Home
 		Aliases:     []string{},
 	}
 
-	r.MuxRoute = a.router.Handle(url, r).Methods(method)
+	r.MuxRoute = e.router.Handle(url, r).Methods(method)
 	r.Name(name)
 
-	routes := a.Routes()
+	routes := e.app.Routes()
 	routes = append(routes, r)
-	sort.Sort(routes)
-
-	if a.root != nil {
-		a.root.routes = routes
-	} else {
-		a.routes = routes
-	}
+	// NOTE: sorting is fancy but we lose the evaluation order information
+	// of routing decision. Let's keep the routes as registered order so
+	// developers can easily evaluate the order with `buffalo routes` and
+	// can debug any routing priority issue. (just keep the original line
+	// as history reference)
+	//sort.Sort(routes)
+
+	e.app.routes = routes
 
 	return r
 }
diff --git a/runtime/version.go b/runtime/version.go
index 2159ff4a..701242d9 100644
--- a/runtime/version.go
+++ b/runtime/version.go
@@ -1,4 +1,4 @@
 package runtime
 
 // Version is the current version of the buffalo binary
-var Version = "v0.18.5"
+var Version = "v0.18.6"


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 37974e4b..cf7a64a2 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -25,7 +25,7 @@ jobs:
     steps:
       - uses: actions/checkout@v3
       - name: Setup Go ${{ matrix.go }}
-        uses: actions/setup-go@v2
+        uses: actions/setup-go@v3
         with:
           go-version: ${{ matrix.go-version }}
 
diff --git a/errors_test.go b/errors_test.go
index 7042ade2..bf55f600 100644
--- a/errors_test.go
+++ b/errors_test.go
@@ -143,6 +143,18 @@ func Test_defaultErrorHandler_XML_production(t *testing.T) {
 	r.Contains(b, `</response>`)
 }
 
+func Test_defaultErrorHandler_nil_error(t *testing.T) {
+	r := require.New(t)
+	app := New(Options{})
+	app.GET("/", func(c Context) error {
+		return c.Error(http.StatusInternalServerError, nil)
+	})
+
+	w := httptest.New(app)
+	res := w.JSON("/").Get()
+	r.Equal(http.StatusInternalServerError, res.Code)
+}
+
 func Test_PanicHandler(t *testing.T) {
 	app := New(Options{})
 	app.GET("/string", func(c Context) error {
diff --git a/route_mappings_test.go b/route_mappings_test.go
index fe80e288..5a189e31 100644
--- a/route_mappings_test.go
+++ b/route_mappings_test.go
@@ -155,13 +155,13 @@ func Test_App_Routes_Resource(t *testing.T) {
 	}
 }
 
-func Test_App_Host(t *testing.T) {
+func Test_App_VirtualHost(t *testing.T) {
 	r := require.New(t)
 
 	a1 := New(Options{})
 	r.Nil(a1.root)
 
-	h1 := a1.Host("www.example.com")
+	h1 := a1.VirtualHost("www.example.com")
 	h1.GET("/foo", voidHandler)
 
 	routes := h1.Routes()
@@ -177,7 +177,7 @@ func Test_App_Host(t *testing.T) {
 	a2 := New(Options{})
 	r.Nil(a1.root)
 
-	h2 := a2.Host("{subdomain}.example.com")
+	h2 := a2.VirtualHost("{subdomain}.example.com")
 	h2.GET("/foo", voidHandler)
 	h2.GET("/foo/{id}", voidHandler).Name("fooID")