https://github.com/amcaplan/simple_jsonapi_client
An opinionated framework for building JSONAPI clients
https://github.com/amcaplan/simple_jsonapi_client
Last synced: about 1 year ago
JSON representation
An opinionated framework for building JSONAPI clients
- Host: GitHub
- URL: https://github.com/amcaplan/simple_jsonapi_client
- Owner: amcaplan
- License: mit
- Created: 2017-08-13T21:45:22.000Z (almost 9 years ago)
- Default Branch: master
- Last Pushed: 2023-01-19T04:50:49.000Z (over 3 years ago)
- Last Synced: 2025-03-23T19:49:16.196Z (over 1 year ago)
- Language: Ruby
- Homepage:
- Size: 119 KB
- Stars: 10
- Watchers: 3
- Forks: 9
- Open Issues: 10
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
[](https://travis-ci.org/amcaplan/simple_jsonapi_client)
[](https://badge.fury.io/rb/simple_jsonapi_client)
# What is `SimpleJSONAPIClient`?
`SimpleJSONAPIClient` is a framework for building Ruby clients for JSONAPI-compliant services.
# How do I use `SimpleJSONAPIClient`?
## Setup
First create models inheriting from `SimpleJSONAPIClient::Base`, and specifying a few details.
* `COLLECTION_URL` - the path to fetch the resource collection
* `INDIVIDUAL_URL` - the path to fetch an individual resource
* `TYPE` - the JSONAPI resource type to use when creating a new resource
* `attributes` - the names of attributes which can be found on the resource
* relationships - `has_one` and `has_many` define relationships, and take these arguments:
* relationship name (e.g., the `:goats` in `has_many :goats`)
* `class_name` to use when instantiating related objects
They should look like this:
```ruby
class Post < SimpleJSONAPIClient::Base
COLLECTION_URL = '/posts'
INDIVIDUAL_URL = '/posts/%{id}'
TYPE = 'posts'
attributes :title, :text
meta :copyright
has_one :author, class_name: 'Author'
has_many :comments, class_name: 'Comment'
end
class Author < SimpleJSONAPIClient::Base
COLLECTION_URL = '/authors'
INDIVIDUAL_URL = '/authors/%{id}'
TYPE = 'authors'
attributes :name
has_many :posts, class_name: 'Post'
has_many :comments, class_name: 'Comment'
end
class Comment < SimpleJSONAPIClient::Base
COLLECTION_URL = '/comments'
INDIVIDUAL_URL = '/comments/%{id}'
TYPE = 'comments'
attributes :text
has_one :post, class_name: 'Post'
has_one :author, class_name: 'Author'
end
```
If you have behavior you'd like to share across models, you may want to first create an abstract class inheriting from `SimpleJSONAPIClient::Base` and then have all your models inherit from that.
Next, create a [`Faraday`](https://github.com/lostisland/faraday) connection to handle the domain, authorization strategy, and anything else you need (making sure to include [JSON parsing middleware](https://github.com/lostisland/faraday_middleware/wiki/Parsing-responses)):
```ruby
def connection(token)
default_headers = {
'Accept' => 'application/vnd.api+json',
'Content-Type' => 'application/vnd.api+json',
'Authorization' => "token=#{token}"
}
@connection ||= Faraday.new(url: 'https://example.com', headers: default_headers) do |connection|
connection.request :json
connection.response :json, :content_type => /\bjson$/ # use middleware to parse JSON when response Content-Type is json
connection.adapter :net_http
end
end
```
Now you can start making requests!
## Fetching
### Laziness and `SimpleJSONAPIClient`
```ruby
Post.fetch_all(connection: connection)
=> #:each>
```
What's going on? `SimpleJSONAPIClient` tries to be as lazy as possible while still being convenient. So if you actually want to fetch everything, you'll be able to call `Array` methods and it will fetch the resource, paginating through all the results. If it's an endpoint with thousands of pages, you can use `Enumerator` methods like `#each` and it'll paginate through the results, fetching the next page when it runs out of objects.
Let's call `#to_a` to see a bit more detail.
```ruby
posts = Post.fetch_all(connection: connection).to_a
=> [# comments=#>,
# comments=#>]
```
Attributes are loaded immediately, but relationships are lazily instantiated. So if we dig a little bit further:
```ruby
posts.first.author
=> #
```
Nope, still lazy! However, once we start fetching details about the author, `SimpleJSONAPIClient` knows a request has to be made, and fills in the details:
```ruby
posts.first.author.id
=> "3"
posts.first.author
=> # comments=#>
```
We can read more easily by calling `#as_json`:
```ruby
posts.first.author.as_json
=> {
:data => {
:type => "authors",
:attributes => { :name => "Filbert" },
:relationships => {
:posts => {
:data => [{ :type => "posts", :id => "1" }]
},
:comments => { :data => [] }
}
}
}
```
### More About Fetching Capabilities
You can also explicitly fetch a single item:
```ruby
post = Post.fetch(connection: connection, url_opts: { id: 1 })
=> # comments=#>
```
`url_opts`, in all cases where you see them, are passed to the template Strings for `INDIVIDUAL_URL` and `COLLECTION_URL` in the model.
You've already seen that `id` and `relationships` are available; `attributes` and `meta` information also become methods on the object:
```ruby
post = Post.fetch(connection: connection, url_opts: { id: 1 })
post.title
=> "A Very Proper Post Title"
post.text
=> "I am absolutely incensed about something."
post.copyright
=> "Copyright 2017"
```
You can also use JSONAPI includes to reduce the number of requests that are necessary:
```ruby
post = JSONAPIAppClient::Post.fetch(connection: connection, url_opts: { id: 1 }, includes: ['author', 'comments.author'])
post.author # will not make another web request
post.comments.first.author # will not make another web request
```
`SimpleJSONAPIClient` will check the included records for related records you access through the returned model.
And finally, you can use JSONAPI-style filtering and pagination as well:
```ruby
# Given 3 authors...
JSONAPIAppClient::Author.fetch_all(connection: connection).to_a
=> [# comments=#>,
# comments=#>,
# comments=#>]
# Filtering by name
JSONAPIAppClient::Author.fetch_all(connection: connection, filter_opts: { name: 'Filbert' }).to_a
=> [# comments=#>]
# Just grabbing the last page
JSONAPIAppClient::Author.fetch_all(connection: connection, page_opts: { size: 1, number: 3 }).to_a
=> [# comments=#>]
# You can adjust the pagination strategy and SimpleJSONAPIClient will follow it, but return the same results
JSONAPIAppClient::Author.fetch_all(connection: connection, page_opts: { size: 1, number: 1 }).to_a
=> [# comments=#>,
# comments=#>,
# comments=#>]
```
## Creating
Creating records is available from the model class:
```ruby
post = Post.fetch(url_opts: { id: 1 }, connection: connection)
=> # comments=#>
author = Author.fetch(url_opts: { id: 1}, connection: connection)
=> # comments=#>
Comment.create(connection: connection, text: 'I adore your article!', post: post, author: author)
=> # author=#>
```
The created record is returned; if creation fails, a `SimpleJSONAPIClient::Errors::ApiError` is raised.
## Updating
If you want to update a record, you can do it from the model itself:
```ruby
post = Post.fetch(url_opts: { id: 1 }, connection: connection)
=> # comments=#>
[2] pry(main)> post.update(attributes: { text: 'foo' })
=> # comments=#>
```
If you have the ID of the record handy, you update straight from the model class without fetching the record first:
```ruby
Post.update(id: 1, url_opts: { id: 1 }, connection: connection, text: 'foo')
=> # comments=#>
```
## Deleting
You can delete a record from the model itself:
```ruby
post = Post.fetch(url_opts: { id: 1 }, connection: connection)
=> # comments=#>
post.delete
=> true
Post.fetch(url_opts: { id: 1 }, connection: connection)
=> nil
```
or from the class, if you have the ID:
```ruby
Post.delete(url_opts: { id: 1 }, connection: connection)
=> true
Post.fetch(url_opts: { id: 1 }, connection: connection)
=> nil
```
## Installation
Add this line to your application's Gemfile:
```ruby
gem 'simple_jsonapi_client'
```
And then execute:
$ bundle
Or install it yourself as:
$ gem install simple_jsonapi_client
## Development
You must have [Docker](https://docker.com) and [Docker Compose](https://docs.docker.com/compose/) installed to run the tests and use the built-in development utilities.
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment, and `bin/rails` to interact with the Rails app in `spec/jsonapi_app` that is provided for local development and testing.
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/amcaplan/simple_jsonapi_client. 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](http://opensource.org/licenses/MIT).
## Code of Conduct
Everyone interacting in the SimpleJsonapiClient project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/amcaplan/simple_jsonapi_client/blob/master/CODE_OF_CONDUCT.md).