Scalar types represent primitive leaf values in GraphQL. Rails GraphQL allows you to create custom scalars to handle specialized data formats like dates, decimals, JSON, and more.
Create a custom scalar by inheriting from Rails::GraphQL::Type::Scalar:
module GraphQL class EmailScalar < Rails::GraphQL::Type::Scalar desc 'Represents a valid email address' class << self def valid_input?(value) value.is_a?(String) && value.match?(/\A[^@\s]+@[^@\s]+\z/) end def valid_output?(value) value.is_a?(String) end def as_json(value) value.to_s.downcase end def to_json(value) as_json(value).inspect end def deserialize(value) value.to_s.downcase end end endend
module GraphQL class DateScalar < Rails::GraphQL::Type::Scalar desc 'The Date scalar type represents a ISO 8601 string value.' use :specified_by, url: 'https://en.wikipedia.org/wiki/ISO_8601' class << self def valid_input?(value) super && !!Date.iso8601(value) rescue Date::Error false end def valid_output?(value) value.respond_to?(:to_date) && !!value.to_date rescue Date::Error false end def as_json(value) value.to_date.iso8601 end def deserialize(value) Date.iso8601(value) end end endend
module GraphQL class JsonScalar < Rails::GraphQL::Type::Scalar rename! 'JSON' desc <<~DESC The JSON scalar type represents an unstructured JSON data with all its available keys and values. DESC use :specified_by, url: 'https://www.rfc-editor.org/rfc/rfc8259' class << self def valid_input?(value) valid_token?(value, :hash) || value.is_a?(::Hash) end def valid_output?(value) value.is_a?(::Hash) end def to_json(value) value.to_json end def as_json(value) value.as_json end def deserialize(value) value.is_a?(::GQLParser::Token) ? JSON.parse(value) : value end end endend
module GraphQL class DecimalScalar < Rails::GraphQL::Type::Scalar desc <<~DESC The Decimal scalar type represents signed fractional values with extra precision. The values are exchange as string. DESC use :specified_by, url: 'https://en.wikipedia.org/wiki/IEEE_754-2008_revision' class << self def valid_input?(value) super && value.match?(/\A[+-]?\d+\.\d+\z/) end def valid_output?(value) value.respond_to?(:to_d) end def as_json(value) value.to_d.to_s end def deserialize(value) value.to_d end end endend
module GraphQL class UrlScalar < Rails::GraphQL::Type::Scalar desc 'Represents a valid URL' class << self def valid_input?(value) return false unless value.is_a?(String) uri = URI.parse(value) uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS) rescue URI::InvalidURIError false end def valid_output?(value) value.respond_to?(:to_s) end def as_json(value) value.to_s end def to_json(value) as_json(value).inspect end def deserialize(value) URI.parse(value.to_s) end end endend
module GraphQL class ColorScalar < Rails::GraphQL::Type::Scalar desc 'Represents a hex color code (e.g., #FF5733)' class << self def valid_input?(value) value.is_a?(String) && value.match?(/\A#[0-9A-Fa-f]{6}\z/) end def valid_output?(value) value.respond_to?(:to_s) end def as_json(value) value.to_s.upcase end def to_json(value) as_json(value).inspect end def deserialize(value) value.to_s.upcase end end endend
RSpec.describe GraphQL::EmailScalar do describe '.valid_input?' do it 'accepts valid emails' do expect(described_class.valid_input?('[email protected]')).to be true end it 'rejects invalid emails' do expect(described_class.valid_input?('invalid')).to be false end end describe '.deserialize' do it 'normalizes email to lowercase' do expect(described_class.deserialize('[email protected]')) .to eq('[email protected]') end end describe '.as_json' do it 'converts to lowercase' do expect(described_class.as_json('[email protected]')) .to eq('[email protected]') end endend