1
0
mirror of https://github.com/kataras/iris.git synced 2025-12-19 02:47:04 +00: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:
Gerasimos (Makis) Maropoulos
2017-10-10 04:46:32 +03:00
parent 49ee8f2d75
commit 82b5a1d4ed
13 changed files with 660 additions and 130 deletions

View File

@@ -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.

View File

@@ -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
}

View File

@@ -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}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View 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
}