George Claghorn

Namespacing GraphQL classes in Rails apps

The GraphQL Ruby library encourages placing GraphQL classes such as type definitions under the app/graphql/ directory in Rails applications. Here’s an example from the GraphQL Ruby homepage:

# app/graphql/types/profile_type.rb
class Types::ProfileType < Types::BaseObject
  field :id, ID, null: false
  field :name, String, null: false
  field :avatar, Types::PhotoType
end

Because Rails treats all subdirectories of app as root directories for the purpose of autoloading, classes in app/graphql/types/ are nested in a top-level Types module. In the above example, app/graphql/types/profile_type.rb defines Types::ProfileType. But Types is too general a top-level namespace for GraphQL type definitions. The GraphQL schema class defined in app/graphql/schema.rb is named Schema, but it’s specifically a GraphQL schema, not a generic schema. It would be nice to nest GraphQL-specific modules and classes in a GraphQL-specific namespace.

The most obvious way to achieve this is to nest the files in an app/graphql subdirectory—for example, app/graphql/application_graph/. Then we can nest GraphQL classes in the corresponding module (ApplicationGraph::Types::ProfileType and ApplicationGraph::Schema).

With some custom configuration, it’s possible to avoid the additional directory. First, in config/application.rb, remove app/graphql from the list of root directories:

module Foo
  class Application < Rails::Application
    # ...
    config.eager_load_paths -= [ "#{Rails.root}/app/graphql" ]
  end
end

This must be done in config/application.rb and not an initializer because Rails freezes config.eager_load_paths before initializers run.

Next, define the top-level ApplicationGraph module. (Use a different name if you prefer, but I recommend avoiding GraphQL since GraphQL Ruby already uses it.) Configure the autoloader to use ApplicationGraph to namespace constants defined in app/graphql/. At the end of config/application.rb, after the application definition:

module ApplicationGraph; end
Rails.autoloaders.main.push_dir Rails.root.join("app/graphql"), namespace: ApplicationGraph

This can be placed in an initializer instead, but it’s nice to keep all the GraphQL autoloading config together.

Finally, because we removed app/graphql/ from config.eager_load_paths, Rails doesn’t know to watch that directory for changes. Tell it to do so:

# config/application.rb
module Foo
  class Application < Rails::Application
    # ...
    config.watchable_dirs["#{Rails.root}/app/graphql"] = %w[ .rb ]
  end
end

All together, the changes look like this:

# config/application.rb
module Foo
  class Application < Rails::Application
    # ...

    config.eager_load_paths -= [ "#{Rails.root}/app/graphql" ]
    config.watchable_dirs["#{Rails.root}/app/graphql"] = %w[ .rb ]
  end
end

module ApplicationGraph; end
Rails.autoloaders.main.push_dir Rails.root.join("app/graphql"), namespace: ApplicationGraph

Now all files in app/graphql/ define classes under the top-level ApplicationGraph namespace: app/graphql/schema.rb defines ApplicationGraph::Schema, app/graphql/types/profile_type.rb defines ApplicationGraph::Types::ProfileType.