SkillAgentSearch skills...

Subjoin

Ruby library for parsing JSON-API

Install / Use

/learn @seanredmond/Subjoin
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Subjoin

A practical wrapper for JSON-API interactions.

Installation

Add this line to your application's Gemfile:

gem 'subjoin'

And then execute:

$ bundle

Or install it yourself as:

$ gem install subjoin

Documentation

For full documentation run

$ yardoc
$ yard server

Then load http://localhost:8808 in your browser

Usage

Document

Everything starts with a document, specifically a Subjoin::Document -- the equivalent of a JSON-API document which you can create with a URI:

require "subjoin"
doc = Subjoin::Document.new(URI("http://example.com/articles"))

(all examples here based on examples in the JSON-API documentation)

Note that you must pass a URI object. A string would be interpreted as a JSON-API type.

A Subjoin::Document probably has "primary data" which, if present is an Array of Subjoin::Resource objects:

doc.has_data?  # true if there is primary data
  => true
doc.data       # Array of Subjoin::Resource objects
doc.data.first # One resource

The data member of a JSON-API document can be either a single resource object or an array of resource objects. Subjoin::Document#data always returns an Array. In a document with a single resource object, the Array will have one element.

You can access all the other members of the top-level document (all the objects returned are covered below):

doc.links    # Hash of Link objects
doc.included # Inclusions object
doc.meta     # Meta object
doc.jsonapi  # JsonApi object

There are, in addition, methods to test whether any of the above members are present:

doc.has_data?
doc.has_links?
doc.has_included?
doc.has_meta?
doc.has_jsonapi?

Resources

Every Subjoin::Resource has a type and id. The JSON response:

