Absinthe: Fields with numbers in the name not working

Created on 27 Apr 2017  路  7Comments  路  Source: absinthe-graphql/absinthe

I have a work_phone_2 field in my database and in my Absinthe types:

object :contact do
  field :id, :id
  ...
  field :work_phone_2, :string
end

If I run a query against this:

query Contact {
  contact(id: 1) {
    id
    workPhone2
  }
}

I get an error saying:

Cannot query field "workPhone2" on type "Contact". Did you mean "workPhone2"?

If I change it to snake case (work_phone_2) in my query it works fine (workPhone_2 in the query works too). It appears it only fails when a number immediately follows an underscore. For example, if I set up field :work_phone_a2 then querying against workPhoneA2 works fine.

Most helpful comment

I had this problem too, so I overrode a a couple of the Absinthe.Adapter.LanguageConventions functions to stick a _ in before numbers that come after letters.

So in my app, a query for subRank1 to_internal_name()s back to sub_rank_1 matching up with the ecto schema etc.

defmodule MyApp.UnderscoreNumbersAdapter do
  @moduledoc """
  Implements behaviour or Absinthe.Adapter
  Inserts an underscore before numbers to match the strings generated by to_external_name()
  """

  use Absinthe.Adapter

  @ends_with_a_number ~r/([a-z])([0-9]+)/

  def to_internal_name(camelized_name, _role) do
    camelized_name
    |> Macro.underscore()
    |> underscore_number()
  end

  def underscore_number(snake_name) do
    if Regex.match?(@ends_with_a_number, snake_name) do
      Regex.replace(@ends_with_a_number, snake_name, "\\1\_\\2")
    else
      snake_name
    end
  end

end

This can be implemented by specifying the adapter when configuring Absinthe.
see docs

config :absinthe,
  adapter: MyApp.UnderscoreNumbersAdapter

If you're using Absinthe Plug you can configure it here (in router.ex)

    forward "/", Absinthe.Plug,
      schema: MyApp.UnderscoreNumbersAdapter,
      adapter: MyApp.UnderscoreNumbersAdapter

馃槚 This took a little while for me to work out so thought I'd mention it in case it helps anyone.

All 7 comments

The default adapter relies on Elixir's Macro.underscore function to translate document field names into schema field names.

iex(1)> "workPhone2" |> Macro.underscore
"work_phone2"

I would advise naming your fields in a way compatible with that function, or using a custom adapter.

Yeah, the issue here is really the error message.

Right, the problem is that this is really a schema error not a document error. This is essentially a duplicate of https://github.com/absinthe-graphql/absinthe/issues/298

Ahh, ok, that makes sense. The error is definitely confusing. And since Absinthe.Utils.camelize "work_phone_2", lower: true gave me workPhone2 I assumed it would reverse the same.

I'm stuck with the database column name unfortunately, but I can use a better name for my Absinthe type and just use a custom resolver with the correct column. Thanks!

Right yeah unfortunately neither underscore/1 nor camelize/1 are bijective, in that different values passed to camelize can give you the same value.

We've discussed having some kind of schema warning, but the challenge there is that right now adapters aren't placed on schemas, they're merely used at document run call sites.

I'm not sure I'd object however to having a schema specify the list of adapters it supports.

Closing as I don't think this gives us anything in addition to the previously linked issue.

I had this problem too, so I overrode a a couple of the Absinthe.Adapter.LanguageConventions functions to stick a _ in before numbers that come after letters.

So in my app, a query for subRank1 to_internal_name()s back to sub_rank_1 matching up with the ecto schema etc.

defmodule MyApp.UnderscoreNumbersAdapter do
  @moduledoc """
  Implements behaviour or Absinthe.Adapter
  Inserts an underscore before numbers to match the strings generated by to_external_name()
  """

  use Absinthe.Adapter

  @ends_with_a_number ~r/([a-z])([0-9]+)/

  def to_internal_name(camelized_name, _role) do
    camelized_name
    |> Macro.underscore()
    |> underscore_number()
  end

  def underscore_number(snake_name) do
    if Regex.match?(@ends_with_a_number, snake_name) do
      Regex.replace(@ends_with_a_number, snake_name, "\\1\_\\2")
    else
      snake_name
    end
  end

end

This can be implemented by specifying the adapter when configuring Absinthe.
see docs

config :absinthe,
  adapter: MyApp.UnderscoreNumbersAdapter

If you're using Absinthe Plug you can configure it here (in router.ex)

    forward "/", Absinthe.Plug,
      schema: MyApp.UnderscoreNumbersAdapter,
      adapter: MyApp.UnderscoreNumbersAdapter

馃槚 This took a little while for me to work out so thought I'd mention it in case it helps anyone.

Was this page helpful?
0 / 5 - 0 ratings