Embedded schemas and absinthe schema definition

I have an Ecto schema like this:

schema "item" do
        field :type, TypeEnum #some EctoEnum type
        field :name, :string
        field :data, :map
        #other fields omitted 
        timestamps()
    end

The :data field then utilizes a couple embedded schemas to display each specific “type”, similar to this:

  embedded_schema do
    field :specific_field, :string
    #other fields omitted
  end

I needed some form of hierarchy so that is how I developed it… I used this blog post as a reference: http://codeloveandboards.com/blog/2017/10/03/migrating-activerecord-sti-to-ecto/

My question is how do I define the Absinthe schema correctly?
I figured I could define an object for each specific type (and omit the “item” object definition):

@desc "Type 1"
object :type1 do
    field :type, :string
    field :name, :string
    field :specific_field, :string
end 

@desc "Type 2"
#...

But then how would I set up relationships that reference the “Item” object type?
Ie:

   @desc "An Item Response"
   object :item_response do
        field :id, non_null(:id)
        field :person, :person, resolve: dataloader(:people)
        field :casted_vote, non_null(:id)
        field :item, :item, resolve: dataloader(:items) #will need to add the item definition as well
    end

and then define the item object and leave out the :map field? That might work I think but what if I then want to get the actual full definition of the specific type?

Or maybe the question is, how can I use a :map type with absinthe?

Overall I just want to make sure I am headed down the right path here.

Thanks… I am new to this so please bare with me.

1 Like

The “map” type does not exists in Absinthe because GraphQL enforce a strict typing of key/value.

The closest thing you could do is define a JSON scalar in your type so the response object gets serialized without type validation. But, this is not considered “good pratice” in a GraphQL API.

A scalar is simply a way of parsing and serializing a struct. By defaut, Absinthe ships with string, integer, float, boolean and some extra ones like datetime, etc.

1 Like

Thanks for your reply. I read about how JSON isn’t recommended on an issue on the absinthe GitHub and that does make sense. Thank you for explaining why.

Given that, is there no way to “correctly” parse my map field to a concrete object in the Absinthe schema?

This was actually quite simple to resolve. You must use the dataloader/3 helper function and pass in the real assocation followed by the “type”. Then you can pattern match on the “:type” atom and then wire up the where clause.

field :type1, :type1, resolve: dataloader(:items, :item, args: %{type: "type1"})

then pattern match:

  def query(Item, params) do
    params
#other stuff that you want
    |> Map.to_list()
    |> Enum.reduce(Item, &apply_param/2)
  end

  def query(queryable, _params), do: queryable

  def apply_param({:type, type}, queryable),
    do: queryable |> where(type: ^type)

So in the end you define your absinthe objects that match your ecto embedded schemas and then tell dataloader to pull the data from the correct ecto association name (the ecto schema that contains your :map type).

3 Likes

I had a similar case and what I did was use Poison to encode the map column before returning it as a field with type string.