资源

原文:https://gobuffalo.io/documentation/request_handling/resources/

Resources 资源

Often web applications need to build very similar “CRUD” end-points. To help reduce the amount of thought and complexity involved in this, Buffalo supports the concept of a “Resource”.

​ 通常,Web 应用程序需要构建非常相似的“CRUD”端点。为了帮助减少与此相关的思考和复杂性,Buffalo 支持“资源”的概念。

The github.com/gobuffalo/buffalo#Resource interface allows Buffalo to map common routes and respond to common requests.

github.com/gobuffalo/buffalo#Resource 接口允许 Buffalo 映射常见路由并响应常见请求。

Since 0.14.1 自 0.14.1 起

1
2
3
4
5
6
7
type Resource interface {
	List(Context) error
	Show(Context) error
	Create(Context) error
	Update(Context) error
	Destroy(Context) error
}

The github.com/gobuffalo/buffalo#Resource interface was made smaller in release v0.14.1. The New and Edit methods, which serve the HTML forms to edit the resource, are now optional.

github.com/gobuffalo/buffalo#Resource 接口在版本 v0.14.1 中变得更小。用于提供用于编辑资源的 HTML 表单的 NewEdit 方法现在是可选的。

Here’s what the interface looked like before:

​ 以下是该接口以前的样子:

1
2
3
4
5
6
7
8
9
type Resource interface {
	List(Context) error
	Show(Context) error
	New(Context) error
	Create(Context) error
	Edit(Context) error
	Update(Context) error
	Destroy(Context) error
}

Using Resources 使用资源

After implementing the necessary methods on the github.com/gobuffalo/buffalo#Resource interface, the resource can then be mapped to the application using the github.com/gobuffalo/buffalo#App.Resource method.

​ 在 github.com/gobuffalo/buffalo#Resource 接口上实现必要的方法后,可以使用 github.com/gobuffalo/buffalo#App.Resource 方法将资源映射到应用程序。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// action/users.go
type UsersResource struct{ }

func (u UsersResource) List(c buffalo.Context) error {
  // do work
}

func (u UsersResource) Show(c buffalo.Context) error {
  // do work
}

func (u UsersResource) Create(c buffalo.Context) error {
  // do work
}

func (u UsersResource) Update(c buffalo.Context) error {
  // do work
}

func (u UsersResource) Destroy(c buffalo.Context) error {
  // do work
}

Mapping the Resource in app.go:

​ 在 app.go 中映射资源:

1
2
// actions/app.go
app.Resource("/users", UsersResource{})

The above code example would be the equivalent of the following:

​ 上面的代码示例相当于以下内容:

1
2
3
4
5
6
7
8
// actions/app.go
ur := UsersResource{}

app.GET("/users", ur.List)
app.GET("/users/{user_id}", ur.Show)
app.POST("/users", ur.Create)
app.PUT("/users/{user_id}", ur.Update)
app.DELETE("/users/{user_id}", ur.Destroy)

It will produce a routing table that looks similar to:

​ 它将生成一个类似于以下内容的路由表:

1
2
3
4
5
6
7
8
9
$ buffalo routes

METHOD | HOST                  | PATH                    | ALIASES | NAME                 | HANDLER
------ | ----                  | ----                    | ------- | ----                 | -------
GET    | http://127.0.0.1:3000 | /users/                 |         | usersPath            | coke/actions.UsersResource.List
POST   | http://127.0.0.1:3000 | /users/                 |         | usersPath            | coke/actions.UsersResource.Create
GET    | http://127.0.0.1:3000 | /users/{user_id}/       |         | userPath             | coke/actions.UsersResource.Show
PUT    | http://127.0.0.1:3000 | /users/{user_id}/       |         | userPath             | coke/actions.UsersResource.Update
DELETE | http://127.0.0.1:3000 | /users/{user_id}/       |         | userPath             | coke/actions.UsersResource.Destroy

Optional Resource Methods 可选资源方法 # 自 0.14.1 起

Since 0.14.1 在 中, 变小了,现在以下方法是可选的:

In v0.14.1 the github.com/gobuffalo/buffalo#Resource was made smaller with the following methods now being optional:

​ 实现 v0.14.1github.com/gobuffalo/buffalo#Resource 方法时,将向路由表添加以下内容:

