Backends and Models
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.
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.
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
look into aliasing.
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:
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.
This is a common Ruby example.
attr_accessor defines getters and
setters for our properties, and we assign those properties in the
A simple abstraction of the above is ActiveModel::Model:
dry-types is a dependency of Graphiti and successor to the popular Virtus.
3 Model Tips
If your Model does not have an
id property, using a random UUID is