Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/yuki24/artemis

Ruby GraphQL client on Rails that actually makes you more productive
https://github.com/yuki24/artemis

graphql graphql-client http2 rails ruby

Last synced: 27 days ago
JSON representation

Ruby GraphQL client on Rails that actually makes you more productive

Awesome Lists containing this project

README

        

# Artemis [![build](https://github.com/yuki24/artemis/actions/workflows/ruby.yml/badge.svg)](https://github.com/yuki24/artemis/actions/workflows/ruby.yml) [![Gem Version](https://badge.fury.io/rb/artemis.svg)](https://rubygems.org/gems/artemis)

Artemis is a GraphQL client that is designed to fit well on Rails.

* **Convention over Configuration**: You'll never have to make trivial decisions or spend time on boring setup. Start
making a GraphQL request in literally 30s.
* **Performant by default**: You can't do wrong when it comes to performance. All GraphQL files are pre-loaded only
once in production and it'll never affect runtime performance. Comes with options that enable persistent connections
and even HTTP/2, the next-gen high-performance protocol.
* **First-class support for testing**: Testing and stubbing GraphQL requests couldn't be simpler. No need to add
external dependencies to test well.

Battle-tested at [Artsy](https://www.artsy.net)

![graphql-client vs Artemis](https://raw.githubusercontent.com/yuki24/artemis/master/banner.png "graphql-client vs Artemis")

## Getting started

Add this line to your application's Gemfile:

```ruby
gem 'artemis'
```

Once you run `bundle install` on your Rails app, run the install command:

```sh
$ rails g artemis:install artsy https://metaphysics-production.artsy.net/

# or if you need to specify the `--authorization` header:
$ rails g artemis:install github https://api.github.com/graphql --authorization 'token ...'
```

### Generating your first query

Artemis comes with a query generator. For exmaple, you could use the query generator to generate a query stub for `artist`:

```sh
$ rails g artemis:query artist
```

Then this will generate:

```graphql
# app/operations/artist.graphql
query($id: String!) {
artist(id: $id) {
# Add fields here...
}
}
```

Then you could call the class method that has the matching name `artist`:

```ruby
Artsy.artist(id: "pablo-picasso")
# => makes a GraphQL query that's in app/operations/artist.graphql
```

You can also specify a file name:

```sh
$ rails g artemis:query artist artist_details_on_artwork
# => generates app/operations/artist_details_on_artwork.graphql
```

Then you can make a query in `artist_details_on_artwork.graphql` with:

```ruby
Artsy.artist_details_on_artwork(id: "pablo-picasso")
```

## The convention

Artemis assumes that the files related to GraphQL are organized in a certain way. For example, a service that talks to Artsy's GraphQL API could have the following structure:

```
├──app/operations
│   ├── artsy
│   │   ├── _artist_fragment.graphql
│   │   ├── artwork.graphql
│   │   ├── artist.graphql
│   │   └── artists.graphql
│   └── artsy.rb
├──config/graphql.yml
├──test/fixtures/graphql
│  └── artsy
│  ├── artwork.yml
│  ├── artist.yml
│  └── artists.yml
└──vendor/graphql/schema/artsy.json
```

### Fragments
Fragments are defined in defined in a standard way in a file named `_artwork_fragment.graphql` with the standard convention:

```graphql
fragment on Artwork {
id,
name,
artist_id
# other artwork fields here
}
```

The way of calling an Artemis fragment on other queries or models is with a **Rails convention**. Let us suppose we have the Artist model and its corresponding artwork. The way of nesting or calling the artwork on the artist model would look like this:

```graphql
fragment on Artist {
id,
name,
artworks {
...Artsy::ArtworkFragment
}
}
```

Where `Artsy` is the name of the folder/module.

## Callbacks

You can use the `before_execute` callback to intercept outgoing requests and the `after_execute` callback to observe the
response. A common operation that's done in the `before_execute` hook is assigning a token to the header:

```ruby
class Artsy < Artemis::Client
before_execute do |document, operation_name, variables, context|
context[:headers] = {
Authorization: "token ..."
}
end
end
```

Here the `:headers` key is a special context type. The hash object assigned to the `context[:headers]` will be sent as
the HTTP headers of the request.

Another common thing when receiving a response is to check if there's any error in the response and throw and error
accordingly:

```ruby
class Artsy < Artemis::Client
after_execute do |data, errors, extensions|
raise "GraphQL error: #{errors.to_json}" if errors.present?
end
end
```

## Multi domain support

Services like Shopify provide
[a different endpoint per customer](https://shopify.dev/api/admin/graphql/reference#graphql-endpoint) (e.g.
`https://{shop}.myshopify.com`). In order to switch the endpoint on a per-request basis, you will have to use the
`:multi_domain` adapter. This is a wrapper adapter that relies on an actual HTTP adapter such as `:net_http` and
`:curb` so that e.g. it can maintain multiple connections for each endpoint if necessary. This could be configured
as shown below:

```yaml
default: &default
# Specify the :multi_domain adapter:
adapter: :multi_domain

# Other configurations such as `timeout` and `pool_size` are passed down to the underlying adapter:
timeout: 10
pool_size: 25

# Additional adapter-specific configurations could be configured as `adapter_options`:
adapter_options:
# Here you can configure the actual adapter to use. By default, it is set to :net_http. Available adapters are
# :net_http, :net_http_persistent, :curb, and :test. You can not nest the use of the `:multi_domain` adapter.
adapter: :net_http

development:
shopify:
<<: *default

...
```

Upon making a request you will also have to specify the `url` option:

```ruby
# Makes a request to https://myawesomeshop.myshopify.com:
Shopify.with_context(url: "https://myawesomeshop.myshopify.com").product(id: "...")
```

## Configuration

You can configure the GraphQL client using the following options. Those configurations are found in the
`config/graphql.yml`.

| Name | Required? | Default | Description |
| ------------- | --------- | ------------| ----------- |
| `adapter` | No | `:net_http` | The underlying client library that actually makes an HTTP request. See Adapters for available options.
| `pool_size` | No | 25 | The number of keep-alive connections. The `:net_http` adapter will ignore this option.
| `schema_path` | No | See above | The path to the GrapQL schema. Setting an empty value to this will force the client to download the schema upon the first request.
| `timeout` | No | 10 | HTTP timeout set for the adapter in seconds. This will be set to both `read_timeout` and `write_timeout` and there is no way to configure them with a different value as of writing (PRs welcome!)
| `url` | Yes | N/A | The URL for the GraphQL endpoint.

### Adapters

There are four adapter options available. Choose the adapter that best fits on your use case.

| Adapter | Protocol | Keep-alive | Performance | Dependencies |
| ---------------------- | ------------------------ | ----------- | ----------- | ------------ |
| `:curb` | HTTP/1.1, **HTTP/2** | **Yes** | **Fastest** | [`curb 0.9.6+`][curb]
[`libcurl 7.64.0+`][curl]
[`nghttp2 1.0.0+`][nghttp]
| `:net_http` (default) | HTTP/1.1 only | No | Slow | **None**
| `:net_http_persistent` | HTTP/1.1 only | **Yes** | **Fast** | [`net-http-persistent 3.0.0+`][nhp]
| `:multi_domain` | See [multi domain support](#multi-domain-support)
| `:test` | See [Testing](#testing)

#### Third-party adapters

This is a comminuty-maintained adapter. Want to add yours? Send us a pull request!

| Adapter | Description |
| ---------------------- | ------------|
| [`:net_http_hmac`](https://github.com/JanStevens/artemis-api-auth/tree/master) | provides a new Adapter for the Artemis GraphQL ruby client to support HMAC Authentication using [ApiAuth](https://github.com/mgomes/api_auth). |

#### Writing your own adapter

When the built-in adapters do not satisfy your needs, you may want to implement your own adapter. You could do so by following the steps below. Let's implement the [`:net_http_hmac`](https://github.com/JanStevens/artemis-api-auth/tree/master) adapter as an example.

1. Define `NetHttpHmacAdapter` under the `Artemis::Adapters` namespace and implement [the `#execute` method](https://github.com/github/graphql-client/blob/master/guides/remote-queries.md):

```ruby
# lib/artemis/adapters/net_http_hmac_adapter.rb
module Artemis::Adapters
class NetHttpHmacAdapter
def execute(document:, operation_name: nil, variables: {}, context: {})
# Makes an HTTP request for GraphQL query.
end
end
end
```

2. Load the adapter in `config/initializers/artemis.rb` (or any place that gets loaded before Rails runs initializers):

```ruby
require 'artemis/adapters/net_http_hmac_adapter'
```

3. Specify the adapter name in `config/graphql.yml`:

```yml
default: &default
adapter: :net_http_hmac
```

## Rake tasks

Artemis also adds a useful `rake graphql:schema:update` rake task that downloads the GraphQL schema using the
`Introspection` query.

### `graphql:schema:update`

Downloads and saves the GraphQL schema.

| Option Name | Description |
| ------------------ | ------------|
| `SERVICE` | Service name the schema is downloaded from.|
| `AUTHORIZATION` | HTTP `Authorization` header value used to download the schema with.|

#### Examples

```
$ rake graphql:schema:update
# => downloads schema from the service. fails if there are multiple services in config/graphql.yml.

$ rake graphql:schema:update SERVICE=github AUTHORIZATION="token ..."
# => downloads schema from the `github` service using the HTTP header "AUTHORIZATION: token ..."
```

## Testing

Given that you have `app/operations/artsy/artist.graphql` and fixture file for the `artist.yml`:

```yml
# test/fixtures/graphql/artist.yml:
leonardo_da_vinci:
data:
artist:
name: Leonardo da Vinci
birthday: 1452/04/15

yayoi_kusama:
data:
artist:
name: Yayoi Kusama
birthday: 1929/03/22
```

Then you can stub the request with the `stub_graphql` DSL:

```ruby
stub_graphql(Artsy, :artist, id: "yayoi-kusama").to_return(:yayoi_kusama)
stub_graphql(Artsy, :artist, id: "leonardo-da-vinci").to_return(:leonardo_da_vinci)

yayoi_kusama = Artsy.artist(id: "yayoi-kusama")
yayoi_kusama.data.artist.name # => "Yayoi Kusama"
yayoi_kusama.data.artist.birthday # => "1452/04/15"

da_vinci = Artsy.artist(id: "leonardo-da-vinci")
da_vinci.data.artist.name # => "Leonardo da Vinci"
da_vinci.data.artist.birthday # => "1452/04/15"
```

You can also use JSON instead of YAML. See [example fixtures](https://github.com/yuki24/artemis/tree/master/spec/fixtures/responses)
and [test cases](https://github.com/yuki24/artemis/blob/master/spec/test_helper_spec.rb#L16-L51).

### MiniTest

Setting up the test helper with Artemis is very easy and simple. Just add the following code to the
`test/test_helper.rb` in your app:

```ruby
# spec/test_helper.rb
require 'artemis/test_helper'

class ActiveSupport::TestCase
setup do
graphql_requests.clear
graphql_responses.clear
end
end
```

### RSpec

Artemis also comes with a script that wires up helper methods on Rspec. Because it is more common to use the `spec/`
directory to organize spec files in RSpec, the `config.artemis.fixture_path` config needs to point to
`spec/fixtures/graphql`. Other than that, it is very straightforward to set it up:

```ruby
# config/application.rb
config.artemis.fixture_path = 'spec/fixtures/graphql'
```

```ruby
# Add this to your spec/rails_helper.rb or spec_helper.rb if you don't have rails_helper.rb
require 'artemis/rspec'
```

## Development

After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).

## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/yuki24/artemis. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.

## License

The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).

## Code of Conduct

Everyone interacting in the Artemis project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/artemis/blob/master/CODE_OF_CONDUCT.md).

[curb]: https://rubygems.org/gems/curb
[curl]: https://curl.haxx.se/docs/http2.html
[nghttp]: https://nghttp2.org/
[nhp]: https://rubygems.org/gems/net-http-persistent