Graphql-ruby: resolve with a Hash

Created on 29 Feb 2016  路  6Comments  路  Source: rmosolgo/graphql-ruby

Is there any way to resolve a field with a ordinary Ruby Hash? At the moment I need to convert the response of a particular API response into a list of OpenStructs, so that graphql can use public_send on it.

resolve ->(obj, args, ctx) {
  HTTParty.post('http://example.com')
    .parsed_response
    .fetch('some_key')
    .map { |data| OpenStruct.new(data) } # this step feels unnecessary and could be a potential performance problem
}

Most helpful comment

The latest graphql version supports custom definitions, I posted a solution in the original PR but forgot to copy it over here! What do you think of this?

# Define a callable with desired behavior:
module HashField 
  def self.call(object_type, field_name, field_type) 
    # stringify the field name because graphql APIs depend on string names
    field_name_s = field_name.to_s

    # define a field which accesses its value with `[]`
    field = GraphQL::Field.define do 
      name(field_name_s) 
      type(field_type)
      resolve -> (obj, args, ctx) { 
        obj[field_name_s]
        # or, to access the field by Symbol instead:
        # obj[field_name] 
      }
    end 

    # attach the field to the object type which is being defined
    object_type.fields[field_name_s] = field
  end 
end

# Add it to the object's definitions:
GraphQL::ObjectType.accepts_definitions(hash_field: HashField)

# Use the new definition to define fields
GraphQL::ObjectType.define do 
  # These arguments are passed to `HashField.call`, which defines a field based on them
  hash_field :nickname, types.String 
end

All 6 comments

Nothing automatic, you have to specify the resolve proc by hand:

field :some_hash_member, types.String do 
  resolve -> (obj, args, ctx) { 
    obj[:some_hash]
  end
end

You could metaprogram that a bit,

# Generate a proc which resolves by getting 
# `key` from obj
def resolves_by_hash_key(key)
  -> (obj, args, ctx) { obj[key]
end

# ... 

field :some_key, types.String do 
  resolve(resolves_by_hash_key(:some_key))
end

We use hashes extensively and we are doing this atm:

       resolve -> (data, args, _ctx) do
         data['price']
       end

It would be great if the one line syntax could support this e.g.

field :price, !types.String, hash: true

The latest graphql version supports custom definitions, I posted a solution in the original PR but forgot to copy it over here! What do you think of this?

# Define a callable with desired behavior:
module HashField 
  def self.call(object_type, field_name, field_type) 
    # stringify the field name because graphql APIs depend on string names
    field_name_s = field_name.to_s

    # define a field which accesses its value with `[]`
    field = GraphQL::Field.define do 
      name(field_name_s) 
      type(field_type)
      resolve -> (obj, args, ctx) { 
        obj[field_name_s]
        # or, to access the field by Symbol instead:
        # obj[field_name] 
      }
    end 

    # attach the field to the object type which is being defined
    object_type.fields[field_name_s] = field
  end 
end

# Add it to the object's definitions:
GraphQL::ObjectType.accepts_definitions(hash_field: HashField)

# Use the new definition to define fields
GraphQL::ObjectType.define do 
  # These arguments are passed to `HashField.call`, which defines a field based on them
  hash_field :nickname, types.String 
end

This look good, but this seems to be a common case to warrant having hash_field in the gem maybe?

Coming back around to this,

hash_field in the gem

Somehow we have to disambiguate between String keys and Symbol keys. At first I thought

hash_field :sym_key_field # ... 
hash_field "string_key_field" # ... 

But that seems a bit hard on the eyes. What about accepting a hash_key: param, similar to the property: param? It could be:

hash_field :sym_key_field, types.Int, hash_key: :sym_key 
hash_field :str_key_field, types.Int, hash_key: "str_key"

That way, the hash_key field could be a signal to use #[] to resolve the field, and whatever value is passed is the key.

If you want to PR it, please feel free, otherwise I'll try to get a chance soon!

This is implemented on master like this:

field :sym_key_field, types.Int, hash_key: :sym_key 
field :str_key_field, types.Int, hash_key: "str_key"

You can pass a hash_key option to the field helper, or use hash_key in a block:

field ... do 
  hash_key :sym_key 
end 

See ae9abd2

Was this page helpful?
0 / 5 - 0 ratings