Escolar Documentos
Profissional Documentos
Cultura Documentos
Richard Clayton
Goal:
Build a simple Revel controller that performs CRUD operations against
a MySQL database.
Caveats:
Use RESTful Paths.
Use JSON request bodies and responses.
Use Revel Validation.
This setup should reflect a backend that you would use with a Single
Page Application framework like AngularJS.
Synopsis:
I'm building a simple auction management system for the wife. One of
the model items I have to deal with is a BidItem , which represents
something you can purchase during the bidding process. I'll
demonstrate how to build the full controller and database bindings for
this one model item.
We will implement the controller with the following steps:
1. Define REST Routes.
2. Define Model.
3. Implement Validation Logic.
4. Set up the GorpController .
5. Register Configuration.
6. Set up the Database.
What I will not go into is security and proper response handling. Also,
I'm going to be as succinct as possible so I don't waste your time.
/item/:id
/item
/item/:id
/item/:id
/items
BidItemCtrl.Get
BidItemCtrl.Add
BidItemCtrl.Update
BidItemCtrl.Delete
BidItemCtrl.List
The first two columns are obvious (HTTP Verb and Route). The :id is
an example of placeholder syntax. Revel will provides three ways to
get this value, including using the value as a function argument. The
BidItemCtrl.<Func> refers to the controller and method that will
2. Define Model.
I've created a folder called models in <app-root>/app . This is where
I define my models. This is my model in file
<app-root>/app/models/bid-item.go :
package models
type BidItem struct
Id
Name
Category
json:"category"`
EstimatedValue
json:"est_value"`
StartBid
json:"start_bid"`
BidIncrement
json:"bid_incr"`
InstantBuy
json:"inst_buy"`
}
{
int64
string
string
`db:"id" json:"id"`
`db:"name" json:"name"`
`db:"category"
float32 `db:"est_value"
float32 `db:"start_bid"
float32 `db:"bid_incr"
float32 `db:"inst_buy"
Note the meta information after each property on the structure. The
db:"<name>" is the desired column-name in the database and the
json:"<name>" is the JSON property name used in
that). Of course, this means we are going to trade off having a clean
model (unaware of other frameworks) for development expedience.
import (
"github.com/revel/revel"
"regexp"
)
// ...
func (b *BidItem) Validate(v *revel.Validation) {
v.Check(b.Name,
revel.ValidRequired(),
revel.ValidMaxSize(25))
v.Check(b.Category,
revel.ValidRequired(),
revel.ValidMatch(
regexp.MustCompile(
"^(travel|leasure|sports|entertainment)
$")))
v.Check(b.EstimatedValue,
revel.ValidRequired())
v.Check(b.StartBid,
revel.ValidRequired())
v.Check(b.BidIncrement,
revel.ValidRequired())
ruleset for model. It's certainly not complete and leaves much to be
desired.
4. Set up the
GorpController
If you would like transactions for your database, but don't want to
have to manually set them up in your controller, I would recommend
following this step. Keep in mind, all the code I show you after this will
utilize this functionality.
If you go spelunking in the Revel sample projects, you will find this
little gem in the Bookings project. This is the GorpController , which
is a simple extension to the revel.Controller that defines some boiler
plate around wrapping controller methods in database transactions.
I've moved out the database creation code into a separate file (which
we will come back to in step 6), so the file should now look like this:
package controllers
import (
"github.com/coopernurse/gorp"
"database/sql"
"github.com/revel/revel"
var (
Dbm *gorp.DbMap
)
type GorpController struct {
*revel.Controller
Txn *gorp.Transaction
}
func (c *GorpController) Begin() revel.Result {
txn, err := Dbm.Begin()
if err != nil {
panic(err)
}
c.Txn = txn
return nil
}
func (c *GorpController) Commit() revel.Result {
if c.Txn == nil {
return nil
}
if err := c.Txn.Commit(); err != nil && err !=
sql.ErrTxDone {
panic(err)
}
c.Txn = nil
return nil
}
func (c *GorpController) Rollback() revel.Result {
if c.Txn == nil {
return nil
}
if err := c.Txn.Rollback(); err != nil && err !=
sql.ErrTxDone {
panic(err)
}
c.Txn = nil
return nil
}
Two important variables to note are the var ( Dbm *gorp.DbMap ) and
the GorpController 's property Txn *gorp.Transaction . We will use
the Dbm variable to perform database creation and the Txn variable
to execute queries and commands against MySQL in the controller.
file, we will define an init() function which will register the handlers:
package controllers
import "github.com/revel/revel"
func init(){
revel.InterceptMethod((*GorpController).Begin,
revel.BEFORE)
revel.InterceptMethod((*GorpController).Commit,
revel.AFTER)
revel.InterceptMethod((*GorpController).Rollback,
revel.FINALLY)
}
5. Register Configuration.
We are about ready to start setting up the database. Instead of
[dev]
db.user = auctioneer
db.password = password
db.host = 192.168.24.42
db.port = 3306
db.name = auction
Now these values will be made available to use at runtime via the Revel
API: revel.Config.String(paramName) .
connection to the database and creating the tables for our model if the
table does not already exist.
We are going to go back to our <app-root>/app/controllers/init.go
file and add the logic to create a database connection, as well as, the
table for our BidItem model if it does not exist.
First, I'm going to add two helper functions. The first is a more generic
way for us to pull out configuration values from Revel (including
One thing to note in the code above is that we are setting the Dbm
connection, Dbm , we will define our BidItem schema and create the
table if it does not exist. We have not yet written the
import (
"github.com/revel/revel"
"github.com/coopernurse/gorp"
"database/sql"
_ "github.com/go-sql-driver/mysql"
"fmt"
"strings"
)
import statement, your project will break. The reason is that GORP
The mysql package implements those interfaces, but you will not see
any direct reference to the library in the code.
Notice how I use the term define and not create. This is because the
database is not actually created until the call to
Dbm.CreateTablesIfNotExists() .
When we are done with these steps, you will be able to start the
application and verify the table creation in MySQL. If you are curious
as to how GORP defines the table, here's a screen capture of
mysql > describe BidItem; :
7. Extend the
GorpController
package controllers
import (
"auction/app/models"
"github.com/revel/revel"
"encoding/json"
)
type BidItemCtrl struct {
GorpController
}
method, but that should be obvious since you do that anyway in most
web frameworks (albeit it's a little it's a little less elegant in Revel).
Add BidItem.
Inserting the record is pretty easy with GORP:
c.Txn.Insert(<pointer to struct>) .
Get BidItem.
func (c BidItemCtrl) Get(id int64) revel.Result {
biditem := new(models.BidItem)
err := c.Txn.SelectOne(biditem,
`SELECT * FROM BidItem WHERE id = ?`, id)
if err != nil {
return c.RenderText("Error. Item probably doesn't
exist.")
}
return c.RenderJson(biditem)
}
and a limit to indicate the amount of records we want. For this, we will
accept two query parameters: lid (last id) and limit (number of rows
to return).
Since we don't want a SQL injection attack, we're going to parse these
query options as integers. Here are a couple of functions to help the
parsing (keeping it in <app-root>/app/controllers/common.go ):
package controllers
import (
"strconv"
)
func parseUintOrDefault(intStr string, _default uint64)
uint64 {
if value, err := strconv.ParseUint(intStr, 0, 64);
err != nil {
return _default
} else {
return value
}
}
func parseIntOrDefault(intStr string, _default int64)
int64 {
if value, err := strconv.ParseInt(intStr, 0, 64);
err != nil {
return _default
} else {
return value
}
Update BidItem.
func (c BidItemCtrl) Update(id int64) revel.Result {
biditem, err := c.parseBidItem()
if err != nil {
return c.RenderText("Unable to parse the BidItem
from JSON.")
}
// Ensure the Id is set.
biditem.Id = id
success, err := c.Txn.Update(&biditem)
if err != nil || success == 0 {
return c.RenderText("Unable to update bid item.")
}
return c.RenderText("Updated %v", id)
}
Delete BidItem.
And the last action for the controller.
Conclusion.
While a little long, I think that was probably the most complete
walkthrough/example you'll find on implementing a Controller in
Revel with GORP and MySQL. I hope this helps you in your effort of
learning Go, GORP or Revel.
References.
http://revel.github.io/manual/index.html
https://github.com/coopernurse/gorp
https://github.com/revel/revel/tree/master/samples/booking
http://nathanleclaire.com/blog/2013/11/04/want-to-workwith-databases-in-golang-lets-try-some-gorp/
Plus many other StackOverflow articles and blog posts...
u 6 Comments
Blog Home
Archive
RSS