GraphqlMigrateExecution

Test Gem Version

A command-line development tool to update your Ruby source code to support GraphQL::Execution::Next, then clean up unused legacy configs after you don’t need them anymore.

Install

bundle add graphql_migrate_execution

Use

Usage: graphql_migrate_execution glob [options]

A development tool for adopting GraphQL-Ruby's new runtime module, GraphQL::Execution::Next

Inspect the files matched by `glob` and ...

- (default) print an analysis result for what can be updated
- `--migrate`: update files with new configuration
- `--cleanup`: remove legacy configuration and instance methods

Options:

        --migrate                    Update the files with future-compatible configuration
        --cleanup                    Remove resolver instance methods for GraphQL-Ruby's old runtime
        --dry-run                    Don't actually modify files
        --implicit [MODE]            Handle implicit field resolution this way (ignore / hash_key / hash_key_string)

Supported Field Resolution Patterns

Check out the docs for refactors implemented by this tool:

Unsupported Field Resolution Patterns

Here are a few fields in my app that this tool didn’t handle automatically, along with my manual migrations:

This resolver called arbitrary code after using Dataloader:

“‘ruby field :is_locked_to_viewer, Boolean, null: false

def is_locked_to_viewer status = dataload(Sources::GrowthTaskStatusForUserSource, context, object) status == :LOCKED end “‘

I could have handled this by refactoring the dataload call to return true|false. Then it could have been auto-migrated. Instead, I migrated it like this:

“‘ruby field :is_locked_to_viewer, Boolean, null: false, resolve_batch: true

def self.is_locked_to_viewer(objects, context) statuses = context.dataload_all(Sources::GrowthTaskStatusForUserSource, context, objects) statuses.map { |s| s == :LOCKED } end

def is_locked_to_viewer self.class.is_locked_to_viewer([ object ], context).first end “‘

This field only called dataloader in some cases:

“‘ruby field :viewer_growth_task_submission, GrowthTaskSubmissionType

def viewer_growth_task_submission if object.frequency.present? # TODO should not include a recurring submission whose duration has passed nil else context.dataloader.with(Sources::GrowthTaskForViewerSource, context).request(object.id) end end “‘

It could have been auto-migrated if I made two refactors:

But I didn’t do that. Instead, I migrated it manually:

“‘ruby field :viewer_growth_task_submission, GrowthTaskSubmissionType, resolve_batch: true

def self.viewer_growth_task_submission(objects, context) requests = objects.map do |object| if object.frequency.present? # TODO should not include a recurring submission whose duration has passed nil else context.dataloader.with(Sources::GrowthTaskForViewerSource, context).request(object.id) end end requests.map { |l| l&.load } end

def viewer_growth_task_submission self.class.viewer_growth_task_submission([ object ], context).first end “‘

The tool just gives up when it sees calls on self. It didn’t handle this:

“‘ruby field :current_user, Types::UserType

def current_user context end

field :unread_notification_count, Integer, null: false

def unread_notification_count # vvvvvvvvv Calls the resolver method above current_user ? current_user.notification_events.unread.count : 0 end “‘

I migrated it manually:

“‘ruby field :unread_notification_count, Integer, null: false, resolve_static: true

def self.unread_notification_count(context) if (cu = current_user(context)) cu.notification_events.unread.count else 0 end end

def unread_notification_count self.class.unread_notification_count(context) end “‘

The tool’s heavy-handed Ruby source generation botched this:

ruby field :growth_levels, Types::GrowthLevelType.connection_type, null: false, resolve_each: true def growth_levels; object.growth_levels.by_sequence; end;

This tool could be improved to properly handle single-line methods – open an issue if you need this.

Develop

bundle exec rake test # TEST=test/...

TODO