{
  "data": {
  "type": "articles",
  "id": "1",
  "attributes": {
    "title": "JSON API paints my bikeshed!"
  },
  ...
}

would correspond to:

article = doc.data.first
article.type
  => "articles"
article.id
  => "1"

The attributes of a Subjoin::Resource object, or any object that includes Subjoin::Attributable, can be accessed like hash on the object itself:

article["title"]
  => "JSON API paints my bikeshed!"

You can also get the entire attributes Hash as Subjoin::Attributable#attributes:

article.attributes # Hash
article.attributes.keys
  => ["title"]
article.attributes["title"]
=> "JSON API paints my bikeshed!"

The other expected members of a resource object are available. The objects returned by these methods are all explained below:

article.links         # Hash of Link objects
article.relationships # Array of Relationship objects
article.meta          # Meta object

As with Subjoin::Document, there are methods to see if any of the above are available

article.has_links?
article.has_meta?

Links

Subjoin::Document, Subjoin::Resource, and Subjoin::Relationship can all have links. They all have the Subjoin::Linkable#links method which returns a Hash of Subjoin::Link objects:

article.links.keys
  => ["self"]
article.links["self"].href.to_s
  => "http://example.com/articles/1"

JSON-API allows for two link object formats. One simply has a link

"links": {
  "self": "http://example.com/articles/1"
}

and one has an href attribute and meta object:

"links": {
  "related": {
    "href": "http://example.com/articles/1/comments",
    "meta": {
      "count": 10
    }
  }
}

Subjoin treats either variation like the latter:

article.links["self"].href.to_s
  => "http://example.com/articles/1"
article.links["self"].has_meta?
  => false
article.links["self"].meta?
  => nil

article.links["related"].href.to_s
  => "http://example.com/articles/1/comments"
article.links["related"].has_meta?
  => true
article.links["related"].meta["count"]
  => 10

Note that the href is always returned as a URI object. If you have a Subjoin::Link you can get the corresponding Subjoin::Document:

article.links["related"].get # Same thing as Subjoin::Document.new
                             # with the URL

Resource Identifiers

Before getting to relationships, we should take a minute to look at resource identifiers. Above, we saw that every Subjoin::Resource has a type and id.

article.type # "articles"
article.id   # "1"

Though the above attributes exist individually, these two attributes work together as a compound key and are, in fact put together in Subjoin as a Subjoin::Identifier object:

article.identifier      # Identifier object
article.identifier.type # "articles"
article.identifier.id   # "1"

Subjoin::Identifier objects are used for equality: two Subjoin::Resource objects are considered equal if they have equal Identifers:

article1 == article2                                    # Really tests...
article1.identifier == article2.identifier              # Really tests...
article1.identifier.type == article2.identifier.type &&
    article1.identifer.id == article2.identifier.id

More importantly, identifiers occur in Relationship objects as pointers to other resources. These pointers are called linkages:

article.relationships.author.linkages # Array of Identifier objects

They may have, optionally, a meta attribute as well, and meta attributes are ignored in tests for equality.

Relationships, Linkages, Included Resources

Okay, now we can get to how you'll really use JSON-API resources and why you would JSON-API over other options: resource linking and included resources.

In many RESTful APIs, resources have embedded child resources which is, in my experience, a principle source of the bikeshedding arguments that JSON-API tries to avoid ("should X be a child of Y, or Y a child of X?" "How should the X response be different when it is achild of another resource?"). Instead of nesting and embedding, in JSON-API resources may have relationships to other resources.

article.relationships        # A Hash of Subjoin::Relationship objects
article.relationships.keys
  => ["author", "comments"]

This much tells you that an "article" can have an "author" and "comments". In Subjoin, relationships are instantiated as Subjoin::Relationship objects whose two important properties are links and linkages. Subjoin::Relationship are Subjoin::Linkable so links works as it does in other objects.

author = article.relationships["author"]    # Relationship object
author.links.keys
  => ["self", "related"]
author.links["related"].to_s
  => "http://example.com/articles/1/author"
author.links["related"].get                 # Get a new Document

Alongside links, linkages give you resource identifiers for the related resources. while the "comments" link tells us how to get a document with all the related comments:

comments.links["self"].to_s
  => "http://example.com/articles/1/relationships/comments"

The corresponding linkages returns an Array of Subjoin::Identifier that are pointers to specific resources:

comments = article.relationships["comments"]
comments.linkages.count
  => 2

This tells us that there are two related comments. If we look at one, we can get the type and id:

comments.linkages[0].type
  => "comments"
comments.linkages[0].id
  => "5"

So far so good, but now what? Inclusion

With Subjoin, you can request that these related resources be included in the document, one of three ways:

# URI parameters
doc = Subjoin::Document.new(URI("http://example.com/articles/1?include=author,comments"))

# Parameters Hash with a string
doc = Subjoin::Document.new(URI("http://example.com/article/1s", {"include" => "author,comments"}))

# Parameters Hash with an array of strings
doc = Subjoin::Document.new(URI("http://example.com/articles/1", {"include" => ["author" ,"comments"]}))

All three are equivalent. The array of strings version works especially well with relationship keys

doc2 = Subjoin::Document.new(URI("http://example.com/articles/1", {"include" => articles.relationships.keys}))

When a document is requested with included resources, they can be accessed via included

# Get the document
doc = Subjoin::Document.new(URI("http://example.com/articles/1", {"include" => ["author" ,"comments"]}))

# Get the article
article = doc.data.first

# Get the realted author identifier
auth_identifier = article.related["author"].linkages.first
auth_identifier.type
  => "people"
auth_identifier.id
  => "9"

# Get the embedded resource
doc.has_included?
  => true

# Look up included resource by identifier
author = doc.included[auth_identifier]

# Now we have access to the whole author resource
author.type
  => "people"
author.id
  => "9"
author["twitter"]
  => "dgeb"

If that sounds kind of complicated, it is. But you can...

Let Subjoin resolve the linkages for you

To make all this easier, Subjoin::Resource provides a rels method that does all this under the hood:

article.rels.keys
  => ["author", "comments"]
author = article.rels["author"] # Returns the author Resource
author["twitter"]
  => "dgeb"

Meta Information

[Meta information](http://json

Related Skills

View on GitHub
GitHub Stars12
CategoryDevelopment
Updated1y ago
Forks1

Languages

Ruby

Security Score

75/100

Audited on Mar 18, 2025

No findings