1
2
New(Context) error
Edit(Context) error

When implemented the New and Edit methods will add the following to the routing table:

​ 生成资源 #

1
2
3
4
METHOD | HOST                   | PATH                   | ALIASES | NAME         | HANDLER
------ | ----                   | ----                   | ------- | ----         | -------
GET    | http://127.0.0.1:3000  | /users/new             |         | newUsersPath | coke/actions.UsersResource.New
GET    | http://127.0.0.1:3000  | /users/{user_id}/edit/ |         | editUserPath | coke/actions.UsersResource.Edit

Generating Resources 命令将生成必要的模型、迁移、Go 代码和 HTML 来对资源进行 CRUD 操作。

The buffalo generate resource command will generate the necessary models, migrations, Go code, and HTML to CRUD the resource.

​ 在 API 应用程序中运行生成器时,Buffalo 将生成代码以满足 buffalo generate resource 接口。

When running the generator in an API application Buffalo will generate code to meet the github.com/gobuffalo/buffalo#Resource interface.

​ 在 Web 应用程序中运行生成器时,Buffalo 将生成代码以满足 github.com/gobuffalo/buffalo#Resource 接口,以及可选的 和 方法。

1
2
3
4
5
6
7
type Resource interface {
  List(Context) error
  Show(Context) error
  Create(Context) error
  Update(Context) error
  Destroy(Context) error
}

When running the generator in a Web application Buffalo will generate code to the meet the github.com/gobuffalo/buffalo#Resource interface, as well as the optional New and Edit methods.

1
2
3
4
5
6
7
8
9
type Resource interface {
  List(Context) error
  Show(Context) error
  New(Context) error
  Create(Context) error
  Edit(Context) error
  Update(Context) error
  Destroy(Context) error
}

Example Resource Generation 示例资源生成

In this example Buffalo will generate the code needed to CRUD a resource named widget (Go: Widget) that has the following attributes:

​ 在此示例中,Buffalo 将生成创建、读取、更新和删除名为 widget (Go: Widget ) 的资源所需的代码,该资源具有以下属性:

Model Attribute 模型属性Go Type Go 类型DB type 数据库类型Form Type 表单类型
titleTitlestringvarchartext
descriptionDescriptionnulls.Stringvarchar (nullable)textarea
1
$ buffalo generate resource widget title description:nulls.Text

actions/app.go

actions/widgets.go

actions/widgets_test.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package actions

import (
	"net/http"

	"coke/locales"
	"coke/models"
	"coke/public"

	"github.com/gobuffalo/buffalo"
	"github.com/gobuffalo/buffalo-pop/v3/pop/popmw"
	"github.com/gobuffalo/envy"
	csrf "github.com/gobuffalo/mw-csrf"
	forcessl "github.com/gobuffalo/mw-forcessl"
	i18n "github.com/gobuffalo/mw-i18n/v2"
	paramlogger "github.com/gobuffalo/mw-paramlogger"
	"github.com/unrolled/secure"
)

// ENV is used to help switch settings based on where the
// application is being run. Default is "development".
var ENV = envy.Get("GO_ENV", "development")

var (
	app *buffalo.App
	T   *i18n.Translator
)

// App is where all routes and middleware for buffalo
// should be defined. This is the nerve center of your
// application.
//
// Routing, middleware, groups, etc... are declared TOP -> DOWN.
// This means if you add a middleware to `app` *after* declaring a
// group, that group will NOT have that new middleware. The same
// is true of resource declarations as well.
//
// It also means that routes are checked in the order they are declared.
// `ServeFiles` is a CATCH-ALL route, so it should always be
// placed last in the route declarations, as it will prevent routes
// declared after it to never be called.
func App() *buffalo.App {
	if app == nil {
		app = buffalo.New(buffalo.Options{
			Env:         ENV,
			SessionName: "_coke_session",
		})

		// Automatically redirect to SSL
		app.Use(forceSSL())

		// Log request parameters (filters apply).
		app.Use(paramlogger.ParameterLogger)

		// Protect against CSRF attacks. https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
		// Remove to disable this.
		app.Use(csrf.New)

		// Wraps each request in a transaction.
		//   c.Value("tx").(*pop.Connection)
		// Remove to disable this.
		app.Use(popmw.Transaction(models.DB))
		// Setup and use translations:
		app.Use(translations())

		app.GET("/", HomeHandler)

		app.Resource("/widgets", WidgetsResource{})
		app.ServeFiles("/", http.FS(public.FS())) // serve files from the public directory
	}

	return app
}

