Gestión de Peticiones
Recursos#
A menudo las aplicaciones Web necesitan desarrollar end-points muy parecidos al modelo CRUD. Para reducir la complejidad que esto añade, Buffalo soporta el concepto de “Recurso”.
La interfaz github.com/gobuffalo/buffalo#Resource
permite a Buffalo asignar rutas comunes y responder a solicitudes comunes.
type Resource interface {
List(Context) error
Show(Context) error
Create(Context) error
Update(Context) error
Destroy(Context) error
}
La interfaz github.com/gobuffalo/buffalo#Resource
se ha reducido en la versión v0.14.1
. Los métodos New
y Edit
, que sirven a los formularios HTML para editar el recurso, son ahora opcionales.
Así es como se veía la interfaz antes:
type Resource interface {
List(Context) error
Show(Context) error
New(Context) error
Create(Context) error
Edit(Context) error
Update(Context) error
Destroy(Context) error
}
Uso de Recursos#
Luego de implementar los métodos necesarios de la interfaz github.com/gobuffalo/buffalo#Resource
, el recurso puede ser mapeado en la aplicación utilizando el método github.com/gobuffalo/buffalo#App.Resource
.
// 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
}
Mapeando el Recurso en app.go:
// actions/app.go
app.Resource("/users", UsersResource{})
El código de ejemplo anterior sería el equivalente al siguente:
// 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)
Produciría una tabla de rutas que luce similar a la siguiente:
$ 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
Métodos de Recursos Opcionales#
En la version v0.14.1
el github.com/gobuffalo/buffalo#Resource
se redujo con los siguientes métodos que son ahora opcionales:
New(Context) error
Edit(Context) error
Cuando se implementan los métodos New
y Edit
se añade lo siguiente a la tabla de enrutamiento:
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
Generando Recursos#
El comando buffalo generate resource
generará los modelos, las migraciones, el código Go y el HTML necesarios para el CRUD del recurso.
Cuando se ejecuta el generador en una aplicación API Buffalo generará código para cumplir con la interfaz github.com/gobuffalo/buffalo#Resource
.
type Resource interface {
List(Context) error
Show(Context) error
Create(Context) error
Update(Context) error
Destroy(Context) error
}
Cuando se ejecuta el generador en una aplicación web, Buffalo generará código para cumplir con la interfaz github.com/gobuffalo/buffalo#Resource
, así como los métodos opcionales New y Edit
type Resource interface {
List(Context) error
Show(Context) error
New(Context) error
Create(Context) error
Edit(Context) error
Update(Context) error
Destroy(Context) error
}
Ejemplo de Generación de Recursos#
En este ejemplo Buffalo generará el código necesario para hacer un CRUD de un recurso llamado widget
(Go: Widget
) que tiene los siguientes atributos:
Model Attribute | Go Type | DB type | Form Type | |
---|---|---|---|---|
title |
Title |
string |
varchar |
text |
description |
Description |
nulls.String |
varchar (nullable) |
textarea |
$ buffalo generate resource widget title description:nulls.Text
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/en/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/en/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"},
})
}
- 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."
create_table("widgets") {
t.Column("id", "uuid", {primary: true})
t.Column("title", "string", {})
t.Column("description", "text", {null: true})
t.Timestamps()
}
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
}
<%= f.InputTag("Title") %>
<%= f.TextAreaTag("Description", {rows: 10}) %>
<button class="btn btn-success" role="submit">Save</button>
Eliminando recursos#
Puedes eliminar los archivos generados por este generador ejecutando:
$ buffalo destroy resource users
Este comando te preguntará qué archivos quiere eliminar, puede responder a cada una de las preguntas con y/n
o puede pasar el flag -y
al comando de la siguiente manera:
$ buffalo destroy resource users -y
O en forma abreviada:
$ buffalo d r users -y
Anidación de Recursos#
Para simplificar la creación de jerarquías de recursos, Buffalo admite la anidación de recursos.
type WidgetsResource struct {
buffalo.Resource
}
type ImagesResource struct {
buffalo.Resource
}
w := app.Resource("/widgets", WidgetsResource{})
w.Resource("/images", ImagesResource{})
Esto da como resultado las siguientes rutas:
$ 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#
Cuando se genera un recurso, éste tiene incorporado el buffalo.BaseResource
.
type Widget struct {
buffalo.BaseResource
}
El buffalo.BaseResource
tiene implementaciones básicas para todos los métodos requeridos por buffalo.Resource
. Todos estos métodos retornan un 404
por defecto.
// Edit default implementation. Returns a 404
func (v BaseResource) Edit(c Context) error {
return c.Error(http.StatusNotFound, errors.New("resource not implemented"))
}
Video de presentación#
Contenido relacionado#
- Acciones - Más información sobre las acciones de Buffalo.
Siguientes pasos#
- Contexto - Más información sobre el contexto de Buffalo.