KittenCannon™ is a proprietary, state-of-the-art system for selling and shipping cats to cat enthusiasts worldwide.
Your challenge, should you choose to accept it:
- Basic cat breed tagging API
Basic System
Entity (cat breed):
|
-- Tags (traits)
American Bobtail
|
-- affectionate
-- low shedding
-- playful
-- intelligent
Cymric
|
-- affectionate
-- has no tail
-- friendly
Norwegian Forest Cat
|
-- low shedding
-- pet friendly
-- knows kung fu
-- climbs trees
Because this is an open ended exercise, I'd like to take the opportunity to learn something new: graphql This will definitely make the challenge take much longer than it should, but I will get first hand experience to determine how graphql works as a solution to JSON apis.
I have implemented the following methods:
allBreeds
allTags
createBreed(name: String)
createTag(name: String, breed_id: ID)
updateBreed(name: String, id: ID)
updateTag(name: String, id: ID)
deleteBreed(id: ID)
deleteTag(id: ID)
The following fields are accessable from the Breed and Tag type:
- id
- name
- tags / breeds
- tag_count / breed_count
Here is an example query to view all of the breeds with their name and tags. Each tag will also contain the name and id.
query {
allBreeds {
name
id
tags {
name
id
}
}
}
It would return something that looks like this:
{
"data": {
"allBreeds": [
{
"name": "turkish van",
"id": "4",
"tags": [
{
"name": "soft",
"id": "5"
},
{
"name": "odd eyed (different color eyes)",
"id": "6"
},
{
"name": "swims",
"id": "7"
}
]
},
{
"name": "ragdoll",
"id": "5",
"tags": [
{
"name": "soft",
"id": "5"
},
{
"name": "lazy",
"id": "8"
}
]
}
]
}
}
I found a neat gem called graphql-errors which allowed me to put all of my exception handling for errors into my schema. Now JSON gives nice pretty errors.
Example mutation:
mutation {
deleteTag(id: 200) {
id
}
}
Will return the following JSON if id = 200
can not be found:
{
"data": {
"deleteTag": null
},
"errors": [
{
"message": "Record not found: Couldn't find Tag with 'id'=200",
"locations": [
{
"line": 46,
"column": 3
}
],
"path": [
"deleteTag"
]
}
]
}
There are two main models: Breed
and Tag
.
I originally wanted to experiment with the has_and_belongs_to_many
association rather than the more conventional has_many :through
association because it's fun to try new things (cough graphql cough).
HABTM does not have a join model, and when adding tags through the breeds' tags
association, I can not perform model level validations and have to rely on db level constraints.
I'm expecting tons and tons of people to use my service, so I'd like to spare the db from doing all of my validations for me, so I switched to a has_many :through
association with a validation on the BreedTag
model.
We still need the db level constraints in case there are concurrent updates to a breed's tags
.
The Humane Tag Association™ monitored the creation of the tag deletion api. No tags are orphaned (even for a second) during the deletion of a breed.
I am a big fan of RSpec, Shoulda-Matchers, Capybara, FactoryGirl, Fuubar, and Database-Cleaner for helping me test. I found a helpful gem called rspec-graphql_matchers which provided a little bit of syntactic sugar when testing my graphql api.
I followed bits of advice from graphql-ruby.org, how to graphql, and rspec-graphql_matcher on proper ways to test the graphql API. However, there is not a lot of documentation and guides on this in the wild, and a good blog post would do the rails community a great service.