// translations will load locale files, set up the translator `actions.T`,
// and will return a middleware to use to load the correct locale for each
// request.
// for more information: https://gobuffalo.io/docs/localization
func translations() buffalo.MiddlewareFunc {
	var err error
	if T, err = i18n.New(locales.FS(), "en-US"); err != nil {
		app.Stop(err)
	}
	return T.Middleware()
}

// forceSSL will return a middleware that will redirect an incoming request
// if it is not HTTPS. "http://example.com" => "https://example.com".
// This middleware does **not** enable SSL. for your application. To do that
// we recommend using a proxy: https://gobuffalo.io/docs/proxy
// for more information: https://github.com/unrolled/secure/
func forceSSL() buffalo.MiddlewareFunc {
	return forcessl.Middleware(secure.Options{
		SSLRedirect:     ENV == "production",
		SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "https"},
	})
}

locales/widgets.en-us.yaml

1
2
3
4
5
6
- id: "widget.created.success"
  translation: "Widget was successfully created."
- id: "widget.updated.success"
  translation: "Widget was successfully updated."
- id: "widget.destroyed.success"
  translation: "Widget was successfully destroyed."

migrations/20181005153028_create_widgets.up.fizz

migrations/20181005153028_create_widgets.down.fizz

create_table("widgets") {
	t.Column("id", "uuid", {primary: true})
	t.Column("title", "string", {})
	t.Column("description", "text", {null: true})
	t.Timestamps()
}

models/widget.go

models/widget_test.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package models

import (
	"encoding/json"
	"time"

	"github.com/gobuffalo/nulls"
	"github.com/gobuffalo/pop/v6"
	"github.com/gobuffalo/validate/v3"
	"github.com/gobuffalo/validate/v3/validators"
	"github.com/gofrs/uuid"
)

// Widget is used by pop to map your widgets database table to your go code.
type Widget struct {
	ID          uuid.UUID    `json:"id" db:"id"`
	Title       string       `json:"title" db:"title"`
	Description nulls.String `json:"description" db:"description"`
	CreatedAt   time.Time    `json:"created_at" db:"created_at"`
	UpdatedAt   time.Time    `json:"updated_at" db:"updated_at"`
}

// String is not required by pop and may be deleted
func (w Widget) String() string {
	jw, _ := json.Marshal(w)
	return string(jw)
}

// Widgets is not required by pop and may be deleted
type Widgets []Widget

// String is not required by pop and may be deleted
func (w Widgets) String() string {
	jw, _ := json.Marshal(w)
	return string(jw)
}

// Validate gets run every time you call a "pop.Validate*" (pop.ValidateAndSave, pop.ValidateAndCreate, pop.ValidateAndUpdate) method.
// This method is not required and may be deleted.
func (w *Widget) Validate(tx *pop.Connection) (*validate.Errors, error) {
	return validate.Validate(
		&validators.StringIsPresent{Field: w.Title, Name: "Title"},
	), nil
}

// ValidateCreate gets run every time you call "pop.ValidateAndCreate" method.
// This method is not required and may be deleted.
func (w *Widget) ValidateCreate(tx *pop.Connection) (*validate.Errors, error) {
	return validate.NewErrors(), nil
}

// ValidateUpdate gets run every time you call "pop.ValidateAndUpdate" method.
// This method is not required and may be deleted.
func (w *Widget) ValidateUpdate(tx *pop.Connection) (*validate.Errors, error) {
	return validate.NewErrors(), nil
}

templates/widgets/_form.plush.html

templates/widgets/edit.plush.html

templates/widgets/index.plush.html

templates/widgets/new.plush.html

templates/widgets/show.plush.html

1
2
3
<%= f.InputTag("Title") %>
<%= f.TextAreaTag("Description", {rows: 10}) %>
<button class="btn btn-success" role="submit">Save</button>

Destroying Resources 销毁资源

You can remove files generated by this generator by running:

​ 您可以通过运行以下命令来删除此生成器生成的文件:

1
$ buffalo destroy resource users

This command will ask you which files you want to remove, you can either answer each of the questions with y/n or you can pass the -y flag to the command like:

​ 此命令会询问您要删除哪些文件,您可以用 y/n 回答每个问题,或者可以像下面这样将 -y 标志传递给命令:

1
$ buffalo destroy resource users -y

Or in short form:

​ 或简短形式:

1
$ buffalo d r users -y

Nesting Resources 嵌套资源

To simplify creating resource hierarchies, Buffalo supports nesting resources.

​ 为了简化创建资源层次结构,Buffalo 支持嵌套资源。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
type WidgetsResource struct {
	buffalo.Resource
}

type ImagesResource struct {
  buffalo.Resource
}

w := app.Resource("/widgets", WidgetsResource{})
w.Resource("/images", ImagesResource{})

This results in the following routes:

​ 这将导致以下路由:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
$ buffalo routes

METHOD | HOST                  | PATH                                         | ALIASES | NAME                | HANDLER
------ | ----                  | ----                                         | ------- | ----                | -------
GET    | http://127.0.0.1:3000 | /                                            |         | rootPath            | coke/actions.HomeHandler
GET    | http://127.0.0.1:3000 | /widgets/                                    |         | widgetsPath         | coke/actions.WidgetsResource.List
POST   | http://127.0.0.1:3000 | /widgets/                                    |         | widgetsPath         | coke/actions.WidgetsResource.Create
GET    | http://127.0.0.1:3000 | /widgets/new/                                |         | newWidgetsPath      | coke/actions.WidgetsResource.New
GET    | http://127.0.0.1:3000 | /widgets/{widget_id}/                        |         | widgetPath          | coke/actions.WidgetsResource.Show
PUT    | http://127.0.0.1:3000 | /widgets/{widget_id}/                        |         | widgetPath          | coke/actions.WidgetsResource.Update
DELETE | http://127.0.0.1:3000 | /widgets/{widget_id}/                        |         | widgetPath          | coke/actions.WidgetsResource.Destroy
GET    | http://127.0.0.1:3000 | /widgets/{widget_id}/edit/                   |         | editWidgetPath      | coke/actions.WidgetsResource.Edit
GET    | http://127.0.0.1:3000 | /widgets/{widget_id}/images/                 |         | widgetImagesPath    | coke/actions.ImagesResource.List
POST   | http://127.0.0.1:3000 | /widgets/{widget_id}/images/                 |         | widgetImagesPath    | coke/actions.ImagesResource.Create
GET    | http://127.0.0.1:3000 | /widgets/{widget_id}/images/new/             |         | newWidgetImagesPath | coke/actions.ImagesResource.New
GET    | http://127.0.0.1:3000 | /widgets/{widget_id}/images/{image_id}/      |         | widgetImagePath     | coke/actions.ImagesResource.Show
PUT    | http://127.0.0.1:3000 | /widgets/{widget_id}/images/{image_id}/      |         | widgetImagePath     | coke/actions.ImagesResource.Update
DELETE | http://127.0.0.1:3000 | /widgets/{widget_id}/images/{image_id}/      |         | widgetImagePath     | coke/actions.ImagesResource.Destroy
GET    | http://127.0.0.1:3000 | /widgets/{widget_id}/images/{image_id}/edit/ |         | editWidgetImagePath | coke/actions.ImagesResource.Edit

buffalo.BaseResource

When a resource is generated it has buffalo.BaseResource embedded into it.

​ 生成资源时,其中嵌入了 buffalo.BaseResource

1
2
3
type Widget struct {
  buffalo.BaseResource
}

The buffalo.BaseResource has basic implementations for all of the methods required by buffalo.Resource. These methods all 404.

buffalo.BaseResource 具有 buffalo.Resource 所需的所有方法的基本实现。这些方法全部 404

1
2
3
4
// Edit default implementation. Returns a 404
func (v BaseResource) Edit(c Context) error {
  return c.Error(http.StatusNotFound, errors.New("resource not implemented"))
}

Video Presentation 视频演示

  • Actions - Learn more about Buffalo actions. 操作 - 了解有关 Buffalo 操作的更多信息。

Next Steps 后续步骤

  • Context - Learn more about Buffalo context. 上下文 - 了解有关 Buffalo 上下文的更多信息。
最后修改 February 4, 2024: 更新 (98d4a18)