Backends and Models
Overview
A Resource queries and persists to a Backend. It returns Models from the Backend response, which get serialized. In this way, it is an implementation of the Repository Pattern.
This is best illustrated in code. Let’s say we have a Backend class that accepts a hash of options to perform a query:
And a PORO Model that encapsulates those results, holding business logic:
Meaning that normally, our code would look something like:
Let’s wire-up that same code to a Resource:
As you see above, a scope can be anything from an
ActiveRecord::Relation
to a plain Ruby Hash. We want to adjust
something based on the request parameters and pass it to our backend.
From the raw backend results, we can instantiate Models. Note that we
always return the full scope at the end of each block.
Of course, most Backends have predictable and consistent interfaces. It would be a pain to manually write this code for every Resource. So instead we could build an Adapter to DRY this logic:
In summary: a Resource builds a query that is sent to a Backend. The backend executes the query, and we instantiate Models from the raw results.
ActiveRecord
From the ActiveRecord Guides:
Active Record was described by Martin Fowler in his book Patterns of Enterprise Application Architecture. In Active Record, objects carry both persistent data and behavior which operates on that data. Active Record takes the opinion that ensuring data access logic as part of the object will educate users of that object on how to write to and read from the database.
In other words, ActiveRecord combines a Backend and Model. Opinions on this vary, but Graphiti supports either approach: we can separate data and business layers, or combine them. See the ActiveRecord doppelgänger of the above at our Resource cheatsheet.
Model Requirements
The only hard requirement of a Model is that it responds to id
. We use
model.id
to determine uniqueness when rendering a JSONAPI response.
You will get incorrect results if model.id
is not unique.
Models should also respond to any readable attributes. Remember that:
Is the same as
If your Model does not respond to #name
, either pass a block to attribute
or
look into aliasing.
Validations
Graphiti will perform validations on your models during write requests, returning a JSONAPI-compliant errors payload. To get this functionality, your model must adhere to the ActiveModel::Validations API:
It is highly recommended to mix in:
Model Implementations
Because our default is ActiveRecord, it may be unclear what other Models look like. Graphiti itself has no opinion about your Model layer, but below are a few examples.
2.1 PORO
This is a common Ruby example. attr_accessor
defines getters and
setters for our properties, and we assign those properties in the
constructor:
2.2 ActiveModel::Model
A simple abstraction of the above is ActiveModel::Model:
2.3 Dry::Struct
dry-types is a dependency of Graphiti and successor to the popular Virtus.
3 Model Tips
ID-less Models
If your Model does not have an id
property, using a random UUID is
perfectly acceptable: