All Articles

Custom scalar in GraphQL-ruby

GraphQL-ruby has built-in Scalar types. Scalar types are used when the field or argument resolves the value. In some cases, these scalar types might not fit in our application requirement and that is when custom scalar types come into the picture. One such example is DateTime scalar type.

Problem statement

An application can have various requirement. It may need to display date in string format or need consider timezones or parse different format DateTime string/object, etc. Let’s take the example of DateTime custom scalars used by GitHub. GitHub has custom scalar for millisecond precise DateTime or GitTimestamp

GraphQL-ruby has ISO8601DateTime and ISO8601Date built-in scalars which could be used for ISO 8601-encoded DateTime and Date.

field :created_at, GraphQL::Types::ISO8601DateTime, null: false
field :birthday, GraphQL::Types::ISO8601Date, null: false

These scalars can put a limitation when an application has some requirement that cannot be achieved by using built-in scalar types. A simple solution is to write a custom scalar as per our requirement and use it as necessary.

A custom scalar needs coerce_input and coerce_result class methods.

DateTime which when used on GraphQL argument calls coerce_input at runtime and converts the date string to DateTime object in the timezone. Similarly, when a GraphQL field uses the custom scalar, GraphQL calls coerce_result at runtime returning the DateTime in MM/DD/YYYY format.

# graphql/types/custom_scalars/date_time_type.rb

class Types::CustomScalars::DateTimeType < Types::BaseScalar
  description "Formatted DateTime object"

  def self.coerce_input(input_value, context)
    Time.zone.strptime(input_value, "%m/%d/%Y")
  rescue ArgumentError
    nil
  end

  def self.coerce_result(ruby_value, context)
    ruby_value.strftime("%m/%d/%Y")
  end
end

Similarly, here is the example for Time custom scalar.

# graphql/types/custom_scalars/time_type.rb

class Types::CustomScalars::TimeType < Types::BaseScalar
  description "Formatted Time object"

  def self.coerce_input(input_value, context)
    Time.zone.strptime(input_value, "%H:%M:%S %p")
  rescue ArgumentError
    nil
  end

  def self.coerce_result(ruby_value, context)
    ruby_value.strftime("%H:%M:%S %p")
  end

Happy Coding!!