Namespacing GraphQL constants in Rails apps
Update: I’ve extracted the configuration described in this post to a Rails plugin named graphql-rails-namespace. You should use the plugin instead, but I’ll leave this post up for posterity.
The GraphQL Ruby library encourages placing GraphQL modules and 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 constants 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 reload when Ruby files in that directory are changed. 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
.