GraphQL RubyのResolverについて

# 概要
GraphQL Rubyの `resolver`についてまとめました。

# 開発環境
* ruby 2.6.5
* rails 5.2.4
* graphql 1.10.10 
    - https://graphql-ruby.org/getting_started

# resolverを使う前のGraphQL 実装
* 参考: 
https://graphql-ruby.org/schema/root_types.html

* Rails Model

```rb
# == Schema Information
#
# Table name: subject_scores
#
#  id                    :integer
#  subject_name(科目名)   :string
#  user_name(ユーザー名)      :string 
#  score(点数)            :integer
class SubjectScore < ApplicationRecord
end
```

* Types class

```rb
module Types
  class QueryType < GraphQL::Schema::Object
    field :subject_scores, [SubjectScoreType], 'Returns all subject scores', null: true
  end
end
```

```rb
module Types
  class SubjectScoreType < GraphQL::Schema::Object
    field :id, Integer, null: true
    field :subject_name, String, null: true
    field :user_name, String, null: true
    field :score, Integer, null: true
  end
end
```

* Request

```
subjectScores: {
  id
  subjectName
  userName
  score
}
```

* Response (データベースの値がそのまま返却される）

```json
"data": {
  "subjectScores": [
     {"id": 1, "subjectName": "英語", "userName": "太郎1", "score": 25},
     {"id": 1, "subjectName": "英語", "userName": "太郎2", "score": 50},
     {"id": 1, "subjectName": "英語", "userName": "太郎3", "score": 60}
  ]
}
```


# resolverで値を加工して返却する
scoreが30点未満のユーザー名をマスキングしてみます。

### resolver パターン1
* 参考： https://graphql-ruby.org/fields/introduction.html#field-resolution
field名と同じメソッド名を定義するパターン

* resolverの定義

```rb
module Types
  class SubjectScoreType < GraphQL::Schema::Object
    field :id, Integer, null: true
    field :subject_name, String, null: true
    field :user_name, String, null: true
    field :score, Integer, null: true

    def user_name
      if object.score < 30
        '***'
      else
        object.user_name
      end
    end
  end
end
```
* Response（30点未満のユーザー名がマスキングされる）

```json
"data": {
  "subjectScores": [
     {"id": 1, "subjectName": "英語", "userName": "***", "score": 25},
     {"id": 1, "subjectName": "英語", "userName": "太郎2", "score": 50},
     {"id": 1, "subjectName": "英語", "userName": "太郎3", "score": 60}
  ]
}
```



### resolver パターン2
* 参考： https://graphql-ruby.org/fields/introduction.html#field-resolution
`field`の `resolver_method`属性を使うパターン

* resolverの定義

```rb
module Types
  class SubjectScoreType < GraphQL::Schema::Object
    field :id, Integer, null: true
    field :subject_name, String, null: true
    field :user_name, String, null: true, resolver_method: :user_name_masking_resolver
    field :score, Integer, null: true

    def user_name_masking_resolver
      if object.score < 30
        '***'
      else
        object.user_name
      end
    end
  end
end
```

* Response（30点未満のユーザー名がマスキングされる）

```json
"data": {
  "subjectScores": [
     {"id": 1, "subjectName": "英語", "userName": "***", "score": 25},
     {"id": 1, "subjectName": "英語", "userName": "太郎2", "score": 50},
     {"id": 1, "subjectName": "英語", "userName": "太郎3", "score": 60}
  ]
}
```

### resolver パターン3
https://graphql-ruby.org/fields/resolvers.html#using-resolver
`field`の `resolver`属性を使うパターン。
（resolverクラスを別で定義する必要があります）

* resolverクラスの定義

```rb
# app/graphql/resolvers/base.rb
module Resolvers
  class Base < GraphQL::Schema::Resolver
  end
end
```

```rb
module Resolvers
  class MaskingScore < Resolvers::Base
    type String, null: true

    def resolve
      if object.score < 30
        '***'
      else
        object.user_name
      end
    end
  end
end
```

* `user_name`fieldの`resolver`属性に `Resolvers::MaskingScore`を指定します。

```rb
module Types
  class SubjectScoreType < GraphQL::Schema::Object
    field :id, Integer, null: true
    field :subject_name, String, null: true
    field :user_name, String, null: true, resolver: Resolvers::MaskingScore
    field :score, Integer, null: true
  end
end
```

* Response（30点未満のユーザー名がマスキングされる）

```json
"data": {
  "subjectScores": [
     {"id": 1, "subjectName": "英語", "userName": "***", "score": 25},
     {"id": 1, "subjectName": "英語", "userName": "太郎2", "score": 50},
     {"id": 1, "subjectName": "英語", "userName": "太郎3", "score": 60}
  ]
}
```

# まとめ
この例ではパターン1で十分ですが、
resolverを共通処理化し、複数のfieldでそのresolverを使う場合もあると思います。（実際にありました）
その場合はパターン2、3が有効かと。