Ruby_Kaigi_slides.pdf

Fba080a087394e1860c8688e8db9458d?s=47 shawneegao
April 18, 2019

 Ruby_Kaigi_slides.pdf

I will start with some GraphQL basics, then dig into process of metaprogramming a GraphQL layer from a demo ruby server. I will explain the benefits of using this design pattern and how it improves developer experience.

Fba080a087394e1860c8688e8db9458d?s=128

shawneegao

April 18, 2019
Tweet

Transcript

  1. GraphQL Migration: A Use Case for Metaprogramming? RubyKaigi 2019 shawneegao@squareup.com

    @gao_shawnee
  2. About Me

  3. None
  4. None
  5. wagashi wagashi wagashi Lake Ashi ft. Mt. Fuji (a.k.a. shy

    Fuji-san as he’s often covered by fog).
  6. None
  7. Japan Loves Cash

  8. None
  9. None
  10. None
  11. None
  12. None
  13. None
  14. 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
  15. Locations Promo Appointments Seller Service Inventory Chargebacks Capital Employees Deposits

    Marketing Hardware Dashboard’s Write Paths Payments Subscriptions
  16. 273,249 lines of Ruby

  17. 22,337 lines of YAML

  18. Over 200 Controllers

  19. Talking to over 150 Services

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

  22. { “data” : [ payment: { “id”: “4567”, “amount”: “300”,

    “merchant”: “1234”, “card: “7890” } ] }
  23. 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” } ] }
  24. graphql demo

  25. Different Resources Relational Highly Diverse

  26. Road Map • Existing Model Architecture • Write a GraphQL

    Ruby API • Add metaprogramming to our API • Add/Change Business Object • Testing
  27. REST ~150 Specialized Services

  28. ~150 Specialized Services REST Backend REST Frontend Agents

  29. ~150 Specialized Services REST Backend GQL Frontend Agents

  30. ~150 Specialized Services REST Backend GQL Frontend Agents Target

  31. ~150 Specialized Services REST Backend GQL Frontend Agents Target

  32. Target ~150 Specialized Services REST Backend GQL Frontend Agents

  33. Targets

  34. 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
  35. 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
  36. 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
  37. 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
  38. Payment - id - amount - merchant_id Merchant - id

    - store_name Is associated through merchant_id Today’s Data Model
  39. 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
  40. 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
  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. 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
  48. => Payment Payment p = Payment.lookup(1234) => #<Payment id=“1234”> p.amount

    => 100 p.merchant => #<Merchant id=“4567” > p.merchant.store_name => “Grillz by Nelly” Merchant => Merchant m = Merchant.lookup(4567) m.store_name => “Grillz by Nelly” => #<Merchant id=“4567”>
  49. Let’s Create a GraphQL API: GQL Ruby Query Flow

  50. Payment - id - amount - merchant_id Merchant - id

    - store_name Is associated through merchant_id Today’s Data Model
  51. Graphql Controller DemoSchema Mutation Type Query Type Payment Type Merchant

    Type id amount merchant id store_name Some Mutation Merchant Type id store_name
  52. 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
  53. Graphql Controller DemoSchema Mutation Type Query Type Payment Type Merchant

    Type id amount merchant id store_name Some Mutation Merchant Type id store_name
  54. query($paymentId: ID!){ payment(id: $paymentId) { id amount merchant { id

    storeName } } } variables{ “paymentId”: 1234 }
  55. 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
  56. 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
  57. 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
  58. 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
  59. 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
  60. 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
  61. 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
  62. 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
  63. payment(id: $paymentId) 3 field :payment, Types::PaymentType, null: false do 4

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

    argument :id, ID, required: true 5 end “paymentId”: 1234
  65. 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
  66. 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 }
  67. 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
  68. 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
  69. 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
  70. 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
  71. 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
  72. 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
  73. 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
  74. 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
  75. 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
  76. 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
  77. Payment.lookup(1234)

  78. Payment.lookup(1234) { "data": { "payment": { "id": "1234", "amount": 100,

    "merchant": { "id": “4567", "storeName": “Grillz by Nelly” } } } }
  79. Is metaprogramming a good idea?

  80. 1. Flexible 2. Reduce Boilerplate 3. Improved Developer Experience Is

    metaprogramming a good idea?
  81. 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
  82. 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
  83. Types::QueryType Root Types Root Type Resolvers field :payment … field

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

    :merchant … def payment … def merchant … Types::PaymentType Types::MerchantType GQL Type Classes
  85. Types::PaymentType Types::MerchantType GQL Type Classes

  86. Priming: Target Class Annotations

  87. Payment - id - amount - merchant_id Merchant - id

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

    - store_name Is associated through merchant_id Today’s Data Model gql_field_annotations gql_field_annotations
  89. Payment.gql_field_annotations => { amount: {type: "Integer", null: false}, id: {type:

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

    "Types::BaseObject::ID", null: false}, merchant: {type: "Types::MerchantType", null: false} }
  91. 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
  92. 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
  93. Concepts : Dynamic Methods Unbound Methods

  94. Console Demo

  95. 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
  96. 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
  97. 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
  98. 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
  99. 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
  100. 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
  101. 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
  102. 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
  103. 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
  104. 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
  105. 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
  106. 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
  107. 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
  108. 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
  109. Let’s Generate the GQL Type Classes

  110. gql_type_generator = GqlTypeGenerator.new(all_target_classes) => { Payment: Types::PaymentType, Merchant: Types::MerchantType }

    => #<GqlTypeGenerator:0x00007faadc4fff40 @target_to_gql_class_mapping={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
  111. Concepts : Ruby Object Model Constants

  112. console demo

  113. 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
  114. 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
  115. 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
  116. 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
  117. 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
  118. 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
  119. 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
  120. 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
  121. 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
  122. 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
  123. 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
  124. 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"] => #<GraphQL::Schema::Field:0x00007fdbffbd51e0 @original_name=:id, @underscored_name="id", @name="id", @description=nil, @field=nil, @function=nil . . .>
  125. 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
  126. 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
  127. 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
  128. 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
  129. 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
  130. 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
  131. Types::QueryType Root Types Root Type Resolvers field :payment … field

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

    :merchant … def payment … def merchant …
  133. 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
  134. 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
  135. 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
  136. 3 field :payment, Types::PaymentType, null: false do 4 argument :id,

    ID, required: true 5 end
  137. 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
  138. 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
  139. 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
  140. 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
  141. 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
  142. 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
  143. 11 def payment(params) 12 Payment.lookup(params[:id]) 13 end

  144. 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
  145. 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
  146. 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
  147. 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
  148. 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
  149. 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
  150. 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
  151. demo

  152. Let’s Add a New Target: Card

  153. Payment - id - amount - merchant_id Merchant - id

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

    - store_name Today’s Data Model Card - id - name_on_card - bin - payment_id
  155. 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
  156. 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
  157. 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
  158. demo

  159. Requirements Change

  160. Payment - id - amount - merchant_id Merchant - id

    - store_name Today’s Data Model Card - id - name_on_card - bin - payment_id
  161. 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
  162. Types::QueryType Root Types Root Type Resolvers field :payment … field

    :merchant … def payment … def merchant …
  163. 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
  164. 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
  165. 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
  166. demo

  167. Tests

  168. 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
  169. 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
  170. 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
  171. 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
  172. 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
  173. 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
  174. 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
  175. 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
  176. 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
  177. 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
  178. 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
  179. 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
  180. Wrap Up: Revisiting Our Checklist 1.Flexible 2.Reduce Boilerplate 3.Improved Developer

    Experience
  181. shawneegao@gmail.com github@shawneegao @gao_shawnee

  182. Business Operations Platform Team at Square!!!

  183. haegwan@squareup.com Interested in developing with Square?

  184. The End!!!!!