Absinthe: Adding resolve to an interface field

Created on 21 Jul 2017  路  8Comments  路  Source: absinthe-graphql/absinthe

Version: 1.4.0-beta.2

interface :foo do
  field :bar, :integer do
    resolve fn _, _ -> {:ok, 1} end
  end
end

It's possible to add a resolve field to a field on an interface, but the function is never used. I see two possible solutions:

  • use it as a default implementation for every other object that implements this interface
  • don't allow setting resolve in the interface.

I have a preference for the "default" solution because of the use case where I found this issue, but either one of those will make sense.

Feature

Most helpful comment

FYI this works in 1.5 by using import_fields $INTERFACE

defmodule Foo do
  use Absinthe.Schema

  interface :named do
    field :name, :string do
      resolve fn parent, _, _ -> {:ok, parent.name} end
    end

    resolve_type fn _, _ ->
      :user
    end
  end

  object :user do
    interfaces [:named]
    import_fields :named
  end

  query do

  end
end

All 8 comments

I like the default idea as well, at least at first...

As I've seen it it in other implementations, not supporting definition in the interface does serve a purpose: it makes it more obvious when an object type that's declared that it implements an interface type actually has. It also makes it clearer what an implementing types's field resolver actually is. I'm wondering if the second choice you point out might make more sense, to keep things simple.

Consider this case:

interface :foo do
  field :bar, :integer do
    resolve fn _, _ -> {:ok, 1} end
  end
end

object :baz do
  interface :foo
  field :bar, :integer
end

object :spam do
  interface :foo
end
  • What resolver is :baz trying to set for :bar? No resolution has been set for the field; should it use the interface default, or the system-level default? As a reader, would you have to check the interface every time to see what "fell through" to the interface?
  • Since the interface defines the resolver for :bar, is :spam meeting the requirements of the interface, or not? If not, how would it (avoiding the ambiguity of the :baz example above)

Yeah, it looks like the default could be ambiguous, so I guess raising would be a better solution.

Alternatively, what do you think about adding a default block to interfaces? This would mean you don't need to repeat what's there in the implementations. Any mention of a field would mean you lose anything from the default. But I guess this would be a major feature, for now

Scheduling addressing this as part of the "schema definition refactor" milestone.

One idea I had for this that could be both explicit and reduce duplication is something like:

interface :foo do
  field :bar, :integer do
    resolve fn _, _ -> {:ok, 1} end
  end
end

object :baz do
  interfaces [foo: [import_resolvers: true]]
end

object :spam do
  interface :foo
end

In this case, spam would still raise as it doesn't resolve the field. baz does resolve since it has specified import_resolvers: true. Other options for this include import_fields.

I would also like to point out that something similar could be relevant for documentation:

interface :foo do
  @desc """
  The bar field
  """
  field :bar, :integer do
    resolve fn _, _ -> {:ok, 1} end
  end
end

object :baz do
  interfaces [foo: [import_resolvers: true]]
end

This would result in the bar field being documented with the documentation from the interface. Currently with the above, the bar field would not be documented.

Has there been any update on this issue? I would dearly love the default concept mentioned by @michalmuskala to be implemented. I've got a few places in my code where an abstract concept is modelled (like a Company, say) and then concrete types are conceptually derived (like a Supplier and a Manufacturer) and I need to share resolvers with all the different Company types whilst still using specific fields for Supplier and Manufacturer.

@ratbag98 it's slated to be in 1.5. Schema features are frozen on 1.4 at the moment since all the underlying mechanisms are changing in 1.5.

A little pattern that works pretty well for me to address this for now @ratbag98 is to define an abstract_company object with all the fields & resolvers you want to share and then in your supplier and manufacturer, you import the fields from abstract_company.

FYI this works in 1.5 by using import_fields $INTERFACE

defmodule Foo do
  use Absinthe.Schema

  interface :named do
    field :name, :string do
      resolve fn parent, _, _ -> {:ok, parent.name} end
    end

    resolve_type fn _, _ ->
      :user
    end
  end

  object :user do
    interfaces [:named]
    import_fields :named
  end

  query do

  end
end
Was this page helpful?
0 / 5 - 0 ratings

Related issues

rawkode picture rawkode  路  4Comments

boxxxie picture boxxxie  路  5Comments

safwank picture safwank  路  4Comments

bruce picture bruce  路  4Comments

maennchen picture maennchen  路  6Comments