{"id":17675753,"url":"https://github.com/dbz/kittencannon","last_synced_at":"2026-04-11T02:48:49.825Z","repository":{"id":146010151,"uuid":"109326639","full_name":"Dbz/KittenCannon","owner":"Dbz","description":"A Simple Ruby on Rails App With Graphql","archived":false,"fork":false,"pushed_at":"2017-11-05T21:12:10.000Z","size":56,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-13T16:02:41.591Z","etag":null,"topics":["graphql","graphql-ruby","json","json-api","ruby","ruby-on-rails"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Dbz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-11-02T22:46:27.000Z","updated_at":"2017-11-05T10:45:34.000Z","dependencies_parsed_at":null,"dependency_job_id":"15da4720-8433-4550-8125-2045029eeebf","html_url":"https://github.com/Dbz/KittenCannon","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Dbz/KittenCannon","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dbz%2FKittenCannon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dbz%2FKittenCannon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dbz%2FKittenCannon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dbz%2FKittenCannon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Dbz","download_url":"https://codeload.github.com/Dbz/KittenCannon/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dbz%2FKittenCannon/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266761448,"owners_count":23980298,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-23T02:00:09.312Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["graphql","graphql-ruby","json","json-api","ruby","ruby-on-rails"],"created_at":"2024-10-24T07:23:23.931Z","updated_at":"2026-04-11T02:48:44.801Z","avatar_url":"https://github.com/Dbz.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# KittenCannon™ Challenge\n\nKittenCannon™ is a proprietary, state-of-the-art system for selling and shipping\ncats to cat enthusiasts worldwide.\n\nYour challenge, should you choose to accept it:\n\n1. Basic cat breed tagging API\n\nBasic System\n\n```\nEntity (cat breed):\n  |\n  -- Tags (traits)\n```\n\n##### Concrete Example\n\n```\nAmerican Bobtail\n  |\n  -- affectionate\n  -- low shedding\n  -- playful\n  -- intelligent\n\nCymric\n  |\n  -- affectionate\n  -- has no tail\n  -- friendly\n\nNorwegian Forest Cat\n  |\n  -- low shedding\n  -- pet friendly\n  -- knows kung fu\n  -- climbs trees\n```\n\n## The dbz approach\n\nBecause this is an open ended exercise, I'd like to take the opportunity to learn something new: **graphql**\nThis 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.\n\n### Graphql implementation details\n\nI have implemented the following methods:\n\n```\nallBreeds\nallTags\n\ncreateBreed(name: String)\ncreateTag(name: String, breed_id: ID)\n\nupdateBreed(name: String, id: ID)\nupdateTag(name: String, id: ID)\n\ndeleteBreed(id: ID)\ndeleteTag(id: ID)\n```\n\nThe following fields are accessable from the Breed and Tag type:\n\n1. id\n1. name\n1. tags / breeds\n1. tag_count / breed_count\n\nHere is an example query to view all of the breeds with their name and tags. Each tag will also contain the name and id.\n\n```\nquery {\n  allBreeds {\n    name\n    id\n    tags {\n      name\n      id\n    }\n  }\n}\n```\n\nIt would return something that looks like this:\n```\n{\n  \"data\": {\n    \"allBreeds\": [\n      {\n        \"name\": \"turkish van\",\n        \"id\": \"4\",\n        \"tags\": [\n          {\n            \"name\": \"soft\",\n            \"id\": \"5\"\n          },\n          {\n            \"name\": \"odd eyed (different color eyes)\",\n            \"id\": \"6\"\n          },\n          {\n            \"name\": \"swims\",\n            \"id\": \"7\"\n          }\n        ]\n      },\n      {\n        \"name\": \"ragdoll\",\n        \"id\": \"5\",\n        \"tags\": [\n          {\n            \"name\": \"soft\",\n            \"id\": \"5\"\n          },\n          {\n            \"name\": \"lazy\",\n            \"id\": \"8\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\n - [Code Example: QueryType](/app/graphql/types/query_type.rb)\n - [Code Example: BreedType](/app/graphql/types/breed_type.rb)\n - [Code Example: CreateTag Resolver](/app/graphql/resolvers/create_tag.rb)\n\n#### Exception Handling\n\nI found a neat gem called [graphql-errors](https://github.com/exAspArk/graphql-errors) which allowed me to put all of my exception handling for errors into my schema.\nNow JSON gives nice pretty errors.\n\nExample mutation:\n\n```\nmutation {\n  deleteTag(id: 200) {\n    id\n  }\n}\n```\n\nWill return the following JSON if `id = 200` can not be found:\n\n```\n{\n  \"data\": {\n    \"deleteTag\": null\n  },\n  \"errors\": [\n    {\n      \"message\": \"Record not found: Couldn't find Tag with 'id'=200\",\n      \"locations\": [\n        {\n          \"line\": 46,\n          \"column\": 3\n        }\n      ],\n      \"path\": [\n        \"deleteTag\"\n      ]\n    }\n  ]\n}\n```\n\n - [Code Example: Exception handling on the graphql schema](/app/graphql/catapault_schema.rb)\n\n### Rails backend\n\nThere are two main models: `Breed` and `Tag`.\nI 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*).\nHABTM 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.\nI'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.\nWe still need the db level constraints in case there are concurrent updates to a breed's `tags`.\n\n - [Code Example: BreedTags join table migration](/db/migrate/20171104171821_create_breed_tags.rb)\n - [Code Example: Breed model](/app/models/breed.rb)\n\n#### Orphaned tags\n\nThe Humane Tag Association™ monitored the creation of the tag deletion api.\nNo tags are orphaned (even for a second) during the deletion of a breed.\n\n - [Code Example: See SQL skillz translated into ActiveRecord](/app/models/breed.rb#L17-L19)\n\n### Testing\n\nI am a big fan of RSpec, Shoulda-Matchers, Capybara, FactoryGirl, Fuubar, and Database-Cleaner for helping me test.\nI found a helpful gem called [rspec-graphql_matchers](https://github.com/khamusa/rspec-graphql_matchers) which provided a little bit of syntactic sugar when testing my graphql api.\n\nI followed bits of advice from [graphql-ruby.org](http://graphql-ruby.org/schema/testing.html), [how to graphql](https://github.com/howtographql/howtographql/blob/8614026b99e38dbf0a73aaecefd1f703efbedddf/content/backend/graphql-ruby/3-mutations.md#testing-with-unit-test), and [rspec-graphql_matcher](https://github.com/khamusa/rspec-graphql_matchers) on proper ways to test the graphql API.\nHowever, 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.\n\n - [Code Example: Breed model spec](/spec/models/breed_spec.rb)\n - [Code Example: BreedType spec](/spec/graphql/types/breed_type_spec.rb)\n - [Code Example: CreateBreed resolver spec](/spec/graphql/resolvers/create_breed_spec.rb)\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdbz%2Fkittencannon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdbz%2Fkittencannon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdbz%2Fkittencannon/lists"}