Slide 1

Slide 1 text

GraphQL Migration: A Use Case for Metaprogramming? RubyKaigi 2019 [email protected] @gao_shawnee

Slide 2

Slide 2 text

About Me

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

wagashi wagashi wagashi Lake Ashi ft. Mt. Fuji (a.k.a. shy Fuji-san as he’s often covered by fog).

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

Japan Loves Cash

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

Inventory Price per Unit Restock Date Units Sold Cuban Links (Sets) $14,000 3/10/2014 23 Diamond Custom Cuts $200,000 3/10/2014 43 Classic Open Face $230,000 3/10/2014 12 Solid Classics $9,000 3/10/2014 11 Promo Duration Promo Details # Promos used Breezey Promo 3/21/2014 - Forever xx off Loyalty 22 Full Set Discount 3/21/2014 - 4/12/2014 xx% off full set 12 Deposits Pending: $XX Devices: XX # Employees: XX Gift Cards Has Loans Subscriptions # Payments Received Marketing Plans Appointments Chargebacks And So Much More … # Refunds Grillz by Nelly en-US St. Louis, MO Freeze Deactivate Audit Actions: Resolve Unfreeze Payments Graph

Slide 15

Slide 15 text

Locations Promo Appointments Seller Service Inventory Chargebacks Capital Employees Deposits Marketing Hardware Dashboard’s Write Paths Payments Subscriptions

Slide 16

Slide 16 text

273,249 lines of Ruby

Slide 17

Slide 17 text

22,337 lines of YAML

Slide 18

Slide 18 text

Over 200 Controllers

Slide 19

Slide 19 text

Talking to over 150 Services

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

