Pair programming with Github Copilot Chat
Tags: github, githubcopilot, ruby, graphql
Published: 2023-07-27 17:08:10

---
title: Pair programming with Github Copilot Chat
published: true
description: Pair programming with Github Copilot Chat
tags: github,copilot,ruby,graphql
# cover_image: https://images.unsplash.com/photo-1647166545674-ce28ce93bdca?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=3600
# Use a ratio of 100:42 for best results.
# published_at: 2023-07-27 10:05 -7
---

Original Post:
- https://www.samuraikun.dev/articles/pair-programming-with-github-copilot-chat

At 2023.7.21, Github Copilot Chat beta now available for every organization!!

{% embed https://github.blog/2023-07-20-github-copilot-chat-beta-now-available-for-every-organization/ %}

VSCode extensions are also available.
https://marketplace.visualstudio.com/items?itemName=GitHub.copilot-chat

So, I tried pair programming with Github copilot chat in some specific examples.

## Example: Refactoring GraphQL queries by every resolver

The following code is a GraphQL query implementation to get the data for a simple blog application by using  `graphql-ruby`.

ex. `query_type.rb`

```ruby
module Types
  class QueryType < Types::BaseObject
    include GraphQL::Types::Relay::HasNodeField
    include GraphQL::Types::Relay::HasNodesField

    field :user, Types::UserType, null: true do
      description "Find User by ID"
      argument :id, ID, required: true
    end
    def user(id:)
      User.find(id)
    end

    field :users, [Types::UserType], null: false do
      description "Fetch All Users"
    end
    def users(page: nil, items: nil)
      User.all
    end

    field :article, Types::ArticleType, null: true do
      argument :id, ID, required: true
    end
    def article(id:)
      Article.find(id)
    end

    field :articles, [Types::ArticleType], null: true do
      argument :user_id, ID, required: false
    end
    def articles(user_id: nil)
      if user_id
        Article.where(user_id: user_id)
      else
        Article.all
      end
    end

    field :comments, [Types::CommentType], null: false do
      argument :id, ID, required: false
      argument :user_id, ID, required: false
      argument :article_id, ID, required: false
    end
    def comments(id: nil, user_id: nil, article_id: nil)
      scope = Comment.all
      scope = scope.where(id: id) if id
      scope = scope.where(user_id: user_id) if user_id
      scope = scope.where(article_id: article_id) if article_id
      scope
    end

    field :tags, [Types::TagType], null: false do
      argument :name, String, required: false
    end
    def tags(name: nil)
      if name
        Tag.where("name like ?", "#{name}%")
      else
        Tag.all
      end
    end
  end
end
```

The problem with this implementation is that `query_type.rb` contains all the query implementation.

So, I will try to refactor it using Github copilot chat to separate the query implementation for each Resolver class.

### First prompt: “Refactor the selected code”

First, I selected all implementations of `query_type.rb`.

And then, I asked Github Copilot the way.

![Github Copilot Chat](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yfz5t9knm91wnaiizfj3.png)

Oops…, this prompt was too abstract.

### Second prompt: “Refactoring the GraphQL query implemented in `graphql-ruby` to separate each Resolver”

Github Copilot:

> “Sure, here's a possible refactored version of the code that separates each resolver into its own method:”
> 

```ruby
module Types
  class QueryType < Types::BaseObject
    include GraphQL::Types::Relay::HasNodeField
    include GraphQL::Types::Relay::HasNodesField

    field :user, Types::UserType, null: true do
      description "Find User by ID"
      argument :id, ID, required: true
    end
    def user(id:)
      User.find(id)
    end

    field :users, [Types::UserType], null: false do
      description "Fetch All Users"
    end
    def users
      User.all
    end

    field :article, Types::ArticleType, null: true do
      argument :id, ID, required: true
    end
    def article(id:)
      Article.find(id)
    end

    field :articles, [Types::ArticleType], null: true do
      argument :user_id, ID, required: false
    end
    def articles(user_id: nil)
      if user_id
        Article.where(user_id: user_id)
      else
        Article.all
      end
    end

    field :comments, [Types::CommentType], null: false do
      argument :id, ID, required: false
      argument :user_id, ID, required: false
      argument :article_id, ID, required: false
    end
    def comments(id: nil, user_id: nil, article_id: nil)
      scope = Comment.all
      scope = scope.where(id: id) if id
      scope = scope.where(user_id: user_id) if user_id
      scope = scope.where(article_id: article_id) if article_id
      scope
    end

    field :tags, [Types::TagType], null: false do
      argument :name, String, required: false
    end
    def tags(name: nil)
      if name
        Tag.where("name like ?", "#{name}%")
      else
        Tag.all
      end
    end
  end
end
```

Hmm….., It is the same as the previous implementation.😓

### Third prompt: “No! Now, `query_type.rb` includes all queries, but I want to divide resolver class.”

Github Copilot:

> I apologize for misunderstanding your previous request. To separate each resolver into its own class, you can create a new file for each resolver and define a class that inherits from `GraphQL::Schema::Resolver`. Here's an example of how you could refactor the code:
> 

> Create a new file called `user_resolver.rb` and define a class called `UserResolver`:
> 

```ruby
class UserResolver < GraphQL::Schema::Resolver
  description "Find User by ID"

  argument :id, ID, required: true

  type Types::UserType, null: true

  def resolve(id:)
    User.find(id)
  end
end
```

> Create a new file called `users_resolver.rb` and define a class called `UsersResolver`:
> 

```ruby
class UsersResolver < GraphQL::Schema::Resolver
  description "Fetch All Users"

  type [Types::UserType], null: false

  def resolve
    User.all
  end
end
```

> Create a new file called `article_resolver.rb` and define a class called `ArticleResolver`:
> 

```ruby
class ArticleResolver < GraphQL::Schema::Resolver
  argument :id, ID, required: true

  type Types::ArticleType, null: true

  def resolve(id:)
    Article.find(id)
  end
end
```

And the rest of the query output the same pattern of implementation.

> Finally, in your `QueryType` class, replace each field with a call to the corresponding resolver class:
> 

```ruby
module Types
  class QueryType < Types::BaseObject
    include GraphQL::Types::Relay::HasNodeField
    include GraphQL::Types::Relay::HasNodesField

    field :user, resolver: UserResolver
    field :users, resolver: UsersResolver
    field :article, resolver: ArticleResolver
    field :articles, resolver: ArticlesResolver
    field :comments, resolver: CommentsResolver
    field :tags, resolver: TagsResolver
  end
end
```

Sounds good!

Github copilot presented an implementation that separates each Resolver by class.

### Forth prompt: “What directory structure should the resolver class have?”

Github Copilot:

> The directory structure for resolver classes is flexible and can be organized in a way that makes sense for your application. However, a common convention is to create a `resolvers` directory under the `app/graphql` directory, and then create a separate file for each resolver class within the `resolvers` directory. Here's an example directory structure:
> 

```bash
app/graphql/
├── resolvers/
│   ├── article_resolver.rb
│   ├── articles_resolver.rb
│   ├── comment_resolver.rb
│   ├── comments_resolver.rb
│   ├── tag_resolver.rb
│   └── tags_resolver.rb
├── types/
│   ├── article_type.rb
│   ├── comment_type.rb
│   ├── mutation_type.rb
│   ├── query_type.rb
│   └── tag_type.rb
└── schema.rb
```

> In this example, each resolver class is defined in its own file within the resolvers directory. The types directory contains the definition of each GraphQL type used in the schema, and schema.rb contains the definition of the GraphQL schema itself.
> 

Almost perfect!!

It seems to me that we could adopt the improvements suggested by Github Copilot.

Github Copilot Chat makes it easy to add implementations.

Like this👇
![Insert code of Github Copilot Chat answered](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yh3g6utn0jf22x8gcmzj.gif)

### Final prompt: “Output a Request spec with RSpec using each Resolver.”

Github Copilot:

> Sure, here's an example of how you could write a request spec using RSpec for each resolver:
> 

```ruby
require 'rails_helper'

RSpec.describe "GraphQL queries", type: :request do
  describe "user query" do
    let(:user) { create(:user) }
    let(:query) do
      <<~GQL
        query {
          user(id: #{user.id}) {
            id
            name
            email
          }
        }
      GQL
    end

    it "returns the user with the specified ID" do
      post '/graphql', params: { query: query }
      json = JSON.parse(response.body)
      data = json['data']['user']
      expect(data).to include(
        'id' => user.id.to_s,
        'name' => user.name,
        'email' => user.email
      )
    end
  end

  describe "users query" do
    let!(:users) { create_list(:user, 3) }
    let(:query) do
      <<~GQL
        query {
          users {
            id
            name
            email
          }
        }
      GQL
    end

    it "returns all users" do
      post '/graphql', params: { query: query }
      json = JSON.parse(response.body)
      data = json['data']['users']
      expect(data.size).to eq(users.size)
      data.each_with_index do |user_data, i|
        expect(user_data).to include(
          'id' => users[i].id.to_s,
          'name' => users[i].name,
          'email' => users[i].email
        )
      end
    end
  end

  describe "article query" do
    let(:article) { create(:article) }
    let(:query) do
      <<~GQL
        query {
          article(id: #{article.id}) {
            id
            title
            body
          }
        }
      GQL
    end

    it "returns the article with the specified ID" do
      post '/graphql', params: { query: query }
      json = JSON.parse(response.body)
      data = json['data']['article']
      expect(data).to include(
        'id' => article.id.to_s,
        'title' => article.title,
        'body' => article.body
      )
    end
  end

  describe "articles query" do
    let!(:articles) { create_list(:article, 3) }
    let(:query) do
      <<~GQL
        query {
          articles {
            id
            title
            body
          }
        }
      GQL
    end

    it "returns all articles" do
      post '/graphql', params: { query: query }
      json = JSON.parse(response.body)
      data = json['data']['articles']
      expect(data.size).to eq(articles.size)
      data.each_with_index do |article_data, i|
        expect(article_data).to include(
          'id' => articles[i].id.to_s,
          'title' => articles[i].title,
          'body' => articles[i].body
        )
      end
    end

    it "returns articles for a specific user" do
      user = create(:user)
      articles_for_user = create_list(:article, 2, user: user)
      post '/graphql', params: { query: query, variables: { userId: user.id } }
      json = JSON.parse(response.body)
      data = json['data']['articles']
      expect(data.size).to eq(articles_for_user.size)
      data.each_with_index do |article_data, i|
        expect(article_data).to include(
          'id' => articles_for_user[i].id.to_s,
          'title' => articles_for_user[i].title,
          'body' => articles_for_user[i].body
        )
      end
    end
  end

  describe "comments query" do
    let!(:comments) { create_list(:comment, 3) }
    let(:query) do
      <<~GQL
        query {
          comments {
            id
            body
          }
        }
      GQL
    end

    it "returns all comments" do
      post '/graphql', params: { query: query }
      json = JSON.parse(response.body)
      data = json['data']['comments']
      expect(data.size).to eq(comments.size)
      data.each_with_index do |comment_data, i|
        expect(comment_data).to include(
          'id' => comments[i].id.to_s,
          'body' => comments[i].body
        )
      end
    end

    it "returns comments for a specific user" do
      user = create(:user)
      comments_for_user = create_list(:comment, 2, user: user)
      post '/graphql', params: { query: query, variables: { userId: user.id } }
      json = JSON.parse(response.body)
      data = json['data']['comments']
      expect(data.size).to eq(comments_for_user.size)
      data.each_with_index do |comment_data, i|
        expect(comment_data).to include(
          'id' => comments_for_user[i].id.to_s,
          'body' => comments_for_user[i].body
        )
      end
    end

    it "returns comments for a specific article" do
      article = create(:article)
      comments_for_article = create_list(:comment, 2, article: article)
      post '/graphql', params: { query: query, variables: { articleId: article.id } }
      json = JSON.parse(response.body)
      data = json['data']['comments']
      expect(data.size).to eq(comments_for_article.size)
      data.each_with_index do |comment_data, i|
        expect(comment_data).to include(
          'id' => comments_for_article[i].id.to_s,
          'body' => comments_for_article[i].body
        )
      end
    end
  end

  describe "tags query" do
    let!(:tags) { create_list(:tag, 3) }
    let(:query) do
      <<~GQL
        query {
          tags {
            id
            name
          }
        }
      GQL
    end

    it "returns all tags" do
      post '/graphql', params: { query: query }
      json = JSON.parse(response.body)
      data = json['data']['tags']
      expect(data.size).to eq(tags.size)
      data.each_with_index do |tag_data, i|
        expect(tag_data).to include(
          'id' => tags[i].id.to_s,
          'name' => tags[i].name
        )
      end
    end

    it "returns tags with a specific name" do
      tag = create(:tag, name: "ruby")
      post '/graphql', params: { query: query, variables: { name: "ru" } }
      json = JSON.parse(response.body)
      data = json['data']['tags']
      expect(data.size).to eq(1)
      expect(data[0]).to include(
        'id' => tag.id.to_s,
        'name' => tag.name
      )
    end
  end
end
```

This is amazing.😲

This test seems appropriate!

## Summary

Features I've found using Github Copilot Chat

- Github Copilot Chat gives higher quality answers than I expected
- Response time to output answers is also fast
- Easy to add implementations to the editor on VSCode
- It suggests potential next questions