Skip to content

Commit

Permalink
Updated to use better resolvers
Browse files Browse the repository at this point in the history
- the past resolvers used an all CAPS system to differentiate the resolvers
  from the other methods, this was strange and felt wrong. THey have been
  changed into their own structs which have the db added in through a dependency
  injection

- added some helper functions for converting graphql and number types
- added a logging middleware
- changed some errant int32's to graphql.ID's
- added .gqlint file
- changed the schema descriptions to be quotes strings instead of comments
  • Loading branch information
jeffwillette committed Dec 8, 2018
1 parent 473e22b commit 9cb230d
Show file tree
Hide file tree
Showing 11 changed files with 542 additions and 345 deletions.
12 changes: 12 additions & 0 deletions .gqlint
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"rules": {
"camelcase": "error",
"fieldname.typename": "off",
"relay.connection": "error",
"relay.id": "error",
"singular.mutations": "error",
"enum.casing": "error",
"description.type": "warn",
"description.field": "off"
}
}
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ This example is meant to show a full implementation of the server using an SQL d

### Starting

start the server
start the server, if you don't have the sqlite library already installed from gorm, then it
may take a while to compile it

```
go run *.go
Expand All @@ -16,11 +17,15 @@ and then visit the GraphiQL dev server at `localhost:8080`

- int32 maps to the graphql type Int, so if a number is desired, int32 must be used

- the data structs are used for both database storage and graphql resolution.
- there are data structs for database models, and there is another struct that is usually
in the form of...

- resolver methods are all caps because the gorm ORM needs camel cased exported struct
fields in order to create database columns correctly. Another differentiation scheme can
be used if desired
```
type FooResolver struct {
db *DB
m Foo
}
```

- authentication could go in middleware in the server part

Expand Down
32 changes: 32 additions & 0 deletions helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package main

import (
"fmt"
"strconv"

graphql "github.com/graph-gophers/graphql-go"
"github.com/pkg/errors"
)

func gqlIDToUint(i graphql.ID) (uint, error) {
r, err := strconv.ParseInt(string(i), 10, 32)
if err != nil {
return 0, errors.Wrap(err, "GqlIDToUint")
}

return uint(r), nil
}

func int32P(i uint) *int32 {
r := int32(i)
return &r
}

func boolP(b bool) *bool {
return &b
}

func gqlIDP(id uint) *graphql.ID {
r := graphql.ID(fmt.Sprint(id))
return &r
}
59 changes: 38 additions & 21 deletions pet_tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

graphql "github.com/graph-gophers/graphql-go"
"github.com/jinzhu/gorm"
"github.com/pkg/errors"
)

// Tag is the base type for a pet tag to be used by the db and gql
Expand All @@ -14,32 +15,14 @@ type Tag struct {
Pets []Pet `gorm:"many2many:pet_tags"`
}

// RESOLVERS ===========================================================================

// ID resolves the ID for Tag
func (t *Tag) ID(ctx context.Context) *graphql.ID {
return gqlIDP(t.Model.ID)
}

// TITLE resolves the title field
func (t *Tag) TITLE(ctx context.Context) *string {
return &t.Title
}

// PETS resolves the pets field
func (t *Tag) PETS(ctx context.Context) (*[]*Pet, error) {
return db.getTagPets(ctx, t)
}

// DB ==================================================================================
func (db *DB) getTagPets(ctx context.Context, t *Tag) (*[]*Pet, error) {
var p []*Pet
func (db *DB) getTagPets(ctx context.Context, t *Tag) ([]Pet, error) {
var p []Pet
err := db.DB.Model(t).Related(&p, "Pets").Error
if err != nil {
return nil, err
}

return &p, nil
return p, nil
}

func (db *DB) getTagBytTitle(ctx context.Context, title string) (*Tag, error) {
Expand All @@ -51,3 +34,37 @@ func (db *DB) getTagBytTitle(ctx context.Context, title string) (*Tag, error) {

return &t, nil
}

// TagResolver contains the db and the Tag model for resolving
type TagResolver struct {
db *DB
m Tag
}

// ID resolves the ID for Tag
func (t *TagResolver) ID(ctx context.Context) *graphql.ID {
return gqlIDP(t.m.ID)
}

// Title resolves the title field
func (t *TagResolver) Title(ctx context.Context) *string {
return &t.m.Title
}

// Pets resolves the pets field
func (t *TagResolver) Pets(ctx context.Context) (*[]*PetResolver, error) {
pets, err := t.db.getTagPets(ctx, &t.m)
if err != nil {
return nil, errors.Wrap(err, "Pets")
}

r := make([]*PetResolver, len(pets))
for i := range pets {
r[i] = &PetResolver{
db: t.db,
m: pets[i],
}
}

return &r, nil
}
89 changes: 61 additions & 28 deletions pets.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

graphql "github.com/graph-gophers/graphql-go"
"github.com/jinzhu/gorm"
"github.com/pkg/errors"
)

// Pet is the base type for pets to be used by the db and gql
Expand All @@ -15,30 +16,8 @@ type Pet struct {
Tags []Tag `gorm:"many2many:pet_tags"`
}

// RESOLVERS ===========================================================================
// ID resolves the ID field for Pet
func (p *Pet) ID(ctx context.Context) *graphql.ID {
return gqlIDP(p.Model.ID)
}

// OWNER resolves the owner field for Pet
func (p *Pet) OWNER(ctx context.Context) (*User, error) {
return db.getPetOwner(ctx, int32(p.OwnerID))
}

// NAME resolves the name field for Pet
func (p *Pet) NAME(ctx context.Context) *string {
return &p.Name
}

// TAGS resolves the pet tags
func (p *Pet) TAGS(ctx context.Context) (*[]*Tag, error) {
return db.getPetTags(ctx, p)
}

// DB ===================================================================================
// GetPet should authorize the user in ctx and return a pet or error
func (db *DB) getPet(ctx context.Context, id int32) (*Pet, error) {
func (db *DB) getPet(ctx context.Context, id uint) (*Pet, error) {
var p Pet
err := db.DB.First(&p, id).Error
if err != nil {
Expand All @@ -57,14 +36,14 @@ func (db *DB) getPetOwner(ctx context.Context, id int32) (*User, error) {
return &u, nil
}

func (db *DB) getPetTags(ctx context.Context, p *Pet) (*[]*Tag, error) {
var t []*Tag
func (db *DB) getPetTags(ctx context.Context, p *Pet) ([]Tag, error) {
var t []Tag
err := db.DB.Model(p).Related(&t, "Tags").Error
if err != nil {
return nil, err
}

return &t, nil
return t, nil
}

func (db *DB) getPetsByID(ctx context.Context, ids []int, from, to int) ([]Pet, error) {
Expand All @@ -84,9 +63,14 @@ func (db *DB) updatePet(ctx context.Context, args *petInput) (*Pet, error) {
return nil, err
}

// so the pointer dereference is safe
if args.TagIDs == nil {
return nil, errors.Wrap(err, "UpdatePet")
}

// if there are tags to be updated, go through that process
var newTags []Tag
if len(args.TagIDs) > 0 {
if len(*args.TagIDs) > 0 {
err = db.DB.Where("id in (?)", args.TagIDs).Find(&newTags).Error
if err != nil {
return nil, err
Expand Down Expand Up @@ -117,7 +101,7 @@ func (db *DB) updatePet(ctx context.Context, args *petInput) (*Pet, error) {
return &p, nil
}

func (db *DB) deletePet(ctx context.Context, userID, petID int32) (*bool, error) {
func (db *DB) deletePet(ctx context.Context, userID, petID uint) (*bool, error) {
// make sure the record exist
var p Pet
err := db.DB.First(&p, petID).Error
Expand Down Expand Up @@ -161,3 +145,52 @@ func (db *DB) addPet(ctx context.Context, input petInput) (*Pet, error) {

return &pet, nil
}

// PetResolver contains the DB and the model for resolving
type PetResolver struct {
db *DB
m Pet
}

// ID resolves the ID field for Pet
func (p *PetResolver) ID(ctx context.Context) *graphql.ID {
return gqlIDP(p.m.ID)
}

// Owner resolves the owner field for Pet
func (p *PetResolver) Owner(ctx context.Context) (*UserResolver, error) {
user, err := p.db.getPetOwner(ctx, int32(p.m.OwnerID))
if err != nil {
return nil, errors.Wrap(err, "Owner")
}

r := UserResolver{
db: p.db,
m: *user,
}

return &r, nil
}

// Name resolves the name field for Pet
func (p *PetResolver) Name(ctx context.Context) *string {
return &p.m.Name
}

// Tags resolves the pet tags
func (p *PetResolver) Tags(ctx context.Context) (*[]*TagResolver, error) {
tags, err := p.db.getPetTags(ctx, &p.m)
if err != nil {
return nil, errors.Wrap(err, "Tags")
}

r := make([]*TagResolver, len(tags))
for i := range tags {
r[i] = &TagResolver{
db: p.db,
m: tags[i],
}
}

return &r, nil
}
103 changes: 0 additions & 103 deletions resolvers.go

This file was deleted.

Loading

0 comments on commit 9cb230d

Please sign in to comment.