Upgrade to Pro — share decks privately, control downloads, hide ads and more …

GraphQL for Rubyists

GraphQL for Rubyists

The accompanying recording of this presentation is available here: http://rubythursday.com/talks/ruby-roundtable-graphql-relay.

359024e7132672aaeef4a3e792be4ae5?s=128

Brooks Swinnerton

June 04, 2016
Tweet

Transcript

  1. None
  2. Hi, I’m Brooks

  3. I work at !

  4. let’s talk about GraphQL & Relay

  5. in Ruby

  6. but first

  7. let’s start with APIs

  8. REST APIs

  9. are endpoint driven

  10. are verb driven

  11. !"""# verb !""""""""""""""""""""""""""""""""""""""""""""# endpoint https://api.reddit.com/r/catsstandingup.json GET

  12. { "kind": "Listing", "data": { "modhash": "", "children": [ {

    "kind": "t3", "data": { "id": "4lyzb1", "created": 1464776494, "title": "Cat.", "author": "AcezennJames", "score": 1, "num_comments": 0, "subreddit": "CatsStandingUp", "url": "https://i.reddituploads.com/da4620368fdd4c08a17e3b2f56571889? fit=max&h=1536&w=1536&s=a7a58b1ca15529fa6dda138d12d6fb51", "permalink": "/r/CatsStandingUp/comments/4lyzb1/cat/", "thumbnail": "http://b.thumbs.redditmedia.com/uT4-uPd2Z7Jt-gVdJSswq8W5bTa9Jchfnb3GHRlhHLE.jpg", "locked": false } } ] } }
  13. but, we’re Rubyists…

  14. require 'net/http' require 'json' uri = URI('https://api.reddit.com/r/catsstandingup.json') response = Net::HTTP.get(uri)

    parsed_response = JSON.parse(response) posts = parsed_response['data']['children'] html = posts.each_with_object('') do |post, string| thumbnail = post['data']['thumbnail'] string << "<img src='#{thumbnail}' />" end File.write('cats_standing_up.html', html)
  15. None
  16. REST APIs

  17. …for backend engineers

  18. are difficult to change

  19. …for implementors

  20. aren’t optimized for consumption

  21. { "kind": "Listing", "data": { "modhash": "", "children": [ {

    "kind": "t3", "data": { "id": "4lyzb1", "created": 1464776494, "title": "Cat.", "author": "AcezennJames", "score": 1, "num_comments": 0, "subreddit": "CatsStandingUp", "url": "https://i.reddituploads.com/da4620368fdd4c08a17e3b2f56571889? fit=max&amp;h=1536&amp;w=1536&amp;s=a7a58b1ca15529fa6dda138d12d6fb51", "permalink": "/r/CatsStandingUp/comments/4lyzb1/cat/", "thumbnail": "http://b.thumbs.redditmedia.com/uT4-uPd2Z7Jt-gVdJSswq8W5bTa9Jchfnb3GHRlhHLE.jpg", "locked": false } } ] } }
  22. enter, GraphQL

  23. What is it?

  24. a Data Query Language

  25. think SQL

  26. not Neo4j

  27. GraphQL is a specification

  28. created by Facebook

  29. powering all of their mobile apps

  30. what does it look like?

  31. { me { firstName lastName email } }

  32. { me { firstName lastName email } } { "data":{

    "me":{ "firstName":"Brooks", "lastName":"Swinnerton", "email":"brooks@github.com" } } }
  33. !"""# verb !""""""""""""""""""""""""""""""""""""""""# endpoint https://nyc-restaurant-grades.com/graphql POST

  34. { restaurant(name: "Cafe Ghia") { name buildingNumber street zipcode borough

    grade } } { "data": { "restaurant": { "name": "Cafe Ghia", "buildingNumber": "24", "street": "Irving Avenue", "zipcode": "11237", "borough": "BROOKLYN", "grade": "A" } } } https://nyc-restaurant-grades.com/graphql POST
  35. how do you know what you can query?

  36. GraphQL is introspectable

  37. GraphQL is typed

  38. None
  39. documentation and client generation, are free

  40. THIS IS THE HOLY GRAIL

  41. Building a GraphQL server

  42. like SQL, there are many implementations

  43. let’s talk about the Ruby implementation

  44. ActiveRecord::Schema.define(version: 20160605211006) do create_table "inspections"do |t| t.integer "restaurant_id" t.text "type"

    t.datetime "inspected_at" t.datetime "graded_at" t.integer "score" t.text "violation_description" t.text "violation_code" t.text "grade" t.datetime "created_at", null: false t.datetime "updated_at", null: false end create_table "restaurants"do |t| t.text "name" t.text "camis" t.text "building_number" t.text "street" t.text "zipcode" t.integer "borough" t.text "cuisine" t.datetime "created_at", null: false t.datetime "updated_at", null: false end end db/schema.rb
  45. class Inspection < ActiveRecord::Base belongs_to :restaurant end app/models/inspection.rb

  46. class Restaurant < ActiveRecord::Base enum borough: ['BRONX', 'BROOKLYN', 'MANHATTAN', 'STATEN_ISLAND',

    'QUEENS'] has_many :inspections def grade return unless last_inspection last_inspection.grade end private def last_inspection inspections.where.not(grade: nil).order(inspected_at: :desc).last end end app/models/restaurant.rb
  47. graphql-ruby

  48. app/graph/graph/types/restaurant.rb module Graph module Types Restaurant = GraphQL::ObjectType.define do name

    'Restaurant' description 'A place of business serving food in New York City' global_id_field :id field :name, !types.String, 'The doing-business-as value.' field :camis, !types.String, 'The unique identifier.' field :buildingNumber, types.String, 'The street number.', property: :building_number field :street, types.String, 'The street name.' field :zipcode, types.String, 'The zip code.' field :cuisine, types.String, 'The cuisine.' field :grade, types.String, 'The latest grade of an inspection.' end end end
  49. app/graph/graph/types/inspection.rb module Graph module Types Inspection = GraphQL::ObjectType.define do name

    'Inspection' description 'A NYC health inspection.' global_id_field :id field :type, types.String, 'The type of inspection.' field :violationDescription, types.String, 'The violation description.', property: :violation_description field :violationCode, types.String, 'The violation code cited.', property: :violation_code field :grade, types.String, 'The grade received.' field :score, types.Int, 'The numeric score received.' field :inspectedAt, !types.String, 'The inspection timestamp.', property: :inspected_at field :gradedAt, types.String, 'The issued grade timestamp.', property: :graded_at end end end
  50. app/graph/graph/types/root_query.rb module Graph module Types RootQuery = GraphQL::ObjectType.define do name

    "RootQuery" description "The query root." field :restaurant do type -> { Types::Restaurant } description "Perform a search for one restaurant." argument :name, types.String resolve -> (object, arguments, context) do ::Restaurant.find_by(name: arguments['name']) end end end end end
  51. { restaurant(name: "Cafe Ghia") { name buildingNumber street zipcode borough

    grade } } { "data": { "restaurant": { "name": "Cafe Ghia", "buildingNumber": "24", "street": "Irving Avenue", "zipcode": "11237", "borough": "BROOKLYN", "grade": "A" } } } https://nyc-restaurant-grades.com/graphql POST
  52. class Restaurant < ActiveRecord::Base enum borough: ['BRONX', 'BROOKLYN', 'MANHATTAN', 'STATEN_ISLAND',

    'QUEENS'] has_many :inspections def grade return unless last_inspection last_inspection.grade end private def last_inspection inspections.where.not(grade: nil).order(inspected_at: :desc).last end end app/models/restaurant.rb
  53. Enter, Relay

  54. but first

  55. an introduction to Graph Theory

  56. Graphs contain:

  57. nodes

  58. edges

  59. { "name": "Cafe Ghia", "buildingNumber": "24", "street": "Irving Avenue", "zipcode":

    "11237", "borough": "BROOKLYN" }
  60. { "name": "Cafe Ghia", "buildingNumber": "24", "street": "Irving Avenue", "zipcode":

    "11237", "borough": "BROOKLYN" } { "grade": "A", "score": 13, "type": "Cycle Inspection", "violationCode": "10B", "violationDescription": "Plumbing not properly installed.", "inspectedAt": "2016-02-18 00:00:00 UTC", "gradedAt": "2016-02-18 00:00:00 UTC" }
  61. { "name": "Cafe Ghia", "buildingNumber": "24", "street": "Irving Avenue", "zipcode":

    "11237", "borough": "BROOKLYN" } { "grade": "A", "score": 13, "type": "Cycle Inspection", "violationCode": "02G", "violationDescription": "Cold food item held above 41ºF", "inspectedAt": "2016-02-18 00:00:00 UTC", "gradedAt": "2016-02-18 00:00:00 UTC" } { "grade": "A", "score": 13, "type": "Cycle Inspection", "violationCode": "10B", "violationDescription": "Plumbing not properly installed.", "inspectedAt": "2016-02-18 00:00:00 UTC", "gradedAt": "2016-02-18 00:00:00 UTC" }
  62. { restaurant(name:"Cafe Ghia") { inspections(first:2) { edges { node {

    violationCode violationDescription } } } } } https://nyc-restaurant-grades.com/graphql POST
  63. { restaurant(name:"Cafe Ghia") { inspections(first:2) { edges { node {

    violationCode violationDescription } } } } } { "data": { "restaurant": { "inspections": { "edges": [ { "node": { "violationCode": "10F", "violationDescription": "Non-food contact surface improperly constructed. Unacceptable material used." } }, { "node": { "violationCode": "04L", "violationDescription": "Evidence of mice or live mice present in facility's food and/or non-food areas." } } ] } } } } https://nyc-restaurant-grades.com/graphql POST
  64. graphql-relay-ruby

  65. app/graph/graph/types/restaurant.rb module Graph module Types Restaurant = GraphQL::ObjectType.define do name

    'Restaurant' description 'A place of business serving food in New York City' connection :inspections, -> { Types::Inspection.connection_type } do description 'List the inspections.' resolve -> (object, arguments, context) do object.inspections end end end end end
  66. Mutations

  67. Versions

  68. More information

  69. https://facebook.github.io/graphql/

  70. http://nyc-restaurant-grades.com

  71. https://github.com/bswinnerton/nyc-restaurant-grades

  72. Thanks Follow me on Twitter & GitHub: @bswinnerton