ruby-on-railsrubyenumsgraphqlgraphql-ruby

Dealing with GraphQL Enum values in business code


I have a GraphQL enum like:

class MediaFilterType < Types::BaseEnum
  value "TWITTER", value: :twitter
  value "FACEBOOK", value: :facebook
  value "YOUTUBE", value: :youtube
end

And a field that receives an array of this type or nil. Actually when we receive nil, we should pass along that we are going to use all available values in the enum:

def dashboard(media_types: nil)
  if media_type.nil?
    # Here is the problem below
    media_types = MediaFilterType.values.values.map(&:value)
  end
  ...
  DashboardQuery.new(media_types).call
end

So I have to do this conditional sanitization for every field just like dashboard field. Not a big deal, except for the duplication. But I think this is a responsibility that should be inside the Query (there is a collaborator that can do that sanitizing inside the Query object). But I think it would be worse to extract the values from the GQL enum type inside a business object. Actually don't even know if I should be extracting these enum values anyway.

Is it good practice to extract values from the GQL type like that?

Appreciate any help.


Solution

  • There is no definitive 'right' answer here. The issue is, graphql-ruby requires a fair bit of duplication in many other cases - think of your models and types. I would not be too scared of a tiny bit of duplication. However...

    But I think it would be worse to extract the values from the GQL enum type inside a business object

    Depends. If the values are only used within the API and literally nowhere else your implementation is already as good as it gets.

    Should I create an Enum with the same values in my business layer?

    That is an option. If you forgo documentation for the GraphQL enum types you can get rid of any duplication fairly elegantly. Assuming you add your media filters as enums on some model:

    class Dashboard
      enum media_types: [:twitter :facebook :youtube]
    end
    

    You can then do:

    class MediaFilterType < Types::BaseEnum
      Dashboard.media_types.each { |media_type| value(media_type.upcase, value: media_type.to_sym)}
    end
    

    And for your field:

    types = media_types || Dashboard.media_types
    DashboardQuery.new(media_types).call
    
    

    Your model enum now serves as a single source of truth.

    However, it is not uncommon that your API diverges a bit from your models with time, so in that case there really isn't a nice way around duplicating at least some things. Just to keep that in mind.