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

Ruby_Kaigi_slides.pdf

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.

shawneegao

April 18, 2019
Tweet

Other Decks in Technology

Transcript

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

    View Slide

  2. About Me

    View Slide

  3. View Slide

  4. View Slide

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

    View Slide

  6. View Slide

  7. Japan Loves Cash

    View Slide

  8. View Slide

  9. View Slide

  10. View Slide

  11. View Slide

  12. View Slide

  13. View Slide

  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

    View Slide

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

    View Slide

  16. 273,249 lines of Ruby

    View Slide

  17. 22,337 lines of YAML

    View Slide

  18. Over 200 Controllers

    View Slide

  19. Talking to over 150
    Services

    View Slide

  20. View Slide

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

    View Slide

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

    View Slide

  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”
    }
    ]
    }

    View Slide

  24. graphql demo

    View Slide

  25. Different Resources

    Relational
    Highly Diverse

    View Slide

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

    View Slide

  27. REST
    ~150 Specialized Services

    View Slide

  28. ~150 Specialized Services
    REST Backend REST Frontend
    Agents

    View Slide

  29. ~150 Specialized Services
    REST Backend GQL Frontend
    Agents

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  33. Targets

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  48. => 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”
    => #

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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
    }

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  77. Payment.lookup(1234)

    View Slide

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

    View Slide

  79. Is metaprogramming a
    good idea?

    View Slide

  80. 1. Flexible

    2. Reduce Boilerplate

    3. Improved Developer Experience
    Is metaprogramming a good
    idea?

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  85. Types::PaymentType
    Types::MerchantType
    GQL Type Classes

    View Slide

  86. Priming:
    Target Class Annotations

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  93. Concepts :
    Dynamic Methods
    Unbound Methods

    View Slide

  94. Console Demo

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  109. Let’s Generate the GQL Type Classes

    View Slide

  110. gql_type_generator = GqlTypeGenerator.new(all_target_classes)
    => {
    Payment: Types::PaymentType,
    Merchant: Types::MerchantType
    }
    => #@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

    View Slide

  111. Concepts :
    Ruby Object Model
    Constants

    View Slide

  112. console demo

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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"]
    => #@underscored_name="id", @name="id", @description=nil, @field=nil,
    @function=nil . . .>

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  151. demo

    View Slide

  152. Let’s Add a New Target: Card

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  158. demo

    View Slide

  159. Requirements
    Change

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  166. demo

    View Slide

  167. Tests

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  180. Wrap Up:
    Revisiting Our Checklist
    1.Flexible

    2.Reduce Boilerplate

    3.Improved Developer Experience

    View Slide

  181. View Slide

  182. Business Operations Platform Team at Square!!!

    View Slide

  183. [email protected]
    Interested in developing with Square?

    View Slide

  184. The End!!!!!

    View Slide