mirror of
https://github.com/kataras/iris.git
synced 2025-03-11 01:06:20 +01:00
Enhance the MVC "using-method-result" example. Prev: update to version 8.5.0
Prev Commit: 49ee8f2d75
[formerly 6a3579f2500fc715d7dc606478960946dcade61d]
Changelog: https://github.com/kataras/iris/blob/master/HISTORY.md#mo-09-october-2017--v850
This example is updated with the current commit: https://github.com/kataras/iris/tree/master/_examples/mvc/using-output-result
Former-commit-id: 29486ef014b3667fa1c7c66e11c8e95c76a37e57
This commit is contained in:
parent
49ee8f2d75
commit
82b5a1d4ed
10
HISTORY.md
10
HISTORY.md
|
@ -18,13 +18,13 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene
|
|||
|
||||
**How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`.
|
||||
|
||||
# Su, 09 October 2017 | v8.5.0
|
||||
# Mo, 09 October 2017 | v8.5.0
|
||||
|
||||
## MVC
|
||||
|
||||
Great news for our **MVC** Fans or if you're not you may want to use that powerful feature today, because of the smart coding and decisions the performance is quite the same to the pure handlers, see [_benchmarks](_benchmarks).
|
||||
|
||||
Iris now gives you the ability to render a response based on the **output values** returned method functions!
|
||||
Iris now gives you the ability to render a response based on the **output values** returned from the controller's method functions!
|
||||
|
||||
You can return any value of any type from a method function
|
||||
and it will be sent to the client as expected.
|
||||
|
@ -162,14 +162,10 @@ func (c *MoviesController) DeleteBy(id int) iris.Map {
|
|||
}
|
||||
```
|
||||
|
||||
Another good example with a typical folder structure, that many developers are used to work, is located at the new [README.md](README.md) under the [Quick MVC Tutorial #3](README.md#quick-mvc-tutorial--3) section.
|
||||
Another good example with a typical folder structure, that many developers are used to work, is located at the new [README.md](README.md) under the [Quick MVC Tutorial #3](README.md#quick-mvc-tutorial-3) section.
|
||||
|
||||
### The complete example source code can be found at [_examples/mvc/using-method-result](_examples/mvc/using-method-result) folder.
|
||||
|
||||
----
|
||||
|
||||
Upgrade with `go get -u -v github.com/kataras/iris` or let the auto-updater to do its job.
|
||||
|
||||
# Fr, 06 October 2017 | v8.4.5
|
||||
|
||||
- Badger team added support for transactions [yesterday](https://github.com/dgraph-io/badger/commit/06242925c2f2a5e73dc688e9049004029dd7f9f7), therefore the [badger session database](sessions/sessiondb/badger) is updated via https://github.com/kataras/iris/commit/0b48927562a2202809a7674ebedb738dc3da57e8.
|
||||
|
|
340
README.md
340
README.md
|
@ -4,7 +4,7 @@ Iris is a fast, simple and efficient micro web framework for Go. It provides a b
|
|||
|
||||
### About our User Experience Report
|
||||
|
||||
Three days ago, _at 03 October_, we announced the first [Iris User Experience form-based Report](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) to let us learn more about you and any issues that troubles you with Iris (if any).
|
||||
A week ago, _at 03 October_, we announced the first [Iris User Experience form-based Report](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) to let us learn more about you and any issues that troubles you with Iris (if any).
|
||||
|
||||
At overall, the results (so far) are very promising, high number of participations and the answers to the questions are near to the green feedback we were receiving over the past months from Gophers worldwide via our [rocket chat](https://chat.iris-go.com) and [author's twitter](https://twitter.com/makismaropoulos). **If you didn't complete the form yet, [please do so](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) as soon as possible!**
|
||||
|
||||
|
@ -67,7 +67,7 @@ $ go get -u github.com/kataras/iris
|
|||
|
||||
- Iris takes advantage of the [vendor directory](https://docs.google.com/document/d/1Bz5-UB7g2uPBdOx-rw5t9MxJwkfpx90cqG9AFL0JAYo) feature. You get truly reproducible builds, as this method guards against upstream renames and deletes.
|
||||
|
||||
- [Latest changes | v8.5.0](https://github.com/kataras/iris/blob/master/HISTORY.md#su-09-october-2017--v850)
|
||||
- [Latest changes | v8.5.0](https://github.com/kataras/iris/blob/master/HISTORY.md#mo-09-october-2017--v850)
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
@ -449,83 +449,336 @@ like shared layouts, tmpl funcs, reverse routing and more!
|
|||
|
||||
package models
|
||||
|
||||
import "github.com/kataras/iris/context"
|
||||
|
||||
// Movie is our sample data structure.
|
||||
type Movie struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Year int `json:"year"`
|
||||
Genre string `json:"genre"`
|
||||
Poster string `json:"poster"`
|
||||
}
|
||||
|
||||
// Dispatch completes the `kataras/iris/mvc#Result` interface.
|
||||
// Sends a `Movie` as a controlled http response.
|
||||
// If its ID is zero or less then it returns a 404 not found error
|
||||
// else it returns its json representation,
|
||||
// (just like the controller's functions do for custom types by default).
|
||||
//
|
||||
// Don't overdo it, the application's logic should not be here.
|
||||
// It's just one more step of validation before the response,
|
||||
// simple checks can be added here.
|
||||
//
|
||||
// It's just a showcase,
|
||||
// imagine what possible this opens when you designing a bigger application.
|
||||
//
|
||||
// This is called where the return value from a controller's method functions
|
||||
// is type of `Movie`.
|
||||
// For example the `controllers/movie_controller.go#GetBy`.
|
||||
func (m Movie) Dispatch(ctx context.Context) {
|
||||
if m.ID <= 0 {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
ctx.JSON(m, context.JSON{Indent: " "})
|
||||
}
|
||||
```
|
||||
|
||||
> For those who wonder `iris.Context`(go 1.9 type alias feature) and `context.Context` is the same [exact thing](faq.md#type-aliases).
|
||||
|
||||
```go
|
||||
// file: services/movie_service.go
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/kataras/iris/_examples/mvc/using-method-result/models"
|
||||
)
|
||||
|
||||
// MovieService handles CRUID operations of a movie entity/model.
|
||||
// It's here to decouple the data source from the higher level compoments.
|
||||
// As a result a different service for a specific datasource (or repository)
|
||||
// can be used from the main application without any additional changes.
|
||||
type MovieService interface {
|
||||
GetSingle(query func(models.Movie) bool) (movie models.Movie, found bool)
|
||||
GetByID(id int64) (models.Movie, bool)
|
||||
|
||||
InsertOrUpdate(movie models.Movie) (models.Movie, error)
|
||||
DeleteByID(id int64) bool
|
||||
|
||||
GetMany(query func(models.Movie) bool, limit int) (result []models.Movie)
|
||||
GetAll() []models.Movie
|
||||
}
|
||||
|
||||
// NewMovieServiceFromMemory returns a new memory-based movie service.
|
||||
func NewMovieServiceFromMemory(source map[int64]models.Movie) MovieService {
|
||||
return &MovieMemoryService{
|
||||
source: source,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
A Movie Service can have different data sources:
|
||||
func NewMovieServiceFromDB(db datasource.MySQL) {
|
||||
return &MovieDatabaseService{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
Another pattern is to initialize the database connection
|
||||
or any source here based on a "string" name or an "enum".
|
||||
func NewMovieService(source string) MovieService {
|
||||
if source == "memory" {
|
||||
return NewMovieServiceFromMemory(datasource.Movies)
|
||||
}
|
||||
if source == "database" {
|
||||
db = datasource.NewDB("....")
|
||||
return NewMovieServiceFromDB(db)
|
||||
}
|
||||
[...]
|
||||
return nil
|
||||
}
|
||||
*/
|
||||
|
||||
// MovieMemoryService is a "MovieService"
|
||||
// which manages the movies using the memory data source (map).
|
||||
type MovieMemoryService struct {
|
||||
source map[int64]models.Movie
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// GetSingle receives a query function
|
||||
// which is fired for every single movie model inside
|
||||
// our imaginary data source.
|
||||
// When that function returns true then it stops the iteration.
|
||||
//
|
||||
// It returns the query's return last known boolean value
|
||||
// and the last known movie model
|
||||
// to help callers to reduce the LOC.
|
||||
//
|
||||
// It's actually a simple but very clever prototype function
|
||||
// I'm using everywhere since I firstly think of it,
|
||||
// hope you'll find it very useful as well.
|
||||
func (s *MovieMemoryService) GetSingle(query func(models.Movie) bool) (movie models.Movie, found bool) {
|
||||
s.mu.RLock()
|
||||
for _, movie = range s.source {
|
||||
found = query(movie)
|
||||
if found {
|
||||
break
|
||||
}
|
||||
}
|
||||
s.mu.RUnlock()
|
||||
|
||||
// set an empty models.Movie if not found at all.
|
||||
if !found {
|
||||
movie = models.Movie{}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetByID returns a movie based on its id.
|
||||
// Returns true if found, otherwise false, the bool should be always checked
|
||||
// because the models.Movie may be filled with the latest element
|
||||
// but not the correct one, although it can be used for debugging.
|
||||
func (s *MovieMemoryService) GetByID(id int64) (models.Movie, bool) {
|
||||
return s.GetSingle(func(m models.Movie) bool {
|
||||
return m.ID == id
|
||||
})
|
||||
}
|
||||
|
||||
// InsertOrUpdate adds or updates a movie to the (memory) storage.
|
||||
//
|
||||
// Returns the new movie and an error if any.
|
||||
func (s *MovieMemoryService) InsertOrUpdate(movie models.Movie) (models.Movie, error) {
|
||||
id := movie.ID
|
||||
|
||||
if id == 0 { // Create new action
|
||||
var lastID int64
|
||||
// find the biggest ID in order to not have duplications
|
||||
// in productions apps you can use a third-party
|
||||
// library to generate a UUID as string.
|
||||
s.mu.RLock()
|
||||
for _, item := range s.source {
|
||||
if item.ID > lastID {
|
||||
lastID = item.ID
|
||||
}
|
||||
}
|
||||
s.mu.RUnlock()
|
||||
|
||||
id = lastID + 1
|
||||
movie.ID = id
|
||||
|
||||
// map-specific thing
|
||||
s.mu.Lock()
|
||||
s.source[id] = movie
|
||||
s.mu.Unlock()
|
||||
|
||||
return movie, nil
|
||||
}
|
||||
|
||||
// Update action based on the movie.ID,
|
||||
// here we will allow updating the poster and genre if not empty.
|
||||
// Alternatively we could do pure replace instead:
|
||||
// s.source[id] = movie
|
||||
// and comment the code below;
|
||||
current, exists := s.GetByID(id)
|
||||
if !exists { // ID is not a real one, return an error.
|
||||
return models.Movie{}, errors.New("failed to update a nonexistent movie")
|
||||
}
|
||||
|
||||
// or comment these and s.source[id] = m for pure replace
|
||||
if movie.Poster != "" {
|
||||
current.Poster = movie.Poster
|
||||
}
|
||||
|
||||
if movie.Genre != "" {
|
||||
current.Genre = movie.Genre
|
||||
}
|
||||
|
||||
// map-specific thing
|
||||
s.mu.Lock()
|
||||
s.source[id] = current
|
||||
s.mu.Unlock()
|
||||
|
||||
return movie, nil
|
||||
}
|
||||
|
||||
// DeleteByID deletes a movie by its id.
|
||||
//
|
||||
// Returns true if deleted otherwise false.
|
||||
func (s *MovieMemoryService) DeleteByID(id int64) bool {
|
||||
if _, exists := s.GetByID(id); !exists {
|
||||
// we could do _, exists := s.source[id] instead
|
||||
// but we don't because you should learn
|
||||
// how you can use that service's functions
|
||||
// with any other source, i.e database.
|
||||
return false
|
||||
}
|
||||
|
||||
// map-specific thing
|
||||
s.mu.Lock()
|
||||
delete(s.source, id)
|
||||
s.mu.Unlock()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// GetMany same as GetSingle but returns one or more models.Movie as a slice.
|
||||
// If limit <=0 then it returns everything.
|
||||
func (s *MovieMemoryService) GetMany(query func(models.Movie) bool, limit int) (result []models.Movie) {
|
||||
loops := 0
|
||||
|
||||
s.mu.RLock()
|
||||
for _, movie := range s.source {
|
||||
loops++
|
||||
|
||||
passed := query(movie)
|
||||
if passed {
|
||||
result = append(result, movie)
|
||||
}
|
||||
// we have to return at least one movie if "passed" was true.
|
||||
if limit >= loops {
|
||||
break
|
||||
}
|
||||
}
|
||||
s.mu.RUnlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetAll returns all movies.
|
||||
func (s *MovieMemoryService) GetAll() []models.Movie {
|
||||
movies := s.GetMany(func(m models.Movie) bool { return true }, -1)
|
||||
return movies
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
```go
|
||||
// file: controllers/movies_controller.go
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/_examples/mvc/using-method-result/datasource"
|
||||
"errors"
|
||||
|
||||
"github.com/kataras/iris/_examples/mvc/using-method-result/models"
|
||||
"github.com/kataras/iris/_examples/mvc/using-method-result/services"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
// MoviesController is our /movies controller.
|
||||
type MoviesController struct {
|
||||
// MovieController is our /movies controller.
|
||||
type MovieController struct {
|
||||
// mvc.C is just a lightweight lightweight alternative
|
||||
// to the "mvc.Controller" controller type,
|
||||
// use it when you don't need mvc.Controller's fields
|
||||
// (you don't need those fields when you return values from the method functions).
|
||||
mvc.C
|
||||
|
||||
// Our MovieService, it's an interface which
|
||||
// is binded from the main application.
|
||||
Service services.MovieService
|
||||
}
|
||||
|
||||
// Get returns list of the movies.
|
||||
// Demo:
|
||||
// curl -i http://localhost:8080/movies
|
||||
func (c *MoviesController) Get() []models.Movie {
|
||||
return datasource.Movies
|
||||
func (c *MovieController) Get() []models.Movie {
|
||||
return c.Service.GetAll()
|
||||
}
|
||||
|
||||
// GetBy returns a movie.
|
||||
// Demo:
|
||||
// curl -i http://localhost:8080/movies/1
|
||||
func (c *MoviesController) GetBy(id int) models.Movie {
|
||||
return datasource.Movies[id]
|
||||
func (c *MovieController) GetBy(id int64) models.Movie {
|
||||
m, _ := c.Service.GetByID(id)
|
||||
return m
|
||||
}
|
||||
|
||||
// PutBy updates a movie.
|
||||
// Demo:
|
||||
// curl -i -X PUT -F "genre=Thriller" -F "poster=@/Users/kataras/Downloads/out.gif" http://localhost:8080/movies/1
|
||||
func (c *MoviesController) PutBy(id int) (models.Movie, int) {
|
||||
// get the movie
|
||||
m := datasource.Movies[id]
|
||||
|
||||
func (c *MovieController) PutBy(id int64) (models.Movie, error) {
|
||||
// get the request data for poster and genre
|
||||
file, info, err := c.Ctx.FormFile("poster")
|
||||
if err != nil {
|
||||
return models.Movie{}, iris.StatusInternalServerError
|
||||
return models.Movie{}, errors.New("failed due form file 'poster' missing")
|
||||
}
|
||||
// we don't need the file so close it now
|
||||
// we don't need the file so close it now.
|
||||
file.Close()
|
||||
|
||||
// imagine that is the url of the uploaded file...
|
||||
poster := info.Filename
|
||||
genre := c.Ctx.FormValue("genre")
|
||||
|
||||
// update the poster
|
||||
m.Poster = poster
|
||||
m.Genre = genre
|
||||
datasource.Movies[id] = m
|
||||
|
||||
return m, iris.StatusOK
|
||||
// update the movie and return it.
|
||||
return c.Service.InsertOrUpdate(models.Movie{
|
||||
ID: id,
|
||||
Poster: poster,
|
||||
Genre: genre,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteBy deletes a movie.
|
||||
// Demo:
|
||||
// curl -i -X DELETE -u admin:password http://localhost:8080/movies/1
|
||||
func (c *MoviesController) DeleteBy(id int) iris.Map {
|
||||
// delete the entry from the movies slice
|
||||
deleted := datasource.Movies[id].Name
|
||||
datasource.Movies = append(datasource.Movies[:id], datasource.Movies[id+1:]...)
|
||||
// and return the deleted movie's name
|
||||
return iris.Map{"deleted": deleted}
|
||||
func (c *MovieController) DeleteBy(id int64) interface{} {
|
||||
// delete the entry from the movies slice.
|
||||
wasDel := c.Service.DeleteByID(id)
|
||||
if wasDel {
|
||||
// and return the deleted movie's ID
|
||||
return iris.Map{"deleted": id}
|
||||
}
|
||||
// here we can see that a method function can return any of those two types(map or int),
|
||||
// we don't have to specify the return type to a specific type.
|
||||
return iris.StatusBadRequest
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -537,32 +790,37 @@ package datasource
|
|||
import "github.com/kataras/iris/_examples/mvc/using-method-result/models"
|
||||
|
||||
// Movies is our imaginary data source.
|
||||
var Movies = []models.Movie{
|
||||
{
|
||||
var Movies = map[int64]models.Movie{
|
||||
1: {
|
||||
ID: 1,
|
||||
Name: "Casablanca",
|
||||
Year: 1942,
|
||||
Genre: "Romance",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/1.jpg",
|
||||
},
|
||||
{
|
||||
2: {
|
||||
ID: 2,
|
||||
Name: "Gone with the Wind",
|
||||
Year: 1939,
|
||||
Genre: "Romance",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/2.jpg",
|
||||
},
|
||||
{
|
||||
3: {
|
||||
ID: 3,
|
||||
Name: "Citizen Kane",
|
||||
Year: 1941,
|
||||
Genre: "Mystery",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/3.jpg",
|
||||
},
|
||||
{
|
||||
4: {
|
||||
ID: 4,
|
||||
Name: "The Wizard of Oz",
|
||||
Year: 1939,
|
||||
Genre: "Fantasy",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/4.jpg",
|
||||
},
|
||||
{
|
||||
5: {
|
||||
ID: 5,
|
||||
Name: "North by Northwest",
|
||||
Year: 1959,
|
||||
Genre: "Thriller",
|
||||
|
@ -593,21 +851,31 @@ package main
|
|||
|
||||
import (
|
||||
"github.com/kataras/iris/_examples/mvc/using-method-result/controllers"
|
||||
"github.com/kataras/iris/_examples/mvc/using-method-result/datasource"
|
||||
"github.com/kataras/iris/_examples/mvc/using-method-result/middleware"
|
||||
"github.com/kataras/iris/_examples/mvc/using-method-result/services"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
// Load the template files.
|
||||
app.RegisterView(iris.HTML("./views", ".html"))
|
||||
|
||||
// Register our controllers.
|
||||
app.Controller("/hello", new(controllers.HelloController))
|
||||
// Add the basic authentication(admin:password) middleware
|
||||
// for the /movies based requests.
|
||||
app.Controller("/movies", new(controllers.MoviesController), middleware.BasicAuth)
|
||||
|
||||
// Create our movie service (memory), we will bind it to the movies controller.
|
||||
service := services.NewMovieServiceFromMemory(datasource.Movies)
|
||||
|
||||
app.Controller("/movies", new(controllers.MovieController),
|
||||
// Bind the "service" to the MovieController's Service (interface) field.
|
||||
service,
|
||||
// Add the basic authentication(admin:password) middleware
|
||||
// for the /movies based requests.
|
||||
middleware.BasicAuth)
|
||||
|
||||
// Start the web server at localhost:8080
|
||||
// http://localhost:8080/hello
|
||||
|
|
|
@ -86,7 +86,7 @@ _Psst_, we've produced a small video about your feelings regrating to Iris! You
|
|||
### 📑 Table Of Content
|
||||
|
||||
* [Installation](#-installation)
|
||||
* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#su-09-october-2017--v850)
|
||||
* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#mo-09-october-2017--v850)
|
||||
* [Learn](#-learn)
|
||||
* [Structuring](_examples/#structuring)
|
||||
* [HTTP Listening](_examples/#http-listening)
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
8.5.0:https://github.com/kataras/iris/blob/master/HISTORY.md#su-09-october-2017--v850
|
||||
8.5.0:https://github.com/kataras/iris/blob/master/HISTORY.md#mo-09-october-2017--v850
|
|
@ -27,7 +27,7 @@ func NewDataSource() *DataSource {
|
|||
}
|
||||
}
|
||||
|
||||
// GetBy returns receives a query function
|
||||
// GetBy receives a query function
|
||||
// which is fired for every single user model inside
|
||||
// our imaginary database.
|
||||
// When that function returns true then it stops the iteration.
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
// file: controllers/movies_controller.go
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/kataras/iris/_examples/mvc/using-method-result/models"
|
||||
"github.com/kataras/iris/_examples/mvc/using-method-result/services"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
// MovieController is our /movies controller.
|
||||
type MovieController struct {
|
||||
// mvc.C is just a lightweight lightweight alternative
|
||||
// to the "mvc.Controller" controller type,
|
||||
// use it when you don't need mvc.Controller's fields
|
||||
// (you don't need those fields when you return values from the method functions).
|
||||
mvc.C
|
||||
|
||||
// Our MovieService, it's an interface which
|
||||
// is binded from the main application.
|
||||
Service services.MovieService
|
||||
}
|
||||
|
||||
// Get returns list of the movies.
|
||||
// Demo:
|
||||
// curl -i http://localhost:8080/movies
|
||||
func (c *MovieController) Get() []models.Movie {
|
||||
return c.Service.GetAll()
|
||||
}
|
||||
|
||||
// GetBy returns a movie.
|
||||
// Demo:
|
||||
// curl -i http://localhost:8080/movies/1
|
||||
func (c *MovieController) GetBy(id int64) models.Movie {
|
||||
m, _ := c.Service.GetByID(id)
|
||||
return m
|
||||
}
|
||||
|
||||
// PutBy updates a movie.
|
||||
// Demo:
|
||||
// curl -i -X PUT -F "genre=Thriller" -F "poster=@/Users/kataras/Downloads/out.gif" http://localhost:8080/movies/1
|
||||
func (c *MovieController) PutBy(id int64) (models.Movie, error) {
|
||||
// get the request data for poster and genre
|
||||
file, info, err := c.Ctx.FormFile("poster")
|
||||
if err != nil {
|
||||
return models.Movie{}, errors.New("failed due form file 'poster' missing")
|
||||
}
|
||||
// we don't need the file so close it now.
|
||||
file.Close()
|
||||
|
||||
// imagine that is the url of the uploaded file...
|
||||
poster := info.Filename
|
||||
genre := c.Ctx.FormValue("genre")
|
||||
|
||||
// update the movie and return it.
|
||||
return c.Service.InsertOrUpdate(models.Movie{
|
||||
ID: id,
|
||||
Poster: poster,
|
||||
Genre: genre,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteBy deletes a movie.
|
||||
// Demo:
|
||||
// curl -i -X DELETE -u admin:password http://localhost:8080/movies/1
|
||||
func (c *MovieController) DeleteBy(id int64) interface{} {
|
||||
// delete the entry from the movies slice.
|
||||
wasDel := c.Service.DeleteByID(id)
|
||||
if wasDel {
|
||||
// and return the deleted movie's ID
|
||||
return iris.Map{"deleted": id}
|
||||
}
|
||||
// here we can see that a method function can return any of those two types(map or int),
|
||||
// we don't have to specify the return type to a specific type.
|
||||
return iris.StatusBadRequest
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
// file: controllers/movies_controller.go
|
||||
//
|
||||
// This is just an example of usage, don't use it for production, it even doesn't check for
|
||||
// index exceed!
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/_examples/mvc/using-method-result/datasource"
|
||||
"github.com/kataras/iris/_examples/mvc/using-method-result/models"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/mvc"
|
||||
)
|
||||
|
||||
// MoviesController is our /movies controller.
|
||||
type MoviesController struct {
|
||||
// mvc.C is just a lightweight lightweight alternative
|
||||
// to the "mvc.Controller" controller type,
|
||||
// use it when you don't need mvc.Controller's fields
|
||||
// (you don't need those fields when you return values from the method functions).
|
||||
mvc.C
|
||||
}
|
||||
|
||||
// Get returns list of the movies.
|
||||
// Demo:
|
||||
// curl -i http://localhost:8080/movies
|
||||
func (c *MoviesController) Get() []models.Movie {
|
||||
return datasource.Movies
|
||||
}
|
||||
|
||||
// GetBy returns a movie.
|
||||
// Demo:
|
||||
// curl -i http://localhost:8080/movies/1
|
||||
func (c *MoviesController) GetBy(id int) models.Movie {
|
||||
return datasource.Movies[id]
|
||||
}
|
||||
|
||||
// PutBy updates a movie.
|
||||
// Demo:
|
||||
// curl -i -X PUT -F "genre=Thriller" -F "poster=@/Users/kataras/Downloads/out.gif" http://localhost:8080/movies/1
|
||||
func (c *MoviesController) PutBy(id int) (models.Movie, int) {
|
||||
// get the movie
|
||||
m := datasource.Movies[id]
|
||||
|
||||
// get the request data for poster and genre
|
||||
file, info, err := c.Ctx.FormFile("poster")
|
||||
if err != nil {
|
||||
return models.Movie{}, iris.StatusInternalServerError
|
||||
}
|
||||
// we don't need the file so close it now
|
||||
file.Close()
|
||||
|
||||
// imagine that is the url of the uploaded file...
|
||||
poster := info.Filename
|
||||
genre := c.Ctx.FormValue("genre")
|
||||
|
||||
// update the poster
|
||||
m.Poster = poster
|
||||
m.Genre = genre
|
||||
datasource.Movies[id] = m
|
||||
|
||||
return m, iris.StatusOK
|
||||
}
|
||||
|
||||
// DeleteBy deletes a movie.
|
||||
// Demo:
|
||||
// curl -i -X DELETE -u admin:password http://localhost:8080/movies/1
|
||||
func (c *MoviesController) DeleteBy(id int) iris.Map {
|
||||
// delete the entry from the movies slice
|
||||
deleted := datasource.Movies[id].Name
|
||||
datasource.Movies = append(datasource.Movies[:id], datasource.Movies[id+1:]...)
|
||||
// and return the deleted movie's name
|
||||
return iris.Map{"deleted": deleted}
|
||||
}
|
|
@ -5,32 +5,37 @@ package datasource
|
|||
import "github.com/kataras/iris/_examples/mvc/using-method-result/models"
|
||||
|
||||
// Movies is our imaginary data source.
|
||||
var Movies = []models.Movie{
|
||||
{
|
||||
var Movies = map[int64]models.Movie{
|
||||
1: {
|
||||
ID: 1,
|
||||
Name: "Casablanca",
|
||||
Year: 1942,
|
||||
Genre: "Romance",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/1.jpg",
|
||||
},
|
||||
{
|
||||
2: {
|
||||
ID: 2,
|
||||
Name: "Gone with the Wind",
|
||||
Year: 1939,
|
||||
Genre: "Romance",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/2.jpg",
|
||||
},
|
||||
{
|
||||
3: {
|
||||
ID: 3,
|
||||
Name: "Citizen Kane",
|
||||
Year: 1941,
|
||||
Genre: "Mystery",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/3.jpg",
|
||||
},
|
||||
{
|
||||
4: {
|
||||
ID: 4,
|
||||
Name: "The Wizard of Oz",
|
||||
Year: 1939,
|
||||
Genre: "Fantasy",
|
||||
Poster: "https://iris-go.com/images/examples/mvc-movies/4.jpg",
|
||||
},
|
||||
{
|
||||
5: {
|
||||
ID: 5,
|
||||
Name: "North by Northwest",
|
||||
Year: 1959,
|
||||
Genre: "Thriller",
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 25 KiB |
|
@ -4,21 +4,31 @@ package main
|
|||
|
||||
import (
|
||||
"github.com/kataras/iris/_examples/mvc/using-method-result/controllers"
|
||||
"github.com/kataras/iris/_examples/mvc/using-method-result/datasource"
|
||||
"github.com/kataras/iris/_examples/mvc/using-method-result/middleware"
|
||||
"github.com/kataras/iris/_examples/mvc/using-method-result/services"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
// Load the template files.
|
||||
app.RegisterView(iris.HTML("./views", ".html"))
|
||||
|
||||
// Register our controllers.
|
||||
app.Controller("/hello", new(controllers.HelloController))
|
||||
// Add the basic authentication(admin:password) middleware
|
||||
// for the /movies based requests.
|
||||
app.Controller("/movies", new(controllers.MoviesController), middleware.BasicAuth)
|
||||
|
||||
// Create our movie service (memory), we will bind it to the movies controller.
|
||||
service := services.NewMovieServiceFromMemory(datasource.Movies)
|
||||
|
||||
app.Controller("/movies", new(controllers.MovieController),
|
||||
// Bind the "service" to the MovieController's Service (interface) field.
|
||||
service,
|
||||
// Add the basic authentication(admin:password) middleware
|
||||
// for the /movies based requests.
|
||||
middleware.BasicAuth)
|
||||
|
||||
// Start the web server at localhost:8080
|
||||
// http://localhost:8080/hello
|
||||
|
|
|
@ -2,10 +2,40 @@
|
|||
|
||||
package models
|
||||
|
||||
import "github.com/kataras/iris/context"
|
||||
|
||||
// Movie is our sample data structure.
|
||||
type Movie struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Year int `json:"year"`
|
||||
Genre string `json:"genre"`
|
||||
Poster string `json:"poster"`
|
||||
}
|
||||
|
||||
// Dispatch completes the `kataras/iris/mvc#Result` interface.
|
||||
// Sends a `Movie` as a controlled http response.
|
||||
// If its ID is zero or less then it returns a 404 not found error
|
||||
// else it returns its json representation,
|
||||
// (just like the controller's functions do for custom types by default).
|
||||
//
|
||||
// Don't overdo it, the application's logic should not be here.
|
||||
// It's just one more step of validation before the response,
|
||||
// simple checks can be added here.
|
||||
//
|
||||
// It's just a showcase,
|
||||
// imagine what possible this opens when you designing a bigger application.
|
||||
//
|
||||
// This is called where the return value from a controller's method functions
|
||||
// is type of `Movie`.
|
||||
// For example the `controllers/movie_controller.go#GetBy`.
|
||||
func (m Movie) Dispatch(ctx context.Context) {
|
||||
if m.ID <= 0 {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
ctx.JSON(m, context.JSON{Indent: " "})
|
||||
}
|
||||
|
||||
// For those who wonder `iris.Context`(go 1.9 type alias feature) and
|
||||
// `context.Context` is the same exact thing.
|
||||
|
|
206
_examples/mvc/using-method-result/services/movie_service.go
Normal file
206
_examples/mvc/using-method-result/services/movie_service.go
Normal file
|
@ -0,0 +1,206 @@
|
|||
// file: services/movie_service.go
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/kataras/iris/_examples/mvc/using-method-result/models"
|
||||
)
|
||||
|
||||
// MovieService handles CRUID operations of a movie entity/model.
|
||||
// It's here to decouple the data source from the higher level compoments.
|
||||
// As a result a different service for a specific datasource (or repository)
|
||||
// can be used from the main application without any additional changes.
|
||||
type MovieService interface {
|
||||
GetSingle(query func(models.Movie) bool) (movie models.Movie, found bool)
|
||||
GetByID(id int64) (models.Movie, bool)
|
||||
|
||||
InsertOrUpdate(movie models.Movie) (models.Movie, error)
|
||||
DeleteByID(id int64) bool
|
||||
|
||||
GetMany(query func(models.Movie) bool, limit int) (result []models.Movie)
|
||||
GetAll() []models.Movie
|
||||
}
|
||||
|
||||
// NewMovieServiceFromMemory returns a new memory-based movie service.
|
||||
func NewMovieServiceFromMemory(source map[int64]models.Movie) MovieService {
|
||||
return &MovieMemoryService{
|
||||
source: source,
|
||||
}
|
||||
}
|
||||
|
||||
// A Movie Service can have different data sources:
|
||||
// func NewMovieServiceFromDB(db datasource.MySQL) {
|
||||
// return &MovieDatabaseService{
|
||||
// db: db,
|
||||
// }
|
||||
// }
|
||||
|
||||
// Another pattern is to initialize the database connection
|
||||
// or any source here based on a "string" name or an "enum".
|
||||
// func NewMovieService(source string) MovieService {
|
||||
// if source == "memory" {
|
||||
// return NewMovieServiceFromMemory(datasource.Movies)
|
||||
// }
|
||||
// if source == "database" {
|
||||
// db = datasource.NewDB("....")
|
||||
// return NewMovieServiceFromDB(db)
|
||||
// }
|
||||
// [...]
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// MovieMemoryService is a "MovieService"
|
||||
// which manages the movies using the memory data source (map).
|
||||
type MovieMemoryService struct {
|
||||
source map[int64]models.Movie
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// GetSingle receives a query function
|
||||
// which is fired for every single movie model inside
|
||||
// our imaginary data source.
|
||||
// When that function returns true then it stops the iteration.
|
||||
//
|
||||
// It returns the query's return last known boolean value
|
||||
// and the last known movie model
|
||||
// to help callers to reduce the LOC.
|
||||
//
|
||||
// It's actually a simple but very clever prototype function
|
||||
// I'm using everywhere since I firstly think of it,
|
||||
// hope you'll find it very useful as well.
|
||||
func (s *MovieMemoryService) GetSingle(query func(models.Movie) bool) (movie models.Movie, found bool) {
|
||||
s.mu.RLock()
|
||||
for _, movie = range s.source {
|
||||
found = query(movie)
|
||||
if found {
|
||||
break
|
||||
}
|
||||
}
|
||||
s.mu.RUnlock()
|
||||
|
||||
// set an empty models.Movie if not found at all.
|
||||
if !found {
|
||||
movie = models.Movie{}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetByID returns a movie based on its id.
|
||||
// Returns true if found, otherwise false, the bool should be always checked
|
||||
// because the models.Movie may be filled with the latest element
|
||||
// but not the correct one, although it can be used for debugging.
|
||||
func (s *MovieMemoryService) GetByID(id int64) (models.Movie, bool) {
|
||||
return s.GetSingle(func(m models.Movie) bool {
|
||||
return m.ID == id
|
||||
})
|
||||
}
|
||||
|
||||
// InsertOrUpdate adds or updates a movie to the (memory) storage.
|
||||
//
|
||||
// Returns the new movie and an error if any.
|
||||
func (s *MovieMemoryService) InsertOrUpdate(movie models.Movie) (models.Movie, error) {
|
||||
id := movie.ID
|
||||
|
||||
if id == 0 { // Create new action
|
||||
var lastID int64
|
||||
// find the biggest ID in order to not have duplications
|
||||
// in productions apps you can use a third-party
|
||||
// library to generate a UUID as string.
|
||||
s.mu.RLock()
|
||||
for _, item := range s.source {
|
||||
if item.ID > lastID {
|
||||
lastID = item.ID
|
||||
}
|
||||
}
|
||||
s.mu.RUnlock()
|
||||
|
||||
id = lastID + 1
|
||||
movie.ID = id
|
||||
|
||||
// map-specific thing
|
||||
s.mu.Lock()
|
||||
s.source[id] = movie
|
||||
s.mu.Unlock()
|
||||
|
||||
return movie, nil
|
||||
}
|
||||
|
||||
// Update action based on the movie.ID,
|
||||
// here we will allow updating the poster and genre if not empty.
|
||||
// Alternatively we could do pure replace instead:
|
||||
// s.source[id] = movie
|
||||
// and comment the code below;
|
||||
current, exists := s.GetByID(id)
|
||||
if !exists { // ID is not a real one, return an error.
|
||||
return models.Movie{}, errors.New("failed to update a nonexistent movie")
|
||||
}
|
||||
|
||||
// or comment these and s.source[id] = m for pure replace
|
||||
if movie.Poster != "" {
|
||||
current.Poster = movie.Poster
|
||||
}
|
||||
|
||||
if movie.Genre != "" {
|
||||
current.Genre = movie.Genre
|
||||
}
|
||||
|
||||
// map-specific thing
|
||||
s.mu.Lock()
|
||||
s.source[id] = current
|
||||
s.mu.Unlock()
|
||||
|
||||
return movie, nil
|
||||
}
|
||||
|
||||
// DeleteByID deletes a movie by its id.
|
||||
//
|
||||
// Returns true if deleted otherwise false.
|
||||
func (s *MovieMemoryService) DeleteByID(id int64) bool {
|
||||
if _, exists := s.GetByID(id); !exists {
|
||||
// we could do _, exists := s.source[id] instead
|
||||
// but we don't because you should learn
|
||||
// how you can use that service's functions
|
||||
// with any other source, i.e database.
|
||||
return false
|
||||
}
|
||||
|
||||
// map-specific thing
|
||||
s.mu.Lock()
|
||||
delete(s.source, id)
|
||||
s.mu.Unlock()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// GetMany same as GetSingle but returns one or more models.Movie as a slice.
|
||||
// If limit <=0 then it returns everything.
|
||||
func (s *MovieMemoryService) GetMany(query func(models.Movie) bool, limit int) (result []models.Movie) {
|
||||
loops := 0
|
||||
|
||||
s.mu.RLock()
|
||||
for _, movie := range s.source {
|
||||
loops++
|
||||
|
||||
passed := query(movie)
|
||||
if passed {
|
||||
result = append(result, movie)
|
||||
}
|
||||
// we have to return at least one movie if "passed" was true.
|
||||
if limit >= loops {
|
||||
break
|
||||
}
|
||||
}
|
||||
s.mu.RUnlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetAll returns all movies.
|
||||
func (s *MovieMemoryService) GetAll() []models.Movie {
|
||||
movies := s.GetMany(func(m models.Movie) bool { return true }, -1)
|
||||
return movies
|
||||
}
|
|
@ -69,6 +69,16 @@ func (b *binder) lookup(elem reflect.Type) (fields []field.Field) {
|
|||
}
|
||||
|
||||
matcher := func(elemField reflect.StructField) bool {
|
||||
// If the controller's field is interface then check
|
||||
// if the given binded value implements that interface.
|
||||
// i.e MovieController { service services.MoviesController /* interface */ }
|
||||
// app.Controller("/", new(MovieController),
|
||||
// services.NewMovieMemoryController(...))
|
||||
// *MovieMemoryService type
|
||||
// that implements the MovieService interface.
|
||||
if elemField.Type.Kind() == reflect.Interface {
|
||||
return value.Type().Implements(elemField.Type)
|
||||
}
|
||||
return elemField.Type == value.Type()
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user