request.open( 'GET', https://squareup.resources.com/payment/4567 ) Response: ???????

Slide 22

Slide 22 text

{ “data” : [ payment: { “id”: “4567”, “amount”: “300”, “merchant”: “1234”, “card: “7890” } ] }

Slide 23

Slide 23 text

Underfetched! Date Amount Status Merchant Method 3/10/19 $900 Success Grillz by Nelly Debit { “data” : [ payment: { “id”: “4567”, “amount”: “300”, “merchant”: “1234”, “card: “7890” } ] }

Slide 24

Slide 24 text

graphql demo

Slide 25

Slide 25 text

Different Resources Relational Highly Diverse

Slide 26

Slide 26 text

Road Map • Existing Model Architecture • Write a GraphQL Ruby API • Add metaprogramming to our API • Add/Change Business Object • Testing

Slide 27

Slide 27 text

REST ~150 Specialized Services

Slide 28

Slide 28 text

~150 Specialized Services REST Backend REST Frontend Agents

Slide 29

Slide 29 text

~150 Specialized Services REST Backend GQL Frontend Agents

Slide 30

Slide 30 text

~150 Specialized Services REST Backend GQL Frontend Agents Target

Slide 31

Slide 31 text

~150 Specialized Services REST Backend GQL Frontend Agents Target

Slide 32

Slide 32 text

Target ~150 Specialized Services REST Backend GQL Frontend Agents

Slide 33

Slide 33 text

Targets

Slide 34

Slide 34 text

1 class TargetBase 2 include TargetMethods 3 extend ModuleHelper 4 5 def self._fetch_target_data(_token) 6 raise NotImplementedError 7 end 8 9 def self.lookup(token) 10 target_data = _fetch_target_data(token) 11 new(target_data: target_data) 12 end 13 14 def id 15 target_data.id 16 end 17 end

Slide 35

Slide 35 text

1 class TargetBase 2 include TargetMethods 3 extend ModuleHelper 4 5 def self._fetch_target_data(_id) 6 raise NotImplementedError 7 end 8 9 def self.lookup(id) 10 target_data = _fetch_target_data(id) 11 new(target_data: target_data) 12 end 13 14 def id 15 target_data.id 16 end 17 end

Slide 36

Slide 36 text

1 class TargetBase 2 include TargetMethods 3 extend ModuleHelper 4 5 def self._fetch_target_data(_id) 6 raise NotImplementedError 7 end 8 9 def self.lookup(id) 10 target_data = _fetch_target_data(id) 11 new(target_data: target_data) 12 end 13 14 def id 15 target_data.id 16 end 17 end

Slide 37

Slide 37 text

1 class TargetBase 2 include TargetMethods 3 extend ModuleHelper 4 5 def self._fetch_target_data(_id) 6 raise NotImplementedError 7 end 8 9 def self.lookup(id) 10 target_data = _fetch_target_data(id) 11 new(target_data: target_data) 12 end 13 14 def id 15 target_data.id 16 end 17 end

Slide 38

Slide 38 text

Payment - id - amount - merchant_id Merchant - id - store_name Is associated through merchant_id Today’s Data Model

Slide 39

Slide 39 text

app/models/merchant.rb 4 class Merchant < TargetBase 5 stub_target_data( 6 resource: 'merchant', 7 id: 4567, 8 response_body: { 9 id: 4567, 10 store_name: 'Grillz by Nelly' 11 } 12 ) 13 14 def self._fetch_target_data(merchant_id) 15 target_data = 16 MerchantClient.get_by_id(merchant_id) 18 19 OpenStruct.new(target_data) 20 end 21 22 def store_name 23 target_data.store_name 24 end 25 end

Slide 40

Slide 40 text

4 class Merchant < TargetBase 5 stub_target_data( 6 resource: 'merchant', 7 id: 4567, 8 response_body: { 9 id: 4567, 10 store_name: 'Grillz by Nelly' 11 } 12 ) 13 14 def self._fetch_target_data(merchant_id) 15 target_data = 16 MerchantClient.get_by_id(merchant_id) 18 19 OpenStruct.new(target_data) 20 end 21 22 def store_name 23 target_data.store_name 24 end 25 end

Slide 41

Slide 41 text

4 class Merchant < TargetBase 5 stub_target_data( 6 resource: 'merchant', 7 id: 4567, 8 response_body: { 9 id: 4567, 10 store_name: 'Grillz by Nelly' 11 } 12 ) 13 14 def self._fetch_target_data(merchant_id) 15 target_data = 16 MerchantClient.get_by_id(merchant_id) 18 19 OpenStruct.new(target_data) 20 end 21 22 def store_name 23 target_data.store_name 24 end 25 end

Slide 42

Slide 42 text

4 class Merchant < TargetBase 5 stub_target_data( 6 resource: 'merchant', 7 id: 4567, 8 response_body: { 9 id: 4567, 10 store_name: 'Grillz by Nelly' 11 } 12 ) 13 14 def self._fetch_target_data(merchant_id) 15 target_data = 16 MerchantClient.get_by_id(merchant_id) 17 .parsed_response 18 19 OpenStruct.new(target_data) 20 end 21 22 def store_name 23 target_data.store_name 24 end 25 end

Slide 43

Slide 43 text

4 class Payment < TargetBase 5 stub_target_data( 6 resource: 'payment', 7 id: 1234, 8 response_body: { 9 id: 1234, 10 amount: 100, 11 merchant_id: 4567 12 } 13 ) 14 15 def self._fetch_target_data(payment_id) 17 target_data = 18 PaymentClient.get_by_id(payment_id) 20 21 OpenStruct.new(target_data) 22 end 23 24 def amount 25 target_data.amount 26 end 29 30 def merchant_id 31 target_data.merchant_id 32 end 33 34 def merchant 35 @merchant ||= Merchant.lookup(merchant_id) 37 end 38 end

Slide 44

Slide 44 text

4 class Payment < TargetBase 5 stub_target_data( 6 resource: 'payment', 7 id: 1234, 8 response_body: { 9 id: 1234, 10 amount: 100, 11 merchant_id: 4567 12 } 13 ) 14 15 def self._fetch_target_data(payment_id) 17 target_data = 18 PaymentClient.get_by_id(payment_id) 20 21 OpenStruct.new(target_data) 22 end 23 24 def amount 25 target_data.amount 26 end 29 30 def merchant_id 31 target_data.merchant_id 32 end 33 34 def merchant 35 @merchant ||= Merchant.lookup(merchant_id) 37 end 38 end

Slide 45

Slide 45 text

4 class Payment < TargetBase 5 stub_target_data( 6 resource: 'payment', 7 id: 1234, 8 response_body: { 9 id: 1234, 10 amount: 100, 11 merchant_id: 4567 12 } 13 ) 14 15 def self._fetch_target_data(payment_id) 17 target_data = 18 PaymentClient.get_by_id(payment_id) 20 21 OpenStruct.new(target_data) 22 end 23 24 def amount 25 target_data.amount 26 end 29 30 def merchant_id 31 target_data.merchant_id 32 end 33 34 def merchant 35 @merchant ||= Merchant.lookup(merchant_id) 37 end 38 end

Slide 46

Slide 46 text

4 class Payment < TargetBase 5 stub_target_data( 6 resource: 'payment', 7 id: 1234, 8 response_body: { 9 id: 1234, 10 amount: 100, 11 merchant_id: 4567 12 } 13 ) 14 15 def self._fetch_target_data(payment_id) 17 target_data = 18 PaymentClient.get_by_id(payment_id) 20 21 OpenStruct.new(target_data) 22 end 23 24 def amount 25 target_data.amount 26 end 29 30 def merchant_id 31 target_data.merchant_id 32 end 33 34 def merchant 35 @merchant ||= Merchant.lookup(merchant_id) 37 end 38 end

Slide 47

Slide 47 text

4 class Payment < TargetBase 5 stub_target_data( 6 resource: 'payment', 7 id: 1234, 8 response_body: { 9 id: 1234, 10 amount: 100, 11 merchant_id: 4567 12 } 13 ) 14 15 def self._fetch_target_data(payment_id) 17 target_data = 18 PaymentClient.get_by_id(payment_id) 20 21 OpenStruct.new(target_data) 22 end 23 24 def amount 25 target_data.amount 26 end 29 30 def merchant_id 31 target_data.merchant_id 32 end 33 34 def merchant 35 @merchant ||= Merchant.lookup(merchant_id) 37 end 38 end

Slide 48

Slide 48 text

=> Payment Payment p = Payment.lookup(1234) => # p.amount => 100 p.merchant => # p.merchant.store_name => “Grillz by Nelly” Merchant => Merchant m = Merchant.lookup(4567) m.store_name => “Grillz by Nelly” => #

Slide 49

Slide 49 text

Let’s Create a GraphQL API: GQL Ruby Query Flow

Slide 50

Slide 50 text

Payment - id - amount - merchant_id Merchant - id - store_name Is associated through merchant_id Today’s Data Model

Slide 51

Slide 51 text

Graphql Controller DemoSchema Mutation Type Query Type Payment Type Merchant Type id amount merchant id store_name Some Mutation Merchant Type id store_name

Slide 52

Slide 52 text

Graphql Controller DemoSchema Mutation Type Query Type Payment Type Merchant Type id amount merchant id store_name Some Mutation Merchant Type id store_name Bank Accounts Controller Hardware Controller

Slide 53

Slide 53 text

Graphql Controller DemoSchema Mutation Type Query Type Payment Type Merchant Type id amount merchant id store_name Some Mutation Merchant Type id store_name

Slide 54

Slide 54 text

query($paymentId: ID!){ payment(id: $paymentId) { id amount merchant { id storeName } } } variables{ “paymentId”: 1234 }

Slide 55

Slide 55 text

1 class GraphqlController < ApplicationController 2 def execute 3 ..... 4 result = 5 DemoSchema.execute( 6 query, 7 variables: variables, 9 ... 10 ) 11 render json: result 12 rescue => e 13 raise e 14 end query($paymentId: ID!){ payment(id: $paymentId) { id amount merchant { id storeName } } } variables{ “paymentId”: 1234 } app/controllers/graphql_controller.rb

Slide 56

Slide 56 text

1 class GraphqlController < ApplicationController 2 def execute 3 ..... 4 result = 5 DemoSchema.execute( 6 query, 7 variables: variables, 9 ... 10 ) 11 render json: result 12 rescue => e 13 raise e 14 end query($paymentId: ID!){ payment(id: $paymentId) { id amount merchant { id storeName } } } variables{ “paymentId”: 1234 } app/controllers/graphql_controller.rb

Slide 57

Slide 57 text

1 class GraphqlController < ApplicationController 2 def execute 3 ..... 4 result = 5 DemoSchema.execute( 6 query, 7 variables: variables, 9 ... 10 ) 11 render json: result 12 rescue => e 13 raise e 14 end query($paymentId: ID!){ payment(id: $paymentId) { id amount merchant { id storeName } } } variables{ “paymentId”: 1234 } app/controllers/graphql_controller.rb

Slide 58

Slide 58 text

1 class GraphqlController < ApplicationController 2 def execute 3 ..... 4 result = 5 DemoSchema.execute( 6 query, 7 variables: variables, 9 ... 10 ) 11 render json: result 12 rescue => e 13 raise e 14 end query($paymentId: ID!){ payment(id: $paymentId) { id amount merchant { id storeName } } } variables{ “paymentId”: 1234 } app/controllers/graphql_controller.rb

Slide 59

Slide 59 text

query($paymentId: ID!){ payment(id: $paymentId){ id amount merchant { id storeName } } } variables{ “paymentId”: 1234 } 1 class DemoSchema < GraphQL::Schema 2 query(Types::QueryType) 3 query(Types::MutationType) 4 end app/graphql/demo_schema.rb

Slide 60

Slide 60 text

query($paymentId: ID!){ payment(id: $paymentId){ id amount merchant { id storeName } } } variables{ “paymentId”: 1234 } 1 class DemoSchema < GraphQL::Schema 2 query(Types::QueryType) 3 query(Types::MutationType) 4 end app/graphql/demo_schema.rb

Slide 61

Slide 61 text

query($paymentId: ID!){ payment(id: $paymentId){ id amount merchant { id storeName } } } variables{ “paymentId”: 1234 } 1 module Types 2 class QueryType < Types::BaseObject 3 field :payment, Types::PaymentType, null: false do 4 argument :id, ID, required: true 5 end 6 7 field :merchant, Types::MerchantType, null: false do 8 argument :id, ID, required: true 9 end 10 11 def payment(params) 12 Payment.lookup(params[:id]) 13 end 14 15 def merchant(params) 16 Merchant.lookup(params[:id]) 17 end 18 end 19 end 20 app/graphql/type/query_type.rb

Slide 62

Slide 62 text

query($paymentId: ID!){ payment(id: $paymentId){ id amount merchant { id storeName } } } variables{ “paymentId”: 1234 } 1 module Types 2 class QueryType < Types::BaseObject 3 field :payment, Types::PaymentType, null: false do 4 argument :id, ID, required: true 5 end 6 7 field :merchant, Types::MerchantType, null: false do 8 argument :id, ID, required: true 9 end 10 11 def payment(params) 12 Payment.lookup(params[:id]) 13 end 14 15 def merchant(params) 16 Merchant.lookup(params[:id]) 17 end 18 end 19 end 20 app/graphql/type/query_type.rb

Slide 63

Slide 63 text

payment(id: $paymentId) 3 field :payment, Types::PaymentType, null: false do 4 argument :id, ID, required: true 5 end “paymentId”: 1234

Slide 64

Slide 64 text

payment(id: $paymentId) 3 field :payment, Types::PaymentType, null: false do 4 argument :id, ID, required: true 5 end “paymentId”: 1234

Slide 65

Slide 65 text

query($paymentId: ID!){ payment(id: $paymentId){ id amount merchant { id storeName } } } variables{ “paymentId”: 1234 } 1 module Types 2 class QueryType < Types::BaseObject 3 field :payment, Types::PaymentType, null: false do 4 argument :id, ID, required: true 5 end 6 7 field :merchant, Types::MerchantType, null: false do 8 argument :id, ID, required: true 9 end 10 11 def payment(params) 12 Payment.lookup(params[:id]) 13 end 14 15 def merchant(params) 16 Merchant.lookup(params[:id]) 17 end 18 end 19 end 20 app/graphql/type/query_type.rb

Slide 66

Slide 66 text

1 class Types::PaymentType < Types::BaseObject 2 field :id, ID, null: false 3 field :amount, Int, null: false 4 field :merchant, Types::MerchantType, null: false 5 end app/graphql/type/payment_type.rb query($paymentId: ID!){ payment(id: $paymentId){ id amount merchant { id storeName } } } variables{ “paymentId”: 1234 }

Slide 67

Slide 67 text

query($paymentId: ID!){ payment(id: $paymentId){ id amount merchant { id storeName } } } variables{ “paymentId”: 1234 } 1 class Types::PaymentType < Types::BaseObject 2 field :id, ID, null: false 3 field :amount, Int, null: false 4 field :merchant, Types::MerchantType, null: false 5 end app/graphql/type/payment_type.rb

Slide 68

Slide 68 text

payment(id: $paymentId){ id amount merchant { id storeName } } 1 class Types::PaymentType < Types::BaseObject 2 field :id, ID, null: false 3 field :amount, Int, null: false 4 field :merchant, Types::MerchantType, null: false 5 end

Slide 69

Slide 69 text

payment(id: $paymentId){ id amount merchant { id storeName } } 1 class Types::PaymentType < Types::BaseObject 2 field :id, ID, null: false 3 field :amount, Int, null: false 4 field :merchant, Types::MerchantType, null: false 5 end

Slide 70

Slide 70 text

query($paymentId: ID!){ payment(id: $paymentId){ id amount merchant { id storeName } } } variables{ “paymentId”: 1234 } app/graphql/type/merchant_type.rb 1 class Types::MerchantType < Types::BaseObject 2 field :id, ID, null: false 3 field :storeName, String, null: false 4 end

Slide 71

Slide 71 text

query($paymentId: ID!){ payment(id: $paymentId){ id amount merchant { id storeName } } } variables{ “paymentId”: 1234 } app/graphql/type/merchant_type.rb 1 class Types::MerchantType < Types::BaseObject 2 field :id, ID, null: false 3 field :storeName, String, null: false 4 end

Slide 72

Slide 72 text

merchant { id storeName } app/graphql/type/merchant_type.rb 1 class Types::MerchantType < Types::BaseObject 2 field :id, ID, null: false 3 field :storeName, String, null: false 4 end

Slide 73

Slide 73 text

merchant { id storeName } app/graphql/type/merchant_type.rb 1 class Types::MerchantType < Types::BaseObject 2 field :id, ID, null: false 3 field :storeName, String, null: false 4 end

Slide 74

Slide 74 text

query($paymentId: ID!){ payment(id: $paymentId){ id amount merchant { id storeName } } } variables{ “paymentId”: 1234 } 1 module Types 2 class QueryType < Types::BaseObject 3 field :payment, Types::PaymentType, null: false do 4 argument :id, ID, required: true 5 end 6 7 field :merchant, Types::MerchantType, null: false do 8 argument :id, ID, required: true 9 end 10 11 def payment(params) 12 Payment.lookup(params[:id]) 13 end 14 15 def merchant(params) 16 Merchant.lookup(params[:id]) 17 end 18 end 19 end 20 app/graphql/type/query_type.rb

Slide 75

Slide 75 text

query($paymentId: ID!){ payment(id: $paymentId){ id amount merchant { id storeName } } } variables{ “paymentId”: 1234 } 1 module Types 2 class QueryType < Types::BaseObject 3 field :payment, Types::PaymentType, null: false d 4 argument :id, ID, required: true 5 end 6 7 field :merchant, Types::MerchantType, null: false do 8 argument :id, ID, required: true 9 end 10 11 def payment(params) 12 Payment.lookup(params[:id]) 13 end 14 15 def merchant(params) 16 Merchant.lookup(params[:id]) 17 end 18 end 19 end 20 app/graphql/type/query_type.rb

Slide 76

Slide 76 text

query($paymentId: ID!){ payment(id: $paymentId){ id amount merchant { id storeName } } } variables{ “paymentId”: 1234 } 1 module Types 2 class QueryType < Types::BaseObject 3 field :payment, Types::PaymentType, null: false d 4 argument :id, ID, required: true 5 end 6 7 field :merchant, Types::MerchantType, null: false do 8 argument :id, ID, required: true 9 end 10 11 def payment(params) 12 Payment.lookup(params[:id]) 13 end 14 15 def merchant(params) 16 Merchant.lookup(params[:id]) 17 end 18 end 19 end 20 app/graphql/type/query_type.rb

Slide 77

Slide 77 text

Payment.lookup(1234)

Slide 78

Slide 78 text

Payment.lookup(1234) { "data": { "payment": { "id": "1234", "amount": 100, "merchant": { "id": “4567", "storeName": “Grillz by Nelly” } } } }

Slide 79

Slide 79 text

Is metaprogramming a good idea?

Slide 80

Slide 80 text

1. Flexible 2. Reduce Boilerplate 3. Improved Developer Experience Is metaprogramming a good idea?

Slide 81

Slide 81 text

GraphQL Controller DemoSchema Types::QueryType Root Types Root Type Resolvers field :payment … field :merchant … def payment … def merchant … validates Types::PaymentType Types::MerchantType GQL Type Classes Payment Merchant Target Classes

Slide 82

Slide 82 text

GraphQL Controller DemoSchema Types::QueryType Root Types Root Type Resolvers field :payment … field :merchant … def payment … def merchant … validates Types::PaymentType Types::MerchantType GQL Type Classes Payment Merchant Target Classes

Slide 83

Slide 83 text

Types::QueryType Root Types Root Type Resolvers field :payment … field :merchant … def payment … def merchant … Types::PaymentType Types::MerchantType GQL Type Classes

Slide 84

Slide 84 text

Types::QueryType Root Types Root Type Resolvers field :payment … field :merchant … def payment … def merchant … Types::PaymentType Types::MerchantType GQL Type Classes

Slide 85

Slide 85 text

Types::PaymentType Types::MerchantType GQL Type Classes

Slide 86

Slide 86 text

Priming: Target Class Annotations

Slide 87

Slide 87 text

Payment - id - amount - merchant_id Merchant - id - store_name Is associated through merchant_id Today’s Data Model

Slide 88

Slide 88 text

Payment - id - amount - merchant_id Merchant - id - store_name Is associated through merchant_id Today’s Data Model gql_field_annotations gql_field_annotations

Slide 89

Slide 89 text

Payment.gql_field_annotations => { amount: {type: "Integer", null: false}, id: {type: "Types::BaseObject::ID", null: false}, merchant: {type: "Types::MerchantType", null: false} }

Slide 90

Slide 90 text

Payment.gql_field_annotations => { amount: {type: "Integer", null: false}, id: {type: "Types::BaseObject::ID", null: false}, merchant: {type: "Types::MerchantType", null: false} }

Slide 91

Slide 91 text

4 class Payment < TargetBase 33 def amount 34 target_data.amount 35 end 36 38 def token 39 target_data.token 40 end 41 42 def merchant_token 43 target_data.merchant_token 44 end 45 47 def merchant 48 @merchant ||= Merchant.lookup(merchant_token) 49 end 50 end

Slide 92

Slide 92 text

4 class Payment < TargetBase 5 mattr_accessor :gql_field_annotations do 6 {} 7 end 9 . . . 32 annotate_field "Integer", null: false, definition: 33 def amount 34 target_data.amount 35 end 36 37 annotate_field "Types::BaseObject::ID", null: false, definition: 38 def token 39 target_data.token 40 end 41 42 def merchant_id 43 target_data.merchant_id 44 end 45 46 annotate_field "Types::MerchantType", null: false, definition: 47 def merchant 48 @merchant ||= Merchant.lookup(merchant_id) 49 end 50 end

Slide 93

Slide 93 text

Concepts : Dynamic Methods Unbound Methods

Slide 94

Slide 94 text

Console Demo

Slide 95

Slide 95 text

4 class Payment < TargetBase 5 mattr_accessor :gql_field_annotations do 6 {} 7 end 9 . . . 32 annotate_field "Integer", null: false, definition: 33 def amount 34 target_data.amount 35 end 36 37 annotate_field "Types::BaseObject::ID", null: false, definition: 38 def token 39 target_data.token 40 end 41 42 def merchant_id 43 target_data.merchant_id 44 end 45 46 annotate_field "Types::MerchantType", null: false, definition: 47 def merchant 48 @merchant ||= Merchant.lookup(merchant_id) 49 end 50 end

Slide 96

Slide 96 text

4 class Payment < TargetBase 5 mattr_accessor :gql_field_annotations do 6 {} 7 end 32 annotate_field "Integer", null: false, definition: 33 def amount 34 target_data.amount 35 end

Slide 97

Slide 97 text

1 module TargetAnnotation 2 def annotate_field(type, null: false, definition:) 5 self.gql_field_annotations[definition] = { 6 type: type, 7 null: null 8 } 9 10 orig_method = instance_method(definition) 11 define_method(definition) do 12 orig_method.bind(self).call 13 end 14 end 15 end 4 class Payment < TargetBase 5 mattr_accessor :gql_field_annotations do 6 {} 7 end 32 annotate_field "Integer", null: false, definition: 33 def amount 34 target_data.amount 35 end

Slide 98

Slide 98 text

1 module TargetAnnotation 2 def annotate_field(type, null: false, definition:) 5 self.gql_field_annotations[definition] = { 6 type: type, 7 null: null 8 } 9 10 orig_method = instance_method(definition) 11 define_method(definition) do 12 orig_method.bind(self).call 13 end 14 end 15 end 4 class Payment < TargetBase 5 mattr_accessor :gql_field_annotations do 6 {} 7 end 32 annotate_field "Integer", null: false, definition: 33 def amount 34 target_data.amount 35 end

Slide 99

Slide 99 text

1 module TargetAnnotation 2 def annotate_field(type, null: false, definition:) 5 self.gql_field_annotations[definition] = { 6 type: type, 7 null: null 8 } 9 10 orig_method = instance_method(definition) 11 define_method(definition) do 12 orig_method.bind(self).call 13 end 14 end 15 end 4 class Payment < TargetBase 5 mattr_accessor :gql_field_annotations do 6 {} 7 end 32 annotate_field "Integer", null: false, definition: 33 def amount 34 target_data.amount 35 end

Slide 100

Slide 100 text

1 module TargetAnnotation 2 def annotate_field(type, null: false, definition:) 5 self.gql_field_annotations[definition] = { 6 type: type, 7 null: null 8 } 9 10 orig_method = instance_method(definition) 11 define_method(definition) do 12 orig_method.bind(self).call 13 end 14 end 15 end 4 class Payment < TargetBase 5 mattr_accessor :gql_field_annotations do 6 {} 7 end 32 annotate_field "Integer", null: false, definition: 33 def amount 34 target_data.amount 35 end

Slide 101

Slide 101 text

1 module TargetAnnotation 2 def annotate_field(type, null: false, definition:) 5 self.gql_field_annotations[definition] = { 6 type: type, 7 null: null 8 } 9 10 orig_method = instance_method(definition) 11 define_method(definition) do 12 orig_method.bind(self).call 13 end 14 end 15 end 4 class Payment < TargetBase 5 mattr_accessor :gql_field_annotations do 6 {} 7 end 32 annotate_field "Integer", null: false, definition: 33 def amount 34 target_data.amount 35 end :amount

Slide 102

Slide 102 text

1 module TargetAnnotation 2 def annotate_field(type, null: false, definition:) 5 self.gql_field_annotations[definition] = { 6 type: type, 7 null: null 8 } 9 10 orig_method = instance_method(definition) 11 define_method(definition) do 12 orig_method.bind(self).call 13 end 14 end 15 end 4 class Payment < TargetBase 5 mattr_accessor :gql_field_annotations do 6 {} 7 end 32 annotate_field "Integer", null: false, definition: 33 def amount 34 target_data.amount 35 end

Slide 103

Slide 103 text

1 module TargetAnnotation 2 def annotate_field(type, null: false, definition:) 5 self.gql_field_annotations[definition] = { 6 type: type, 7 null: null 8 } 9 10 orig_method = instance_method(definition) 11 define_method(definition) do 12 orig_method.bind(self).call 13 end 14 end 15 end 4 class Payment < TargetBase 5 mattr_accessor :gql_field_annotations do 6 {} 7 end 32 annotate_field "Integer", null: false, definition: 33 def amount 34 target_data.amount 35 end

Slide 104

Slide 104 text

1 module TargetAnnotation 2 def annotate_field(type, null: false, definition:) 5 self.gql_field_annotations[definition] = { 6 type: type, 7 null: null 8 } 9 10 orig_method = instance_method(definition) 11 define_method(definition) do 12 orig_method.bind(self).call 13 end 14 end 15 end 4 class Payment < TargetBase 5 mattr_accessor :gql_field_annotations do 6 {} 7 end 32 annotate_field "Integer", null: false, definition: 33 def amount 34 target_data.amount 35 end :amount

Slide 105

Slide 105 text

1 module TargetAnnotation 2 def annotate_field(type, null: false, definition:) 5 self.gql_field_annotations[definition] = { 6 type: type, 7 null: null 8 } 9 10 orig_method = instance_method(definition) 11 define_method(definition) do 12 orig_method.bind(self).call 13 end 14 end 15 end 4 class Payment < TargetBase 5 mattr_accessor :gql_field_annotations do 6 {} 7 end 32 annotate_field "Integer", null: false, definition: 33 def amount 34 target_data.amount 35 end

Slide 106

Slide 106 text

1 module TargetAnnotation 2 def annotate_field(type, null: false, definition:) 5 self.gql_field_annotations[definition] = { 6 type: type, 7 null: null 8 } 9 10 orig_method = instance_method(definition) 11 define_method(definition) do 12 orig_method.bind(self).call 13 end 14 end 15 end 4 class Payment < TargetBase 5 mattr_accessor :gql_field_annotations do 6 {} 7 end 32 annotate_field "Integer", null: false, definition: 33 def amount 34 target_data.amount 35 end

Slide 107

Slide 107 text

1 module TargetAnnotation 2 def annotate_field(type, null: false, definition:) 5 self.gql_field_annotations[definition] = { 6 type: type, 7 null: null 8 } 9 10 orig_method = instance_method(definition) 11 define_method(definition) do 12 orig_method.bind(self).call 13 end 14 end 15 end 4 class Payment < TargetBase 5 mattr_accessor :gql_field_annotations do 6 {} 7 end 32 annotate_field "Integer", null: false, definition: 33 def amount 34 target_data.amount 35 end

Slide 108

Slide 108 text

1 module TargetAnnotation 2 def annotate_field(type, null: false, definition:) 5 self.gql_field_annotations[definition] = { 6 type: type, 7 null: null 8 } 9 10 orig_method = instance_method(definition) 11 define_method(definition) do 12 orig_method.bind(self).call 13 end 14 end 15 end 4 class Payment < TargetBase 5 mattr_accessor :gql_field_annotations do 6 {} 7 end 32 annotate_field "Integer", null: false, definition: 33 def amount 34 target_data.amount 35 end

Slide 109

Slide 109 text

Let’s Generate the GQL Type Classes

Slide 110

Slide 110 text

gql_type_generator = GqlTypeGenerator.new(all_target_classes) => { Payment: Types::PaymentType, Merchant: Types::MerchantType } => # gql_type_generator.target_to_gql_class_mapping gql_type_generator.target_to_gql_class_mapping[Payment] => Types::PaymentType gql_type_generator.target_to_gql_class_mapping[Merchant] => Types::MerchantType

Slide 111

Slide 111 text

Concepts : Ruby Object Model Constants

Slide 112

Slide 112 text

console demo

Slide 113

Slide 113 text

1 class GqlTypeGenerator 2 attr_accessor :target_to_gql_class_mapping 3 4 def initialize(target_classes) 5 @target_to_gql_class_mapping = {} 6 7 target_classes.each do |target_class| 8 gql_type_class_name = "#{target_class.to_s}Type" 9 10 @target_to_gql_class_mapping[target_class] = 11 Types.const_set( 12 gql_type_class_name, 13 Class.new(Types::BaseObject) do end 16 ) 17 end 18 end 19 end

Slide 114

Slide 114 text

1 class GqlTypeGenerator 2 attr_accessor :target_to_gql_class_mapping 3 4 def initialize(target_classes) 5 @target_to_gql_class_mapping = {} 6 7 target_classes.each do |target_class| 8 gql_type_class_name = "#{target_class.to_s}Type" 9 10 @target_to_gql_class_mapping[target_class] = 11 Types.const_set( 12 gql_type_class_name, 13 Class.new(Types::BaseObject) do end 16 ) 17 end 18 end 19 end

Slide 115

Slide 115 text

1 class GqlTypeGenerator 2 attr_accessor :target_to_gql_class_mapping 3 4 def initialize(target_classes) 5 @target_to_gql_class_mapping = {} 6 7 target_classes.each do |target_class| 8 gql_type_class_name = "#{target_class.to_s}Type" 9 10 @target_to_gql_class_mapping[target_class] = 11 Types.const_set( 12 gql_type_class_name, 13 Class.new(Types::BaseObject) do end 16 ) 17 end 18 end 19 end

Slide 116

Slide 116 text

1 class GqlTypeGenerator 2 attr_accessor :target_to_gql_class_mapping 3 4 def initialize(target_classes) 5 @target_to_gql_class_mapping = {} 6 7 target_classes.each do |target_class| 8 gql_type_class_name = "#{target_class.to_s}Type" 9 10 @target_to_gql_class_mapping[target_class] = 11 Types.const_set( 12 gql_type_class_name, 13 Class.new(Types::BaseObject) do end 16 ) 17 end 18 end 19 end

Slide 117

Slide 117 text

1 class GqlTypeGenerator 2 attr_accessor :target_to_gql_class_mapping 3 4 def initialize(target_classes) 5 @target_to_gql_class_mapping = {} 6 7 target_classes.each do |target_class| 8 gql_type_class_name = "#{target_class}Type" 9 10 @target_to_gql_class_mapping[target_class] = 11 Types.const_set( 12 gql_type_class_name, 13 Class.new(Types::BaseObject) do end 16 ) 17 end 18 end 19 end

Slide 118

Slide 118 text

1 class GqlTypeGenerator 2 attr_accessor :target_to_gql_class_mapping 3 4 def initialize(target_classes) 5 @target_to_gql_class_mapping = {} 6 7 target_classes.each do |target_class| 8 gql_type_class_name = "#{target_class}Type" 9 10 @target_to_gql_class_mapping[target_class] = 11 Types.const_set( 12 gql_type_class_name, 13 Class.new(Types::BaseObject) do end 16 ) 17 end 18 end 19 end

Slide 119

Slide 119 text

1 class GqlTypeGenerator 2 attr_accessor :target_to_gql_class_mapping 3 4 def initialize(target_classes) 5 @target_to_gql_class_mapping = {} 6 7 target_classes.each do |target_class| 8 gql_type_class_name = "#{target_class}Type" 9 10 @target_to_gql_class_mapping[target_class] = 11 Types.const_set( 12 gql_type_class_name, 13 Class.new(Types::BaseObject) 16 ) 17 end 18 end 19 end

Slide 120

Slide 120 text

7 target_classes.each do |target_class| 8 gql_type_class_name = "#{target_class}Type" 9 10 @target_to_gql_class_mapping[target_class] = 11 Types.const_set( 12 gql_type_class_name, 13 Class.new(Types::BaseObject) 16 ) 17 end 1 class Types::PaymentType < Types::BaseObject 2 field :id, ID, null: false 3 field :amount, Int, null: false 4 field :merchant, Types::MerchantType, null: false 5 end

Slide 121

Slide 121 text

7 target_classes.each do |target_class| 8 gql_type_class_name = "#{target_class}Type" 9 10 @target_to_gql_class_mapping[target_class] = 11 Types.const_set( 12 gql_type_class_name, 13 Class.new(Types::BaseObject) 16 ) 17 end 1 class Types::PaymentType < Types::BaseObject 2 field :id, ID, null: false 3 field :amount, Int, null: false 4 field :merchant, Types::MerchantType, null: false 5 end

Slide 122

Slide 122 text

7 target_classes.each do |target_class| 8 gql_type_class_name = "#{target_class}Type" 9 10 @target_to_gql_class_mapping[target_class] = 11 Types.const_set( 12 gql_type_class_name, 13 Class.new(Types::BaseObject) 16 ) 17 end 1 class Types::PaymentType < Types::BaseObject 2 field :id, ID, null: false 3 field :amount, Int, null: false 4 field :merchant, Types::MerchantType, null: false 5 end

Slide 123

Slide 123 text

1 class Types::PaymentType < Types::BaseObject 2 field :id, ID, null: false 3 field :amount, Int, null: false 4 field :merchant, Types::MerchantType, null: false 5 end

Slide 124

Slide 124 text

Types::PaymentType => Types::PaymentType Types::PaymentType.own_fields.keys => [“id”, “amount”, “merchant”] Types::PaymentType.own_fields[“id"].class => GraphQL::Schema::Field Types::MerchantType.own_fields[“id"] => #

Slide 125

Slide 125 text

1 class GqlTypeGenerator 2 attr_accessor :target_to_gql_class_mapping 3 4 def initialize(target_classes) . . . 19 20 def self.add_fields_to_gql_type(target_class, gql_type_class) 22 target_class.gql_field_annotations.each do |field, def_hash| 23 fieldType = field_definition_hash[:type].constantize 24 nullable = field_definition_hash[:null] 25 field field_name, fieldType, null: nullable 26 end 28 end 29 end 1 class Types::PaymentType < Types::BaseObject 2 field :id, ID, null: false 3 field :amount, Int, null: false 4 field :merchant, Types::MerchantType, null: false 5 end

Slide 126

Slide 126 text

1 class GqlTypeGenerator 2 attr_accessor :target_to_gql_class_mapping 3 4 def initialize(target_classes) . . . 19 20 def self.add_fields_to_gql_type(target_class, gql_type_class) 22 target_class.gql_field_annotations.each do |field, def_hash| 23 fieldType = field_definition_hash[:type].constantize 24 nullable = field_definition_hash[:null] 25 field field_name, fieldType, null: nullable 26 end 28 end 29 end 1 class Types::PaymentType < Types::BaseObject 2 field :id, ID, null: false 3 field :amount, Int, null: false 4 field :merchant, Types::MerchantType, null: false 5 end

Slide 127

Slide 127 text

1 class GqlTypeGenerator 2 attr_accessor :target_to_gql_class_mapping 3 4 def initialize(target_classes) . . . 19 20 def self.add_fields_to_gql_type(target_class, gql_type_class) 22 target_class.gql_field_annotations.each do |field, def_hash| 23 fieldType = field_definition_hash[:type].constantize 24 nullable = field_definition_hash[:null] 25 field field_name, fieldType, null: nullable 26 end 28 end 29 end 1 class Types::PaymentType < Types::BaseObject 2 field :id, ID, null: false 3 field :amount, Int, null: false 4 field :merchant, Types::MerchantType, null: false 5 end

Slide 128

Slide 128 text

1 class GqlTypeGenerator 2 attr_accessor :target_to_gql_class_mapping 3 4 def initialize(target_classes) . . . 19 20 def self.add_fields_to_gql_type(target_class, gql_type_class) 22 target_class.gql_field_annotations.each do |field, def_hash| 23 fieldType = field_definition_hash[:type].constantize 24 nullable = field_definition_hash[:null] 25 field field_name, fieldType, null: nullable 26 end 28 end 29 end 1 class Types::PaymentType < Types::BaseObject 2 field :id, ID, null: false 3 field :amount, Int, null: false 4 field :merchant, Types::MerchantType, null: false 5 end

Slide 129

Slide 129 text

1 class GqlTypeGenerator 2 attr_accessor :target_to_gql_class_mapping 3 4 def initialize(target_classes) . . . 19 20 def self.add_fields_to_gql_type(target_class, gql_type_class) 22 target_class.gql_field_annotations.each do |field, def_hash| 23 fieldType = field_definition_hash[:type].constantize 24 nullable = field_definition_hash[:null] 25 graph_type_class.field field_name, fieldType, null: nullable 26 end 28 end 29 end 1 class Types::PaymentType < Types::BaseObject 2 field :id, ID, null: false 3 field :amount, Int, null: false 4 field :merchant, Types::MerchantType, null: false 5 end

Slide 130

Slide 130 text

gql_type_generator = GqlTypeGenerator .new(Target.all_target_classes) gql_type_generator .target_to_gql_class_mapping.each do |target, gql_type| GqlTypeGenerator.add_fields_to_gql_type( target_class, gql_type ) end

Slide 131

Slide 131 text

Types::QueryType Root Types Root Type Resolvers field :payment … field :merchant … def payment … def merchant … Types::PaymentType Types::MerchantType GQL Type Classes

Slide 132

Slide 132 text

Types::QueryType Root Types Root Type Resolvers field :payment … field :merchant … def payment … def merchant …

Slide 133

Slide 133 text

1 module Types 2 class QueryType < Types::BaseObject 3 field :payment, Types::PaymentType, null: false do 4 argument :id, ID, required: true 5 end 6 7 field :merchant, Types::MerchantType, null: false do 8 argument :id, ID, required: true 9 end 10 11 def payment(params) 12 Payment.lookup(params[:id]) 13 end 14 15 def merchant(params) 16 Merchant.lookup(params[:id]) 17 end 18 end 19 end 20

Slide 134

Slide 134 text

1 module Types 2 class QueryType < Types::BaseObject 3 field :payment, Types::PaymentType, null: false do 4 argument :id, ID, required: true 5 end 6 7 field :merchant, Types::MerchantType, null: false do 8 argument :id, ID, required: true 9 end 10 11 def payment(params) 12 Payment.lookup(params[:id]) 13 end 14 15 def merchant(params) 16 Merchant.lookup(params[:id]) 17 end 18 end 19 end 20

Slide 135

Slide 135 text

1 module Types 2 class QueryType < Types::BaseObject 3 field :payment, Types::PaymentType, null: false do 4 argument :id, ID, required: true 5 end 6 7 field :merchant, Types::MerchantType, null: false do 8 argument :id, ID, required: true 9 end 10 11 def payment(params) 12 Payment.lookup(params[:id]) 13 end 14 15 def merchant(params) 16 Merchant.lookup(params[:id]) 17 end 18 end 19 end 20 3 field :payment, Types::PaymentType, null: false do 4 argument :id, ID, required: true 5 end

Slide 136

Slide 136 text

3 field :payment, Types::PaymentType, null: false do 4 argument :id, ID, required: true 5 end

Slide 137

Slide 137 text

1 module Types 2 class QueryType < Types::BaseObject . . . 13 gql_type_generator 14 .target_to_gql_class_mapping.each do |target, gql_type| 15 field_name = target.to_s.downcase.to_sym 16 field field_name, gql_type, null: false do 17 argument :id, ID, required: true 18 end 24 end 25 end 26 end 27 3 field :payment, Types::PaymentType, null: false do 4 argument :id, ID, required: true 5 end

Slide 138

Slide 138 text

1 module Types 2 class QueryType < Types::BaseObject . . . 13 gql_type_generator 14 .target_to_gql_class_mapping.each do |target, gql_type| 15 field_name = target.to_s.downcase.to_sym 16 field field_name, gql_type, null: false do 17 argument :id, ID, required: true 18 end 24 end 25 end 26 end 27 3 field :payment, Types::PaymentType, null: false do 4 argument :id, ID, required: true 5 end

Slide 139

Slide 139 text

1 module Types 2 class QueryType < Types::BaseObject . . . 13 gql_type_generator 14 .target_to_gql_class_mapping.each do |target, gql_type| 15 field_name = target.to_s.downcase.to_sym 16 field field_name, gql_type, null: false do 17 argument :id, ID, required: true 18 end 24 end 25 end 26 end 27 3 field :payment, Types::PaymentType, null: false do 4 argument :id, ID, required: true 5 end

Slide 140

Slide 140 text

1 module Types 2 class QueryType < Types::BaseObject . . . 13 gql_type_generator 14 .target_to_gql_class_mapping.each do |target, gql_type| 15 field_name = target.to_s.downcase.to_sym 16 field field_name, gql_type, null: false do 17 argument :id, ID, required: true 18 end 24 end 25 end 26 end 27 3 field :payment, Types::PaymentType, null: false do 4 argument :id, ID, required: true 5 end

Slide 141

Slide 141 text

3 field :payment, Types::PaymentType, null: false do 4 argument :id, ID, required: true 5 end 1 module Types 2 class QueryType < Types::BaseObject 3 field :payment, Types::PaymentType, null: false do 4 argument :id, ID, required: true 5 end 6 7 field :merchant, Types::MerchantType, null: false do 8 argument :id, ID, required: true 9 end 10 11 def payment(params) 12 Payment.lookup(params[:id]) 13 end 14 15 def merchant(params) 16 Merchant.lookup(params[:id]) 17 end 18 end 19 end 20 11 def payment(params) 12 Payment.lookup(params[:id]) 13 end

Slide 142

Slide 142 text

1 module Types 2 class QueryType < Types::BaseObject 3 field :payment, Types::PaymentType, null: false do 4 argument :id, ID, required: true 5 end 6 7 field :merchant, Types::MerchantType, null: false do 8 argument :id, ID, required: true 9 end 10 11 def payment(params) 12 Payment.lookup(params[:id]) 13 end 14 15 def merchant(params) 16 Merchant.lookup(params[:id]) 17 end 18 end 19 end 20 11 def payment(params) 12 Payment.lookup(params[:id]) 13 end

Slide 143

Slide 143 text

11 def payment(params) 12 Payment.lookup(params[:id]) 13 end

Slide 144

Slide 144 text

11 def payment(params) 12 Payment.lookup(params[:id]) 13 end 1 module Types 2 class QueryType < Types::BaseObject 12 13 gql_type_generator 14 .target_to_gql_class_mapping.each do |target, gql_type| 15 field_name = target.to_s.downcase.to_sym . . . 20 define_method(field_name) do |params| 21 targetClass = Object.const_get(field_name.capitalize) 22 targetClass.lookup(params[:id]) 23 end 24 end 25 end 26 end 27

Slide 145

Slide 145 text

11 def payment(params) 12 Payment.lookup(params[:id]) 13 end 1 module Types 2 class QueryType < Types::BaseObject 12 13 gql_type_generator 14 .target_to_gql_class_mapping.each do |target, gql_type| 15 field_name = target.to_s.downcase.to_sym . . . 20 define_method(field_name) do |params| 21 targetClass = Object.const_get(field_name.capitalize) 22 targetClass.lookup(params[:id]) 23 end 24 end 25 end 26 end 27

Slide 146

Slide 146 text

11 def payment(params) 12 Payment.lookup(params[:id]) 13 end 1 module Types 2 class QueryType < Types::BaseObject 12 13 gql_type_generator 14 .target_to_gql_class_mapping.each do |target, gql_type| 15 field_name = target.to_s.downcase.to_sym . . . 20 define_method(field_name) do |params| 21 targetClass = Object.const_get(field_name.capitalize) 22 targetClass.lookup(params[:id]) 23 end 24 end 25 end 26 end 27

Slide 147

Slide 147 text

11 def payment(params) 12 Payment.lookup(params[:id]) 13 end 1 module Types 2 class QueryType < Types::BaseObject 12 13 gql_type_generator 14 .target_to_gql_class_mapping.each do |target, gql_type| 15 field_name = target.to_s.downcase.to_sym . . . 20 define_method(field_name) do |params| 21 targetClass = Object.const_get(field_name.capitalize) 22 targetClass.lookup(params[:id]) 23 end 24 end 25 end 26 end 27

Slide 148

Slide 148 text

11 def payment(params) 12 Payment.lookup(params[:id]) 13 end 1 module Types 2 class QueryType < Types::BaseObject 12 13 gql_type_generator 14 .target_to_gql_class_mapping.each do |target, gql_type| 15 field_name = target.to_s.downcase.to_sym . . . 20 define_method(field_name) do |params| 21 target_class = Object.const_get(field_name.capitalize) 23 end 24 end 25 end 26 end 27

Slide 149

Slide 149 text

11 def payment(params) 12 Payment.lookup(params[:id]) 13 end 1 module Types 2 class QueryType < Types::BaseObject 12 13 gql_type_generator 14 .target_to_gql_class_mapping.each do |target, gql_type| 15 field_name = target.to_s.downcase.to_sym . . . 20 define_method(field_name) do |params| 21 target_class = Object.const_get(field_name.capitalize) 22 target_class.lookup(params[:id]) 23 end 24 end 25 end 26 end 27

Slide 150

Slide 150 text

11 def payment(params) 12 Payment.lookup(params[:id]) 13 end 1 module Types 2 class QueryType < Types::BaseObject 12 13 gql_type_generator 14 .target_to_gql_class_mapping.each do |target, gql_type| 15 field_name = target.to_s.downcase.to_sym . . . 20 define_method(field_name) do |params| 21 target_class = Object.const_get(field_name.capitalize) 22 target_class.lookup(params[:id]) 23 end 24 end 25 end 26 end 27

Slide 151

Slide 151 text

demo

Slide 152

Slide 152 text

Let’s Add a New Target: Card

Slide 153

Slide 153 text

Payment - id - amount - merchant_id Merchant - id - store_name Is associated through merchant_id Today’s Data Model

Slide 154

Slide 154 text

Payment - id - amount - merchant_id Merchant - id - store_name Today’s Data Model Card - id - name_on_card - bin - payment_id

Slide 155

Slide 155 text

4 class Card < TargetBase 5 mattr_accessor :gql_field_annotations do 6 {} 7 end 8 9 stub_target_data( 10 resource: 'card', 11 id: 7890, 12 response_body: { 13 id: 7890, 14 bin: 1111, 15 names_on_card:'Shawnee Gao', 16 postal_code: 94114, 17 payment_id: 1234 18 } 19 ) 20 21 def self._fetch_target_data(id) 22 target_data = CardClient.get_by_id(id) 23 OpenStruct.new(target_data) 24 end . . . 40 41 def payment_id 42 target_data.payment_id 43 end 44 45 annotate_field "Types::PaymentType", null: false, definition: 46 def payment 47 @payment ||= Payment.lookup(payment_id) 48 end 49 end

Slide 156

Slide 156 text

5 mattr_accessor :gql_field_annotations do 6 {} 7 end 8 9 stub_target_data( 10 resource: 'card', 11 id: 7890, 12 response_body: { 13 id: 7890, 14 bin: 1111, 15 names_on_card:'Shawnee Gao', 16 postal_code: 94114, 17 payment_id: 1234 18 } 19 ) 20 21 def self._fetch_target_data(id) 22 target_data = CardClient.get_by_id(id) 23 OpenStruct.new(target_data) 24 end . . . 40 41 def payment_id 42 target_data.payment_id 43 end 44 45 annotate_field "Types::PaymentType", null: false, definition: 46 def payment 47 @payment ||= Payment.lookup(payment_id) 48 end

Slide 157

Slide 157 text

5 mattr_accessor :gql_field_annotations do 6 {} 7 end 8 9 stub_target_data( 10 resource: 'card', 11 id: 7890, 12 response_body: { 13 id: 7890, 14 bin: 1111, 15 names_on_card:'Shawnee Gao', 16 postal_code: 94114, 17 payment_id: 1234 18 } 19 ) 20 21 def self._fetch_target_data(id) 22 target_data = CardClient.get_by_id(id) 23 OpenStruct.new(target_data) 24 end . . . 40 41 def payment_id 42 target_data.payment_id 43 end 44 45 annotate_field "Types::PaymentType", null: false, definition: 46 def payment 47 @payment ||= Payment.lookup(payment_id) 48 end 49 end

Slide 158

Slide 158 text

demo

Slide 159

Slide 159 text

Requirements Change

Slide 160

Slide 160 text

Payment - id - amount - merchant_id Merchant - id - store_name Today’s Data Model Card - id - name_on_card - bin - payment_id

Slide 161

Slide 161 text

Payment - id - amount - merchant_id - card_id Merchant - id - store_name Today’s Data Model Is associated through merchant_id Card - id - name_on_card - bin associated through payment_id

Slide 162

Slide 162 text

Types::QueryType Root Types Root Type Resolvers field :payment … field :merchant … def payment … def merchant …

Slide 163

Slide 163 text

1 module Types 2 class QueryType < Types::BaseObject 4 gql_type_generator = GqlTypeGenerator.new(Target.all_target_classes) . . . 13 gql_type_generator 14 .target_to_gql_class_mapping.each do |target, gql_type| 15 field_name = target.to_s.downcase.to_sym 16 field field_name, gql_type, null: false do 17 argument :id, ID, required: true 18 end 24 end 25 end 26 end 27

Slide 164

Slide 164 text

1 module Types 2 class QueryType < Types::BaseObject 4 gql_type_generator = GqlTypeGenerator.new(Target.all_target_classes) . . . 12 valid_root_types_target_classes = [Payment, Merchant] 13 gql_type_generator 14 .target_to_gql_class_mapping.each do |target, gql_type| 15 field_name = target.to_s.downcase.to_sym 16 field field_name, gql_type, null: false do 17 argument :id, ID, required: true 18 end 24 end 25 end 26 end 27

Slide 165

Slide 165 text

1 module Types 2 class QueryType < Types::BaseObject 4 gql_type_generator = GqlTypeGenerator.new(Target.all_target_classes) . . . 12 valid_root_type_targets = [Payment, Merchant] 13 valid_root_type_targets.each do |target| 14 gql_type_class = gql_type_generator.target_to_gql_class_mapping[target] 15 field_name = target.to_s.downcase.to_sym 16 field field_name, gql_type, null: false do 17 argument :id, ID, required: true 18 end 24 end 25 end 26 end 27

Slide 166

Slide 166 text

demo

Slide 167

Slide 167 text

Tests

Slide 168

Slide 168 text

1 require 'spec_helper' 2 3 describe "GqlTypeGenerator#initialize" do 4 it 'creates a Target Class to GqlType Class mapping' do 5 all_target_classes = Target.all_target_classes 6 all_target_classes.each do |target_class| 7 expect{ "Types::#{target_class.to_s}Type".constantize }.to raise_error(NameError) 8 end 9 10 util = GqlTypeGenerator.new(all_target_classes) 11 12 all_target_classes.each do |target_class| 13 gql_type_class_name = "Types::#{target_class.to_s}Type".constantize 14 expect(gql_type_class_name).to be_instance_of Class 15 util.target_to_gql_class_mapping[target_class] = gql_type_class_name 16 end 17 end 18 end

Slide 169

Slide 169 text

1 require 'spec_helper' 2 3 describe "GqlTypeGenerator#initialize" do 4 it 'creates a Target Class to GqlType Class mapping' do 5 all_target_classes = Target.all_target_classes 6 all_target_classes.each do |target_class| expect{ "Types::#{target_class.to_s}Type".constantize }.to raise_error(NameError) 8 end 9 10 util = GqlTypeGenerator.new(all_target_classes) 11 12 all_target_classes.each do |target_class| 13 gql_type_class_name = "Types::#{target_class.to_s}Type".constantize 14 expect(gql_type_class_name).to be_instance_of Class 15 util.target_to_gql_class_mapping[target_class] = gql_type_class_name 16 end 17 end 18 end

Slide 170

Slide 170 text

1 require 'spec_helper' 2 3 describe "GqlTypeGenerator#initialize" do 4 it 'creates a Target Class to GqlType Class mapping' do 5 all_target_classes = Target.all_target_classes 6 all_target_classes.each do |target_class| 7 expect{ "Types::#{target_class.to_s}Type".constantize }.to raise_error(NameError) 8 end 9 10 util = GqlTypeGenerator.new(all_target_classes) 11 12 all_target_classes.each do |target_class| 13 gql_type_class_name = "Types::#{target_class.to_s}Type".constantize 14 expect(gql_type_class_name).to be_instance_of Class 15 util.target_to_gql_class_mapping[target_class] = gql_type_class_name 16 end 17 end 18 end

Slide 171

Slide 171 text

1 require 'spec_helper' 2 3 describe "GqlTypeGenerator#initialize" do 4 it 'creates a Target Class to GqlType Class mapping' do 5 all_target_classes = Target.all_target_classes 6 all_target_classes.each do |target_class| 7 expect{ "Types::#{target_class.to_s}Type".constantize }.to raise_error(NameError) 8 end 9 10 gql_type_generator = GqlTypeGenerator.new(all_target_classes) 11 12 all_target_classes.each do |target_class| 13 gql_type_class_name = "Types::#{target_class.to_s}Type".constantize 14 expect(gql_type_class_name).to be_instance_of Class 15 util.target_to_gql_class_mapping[target_class] = gql_type_class_name 16 end 17 end 18 end

Slide 172

Slide 172 text

1 require 'spec_helper' 2 3 describe "GqlTypeGenerator#initialize" do 4 it 'creates a Target Class to GqlType Class mapping' do 5 all_target_classes = Target.all_target_classes 6 all_target_classes.each do |target_class| 7 expect{ "Types::#{target_class.to_s}Type".constantize }.to raise_error(NameError) 8 end 9 gql_type_generator = GqlTypeGenerator.new(all_target_classes) 11 12 all_target_classes.each do |target_class| 13 gql_type_class_name = "Types::#{target_class.to_s}Type".constantize 14 expect(gql_type_class_name).to be_instance_of Class 15 util.target_to_gql_class_mapping[target_class] = gql_type_class_name 16 end 17 end 18 end

Slide 173

Slide 173 text

1 require 'spec_helper' 2 3 describe "GqlTypeGenerator#initialize" do 4 it 'creates a Target Class to GqlType Class mapping' do 5 all_target_classes = Target.all_target_classes 6 all_target_classes.each do |target_class| 7 expect{ "Types::#{target_class.to_s}Type".constantize }.to raise_error(NameError) 8 end 9 10 gql_type_generator = GqlTypeGenerator.new(all_target_classes) 11 12 all_target_classes.each do |target_class| 13 gql_type_class_name = "Types::#{target_class.to_s}Type".constantize 14 expect(gql_type_class_name).to be_instance_of Class 15 util.target_to_gql_class_mapping[target_class] = gql_type_class_name 16 end 17 end 18 end

Slide 174

Slide 174 text

1 require 'spec_helper' 2 3 describe "GqlTypeGenerator#initialize" do 4 it 'creates a Target Class to GqlType Class mapping' do 5 all_target_classes = Target.all_target_classes 6 all_target_classes.each do |target_class| 7 expect{ "Types::#{target_class.to_s}Type".constantize }.to raise_error(NameError) 8 end 9 10 gql_type_generator = GqlTypeGenerator.new(all_target_classes) 11 12 all_target_classes.each do |target_class| 13 gql_type_class_name = "Types::#{target_class.to_s}Type".constantize 14 expect(gql_type_class_name).to be_instance_of Class 15 gql_type_generator.target_to_gql_class_mapping[target_class] = gql_type_class_name 16 end 17 end 18 end

Slide 175

Slide 175 text

1 require 'spec_helper' 2 3 describe "GqlTypeGenerator#initialize" do 4 it 'creates a Target Class to GqlType Class mapping' do 5 all_target_classes = Target.all_target_classes 6 all_target_classes.each do |target_class| 7 expect{ "Types::#{target_class.to_s}Type".constantize }.to raise_error(NameError) 8 end 9 10 gql_type_generator = GqlTypeGenerator.new(all_target_classes) 11 12 all_target_classes.each do |target_class| 13 gql_type_class_name = "Types::#{target_class.to_s}Type".constantize expect(gql_type_class_name).to be_instance_of Class gql_type_generator.target_to_gql_class_mapping[target_class] = gql_type_class_name 16 end 17 end 18 end

Slide 176

Slide 176 text

1 require 'spec_helper' 2 . . . 19 20 describe "GqlTypeGenerator#add_annotated_target_fields_to_gql_type_class" do 21 it 'creates a GqlType Class from Target constant' do 22 Types::PaymentType = Class.new(Types::BaseObject) 26 Types::MerchantType = Class.new(Types::BaseObject) 27 annotated_target_fields_hash = Payment.gql_field_annotations.sort 29 30 GqlTypeGenerator 31 .add_annotated_target_fields_to_gql_type_class( 32 Payment, Types::PaymentType 33 ) 34 35 gql_type_fields_hash = 36 Types::PaymentType.instance_variable_get(:@own_fields) 37 .sort.to_h.symbolize_keys 40 41 annotated_target_fields_hash.each do |field_name, field_definition| 42 expect(gql_type_fields_hash[field_name] 43 .instance_variable_get(:@return_type_expr)) 44 .to eq(field_definition[:type].constantize) 45 46 expect(gql_type_fields_hash[field_name] 47 .instance_variable_get(:@return_type_null)) 48 .to eq(field_definition[:null]) 49 end 50 51 end 52 end

Slide 177

Slide 177 text

1 require 'spec_helper' 2 . . . 19 20 describe "GqlTypeGenerator#add_annotated_target_fields_to_gql_type_class" do 21 it 'creates a GqlType Class from Target constant' do 22 Types::PaymentType = Class.new(Types::BaseObject) 26 Types::MerchantType = Class.new(Types::BaseObject) 27 annotated_target_fields_hash = Payment.gql_field_annotations.sort 29 30 GqlTypeGenerator 31 .add_annotated_target_fields_to_gql_type_class( 32 Payment, Types::PaymentType 33 ) 34 35 gql_type_fields_hash = 36 Types::PaymentType.instance_variable_get(:@own_fields) 37 .sort.to_h.symbolize_keys 40 41 annotated_target_fields_hash.each do |field_name, field_definition| 42 expect(gql_type_fields_hash[field_name] 43 .instance_variable_get(:@return_type_expr)) 44 .to eq(field_definition[:type].constantize) 45 46 expect(gql_type_fields_hash[field_name] 47 .instance_variable_get(:@return_type_null)) 48 .to eq(field_definition[:null]) 49 end 50 51 end 52 end

Slide 178

Slide 178 text

1 require 'spec_helper' 2 . . . 19 20 describe "GqlTypeGenerator#add_annotated_target_fields_to_gql_type_class" do 21 it 'creates a GqlType Class from Target constant' do 22 Types::PaymentType = Class.new(Types::BaseObject) 26 Types::MerchantType = Class.new(Types::BaseObject) 27 annotated_target_fields_hash = Payment.gql_field_annotations.sort 29 30 GqlTypeGenerator 31 .add_annotated_target_fields_to_gql_type_class( 32 Payment, Types::PaymentType 33 ) 34 35 gql_type_fields_hash = 36 Types::PaymentType.instance_variable_get(:@own_fields) 37 .sort.to_h.symbolize_keys 40 41 annotated_target_fields_hash.each do |field_name, field_definition| 42 expect(gql_type_fields_hash[field_name] 43 .instance_variable_get(:@return_type_expr)) 44 .to eq(field_definition[:type].constantize) 45 46 expect(gql_type_fields_hash[field_name] 47 .instance_variable_get(:@return_type_null)) 48 .to eq(field_definition[:null]) 49 end 50 51 end 52 end

Slide 179

Slide 179 text

1 require 'spec_helper' 2 . . . 19 20 describe "GqlTypeGenerator#add_annotated_target_fields_to_gql_type_class" do 21 it 'creates a GqlType Class from Target constant' do 22 Types::PaymentType = Class.new(Types::BaseObject) 26 Types::MerchantType = Class.new(Types::BaseObject) 27 annotated_target_fields_hash = Payment.gql_field_annotations.sort 29 30 GqlTypeGenerator 31 .add_annotated_target_fields_to_gql_type_class( 32 Payment, Types::PaymentType 33 ) 34 35 gql_type_fields_hash = 36 Types::PaymentType.instance_variable_get(:@own_fields) 37 .sort.to_h.symbolize_keys 40 41 annotated_target_fields_hash.each do |field_name, field_definition| 42 expect(gql_type_fields_hash[field_name] 43 .instance_variable_get(:@return_type_expr)) 44 .to eq(field_definition[:type].constantize) 45 46 expect(gql_type_fields_hash[field_name] 47 .instance_variable_get(:@return_type_null)) 48 .to eq(field_definition[:null]) 49 end 50 51 end 52 end

Slide 180

Slide 180 text

Wrap Up: Revisiting Our Checklist 1.Flexible 2.Reduce Boilerplate 3.Improved Developer Experience

Slide 181

Slide 181 text

[email protected] github@shawneegao @gao_shawnee

Slide 182

Slide 182 text

Business Operations Platform Team at Square!!!

Slide 183

Slide 183 text

[email protected] Interested in developing with Square?

Slide 184

Slide 184 text

The End!!!!!