Using Models
Reactive Record interacts with JSON APIs using predefined models and schemas, which represent resources in your application, and usually map to records in your application’s database. It’s similar to the purpose of creating models in a model-view-controller framework.
In this guide, we’ll cover creating your first model with a schema, and making an API request.
Contents
- A short note about resources
- Your first model
- Add schema attributes
- Configuring Routes
- Importing your model
- Model basics
- Making API requests
- Summary
A short note about resources
A model called Comment
will map to a /comments
API endpoint, which maps to a database table called comments
.
A model called Session
will map to a /sessions
API endpoint. Even though you may not have a sessions
database table, a session can still be thought of as a resource that is created or destroyed in your application. For instance, when a user logs in:
Session.create({ username: "generaltzo", password: "chickens-23" })
┗ POST
/sessions
201 Created
or logs out:
Session.destroy()
┗ DELETE
/sessions
204 No Content
Reactive Record can interact with the data as long as the resource is manipulated via a RESTful interface.
Your first model
For organization, you should keep your models together. Following the suggested file layout, place your models in a top-level /models
folder. Each file should contain only one model. The file name should be the singular name of the model itself in title case.
For your first model, we will be creating a Comment
model.
/models/Comment.js
import ReactiveRecord, { Model } from "reactiverecord"
class Comment extends Model {}
export default ReactiveRecord.model("Comment", Comment)
Add schema attributes
All we’ve done here is defined a single Comment
model with no attributes. We need to define a schema
. A schema
describes each of the attributes on a model instance. The schema
attributes are very much like descriptors for database columns.
/models/Comment.js
import ReactiveRecord, { Model } from "reactiverecord"
class Comment extends Model {
static schema = {
body: String,
email: String,
name: String,
postId: Number,
_timestamps: true
}
}
export default ReactiveRecord.model("Comment", Comment)
Schema attributes can be configured further than a primitive constructor. Instead, an object can be given which contains the following attributes:
Attribute | Value | Required |
---|---|---|
type | Array|Boolean|Date|Number|Object|String | Yes |
default | The default value of this field, which will be persisted on the next save | No |
labelText | When rendering a form, the text that should serve as this attributes label. By default, the label text will be the humanized translation of the attribute. remember_me becomes “Remember me”, but can be overridden here to “Remember me next time.” | No |
About Timestamps
Each of the schema attributes here are pretty self-explanatory, except _timestamps
. By writing _timestamps: true
, we’re telling Reactive Record to look for either created_at
and updated_at
attributes, or createdAt
and updatedAt
attributes in the JSON. This is to simplify the differences between default timestamp naming conventions from Rails or MongoDB. Whether your timestamp attributes are camel cased or snake cased, they will be accessible via model.createdAt
and model.updatedAt
.
About the ID
You’ll notice we did NOT define an id
or _id
attribute. This is the only implicit attribute in Reactive Record. There’s no need to define an id
attribute in your model schema. It will be assumed that every model has an id
or _id
attribute acting as its primary key, unless configured otherwise. Whether your JSON contains id
or _id
, it will be accessible via model.id
.
If your model uses another attribute as its primary key, you can configure this in the model schema:
class Order extends Model {
schema = {
_primaryKey: "uuid"
}
}
We’ve now defined a few attributes along with their types. The next step is importing your model to your application.
Models based on the current session
Often, you will need to define models which are identified by the current session only. For example: Cart
, CurrentUser
or Session
. These are models for which there exists only one resource per user consuming an API. The response is different depending on who is accessing the server. For these types of models, use the below configuration in your Model
definition.
class Cart extends Model {
static store = { singleton: true }
}
This will adjust the route configuration to no longer expect an ID in the resulting requests. For instance, calling: Cart.find()
will make a request to /cart
rather than /cart/:id
.
Configuring Routes
By default, each model contains five routes used to create, read, update and delete data. These routes are generated automatically based on the model’s name, but are highly customizable. For instance, for a model called User
, the following routes would be automatically generated:
Action | Route | Description |
---|---|---|
index | /users | Used to GET a list of users |
create | /users | Used when making a POST request to create a user |
show | /users/:id | Used to GET a single user |
update | /users/:id | Used when making a PUT request to update a user |
destroy | /users/:id | Used when making a DELETE request to destroy a user |
Route Tokens
Each route generated follows a template (:prefix/:modelname/:id
) which contains tokens. The tokens within the template are interpolated at the time the route is used. The default tokens are :prefix
, :modelname
and :id
.
The :prefix
token can be defined by setting the API prefix. Doing so will prefix all API requests across all models.
ReactiveRecord.setAPI({ prefix: "/api/v1" })
The :modelname
token is the pluralized and dasherized form of the model name. For instance, a model called CurrentUser
will have current-user
as its generated :modelname
. You can change the dashes to underscores across all models by changing the API delimiter.
/**
* Acceptable values:
* "_"
* "underscore"
* "underscores"
*/
ReactiveRecord.setAPI({ delimiter: "_" })
The :id
token will only be present on a member route, and will only be called :id
if id
is the primary key of the model. If the primary key is configured as uuid
, the token :uuid
will automatically be present in the routes in place of :id
.
More tokens can be used for more sophisticated routes. When using a custom token, be sure this attribute is present at the time the API request is made. Consider the following example:
class Invoices extends Model {
static routes = {
show: "/company/:company_id/invoices/:id"
}
}
Invoice.find(4456, { company_id: 22 })
┗ GET
/company/22/invoices/4456
200 OK
Disabling Routes
Sometimes it’s helpful to exclude certain routes from the model. This doesn’t prevent the route from being accessed, but Reactive Record will throw an error if a disabled route is accessed. You can configure this in the model’s route settings. The following configurations are each acceptable:
class CurrentUser extends Model {
static routes = {
only: "show",
show: "/me"
}
}
class Session extends Model {
static routes = {
except: "index"
}
}
class Note extends Model {
static routes = {
only: ["create", "show"]
}
}
class BankAccount extends Model {
static routes = {
except: ["create", "update", "destroy"]
}
}
Importing your model
The location to import your models for your application is just before the Reactive Record reducer is built. We’ll import them to the top-level reducer.js
file we created in the Getting Started guide.
reducer.js
import ReactiveRecord, { reducer } from "reactiverecord"
import "models/Comment"
/* import more models here before the call to the reducer function */
export default reducer.call(ReactiveRecord)
From now on, your Comment
model can be retrieved by making a call to ReactiveRecord directly. Avoid re-importing the /models/Comment.js
file more than once in your application.
const Comment = ReactiveRecord.model("Comment")
We now have enough to begin interacting with a JSON API!
Model basics
Now that you have a Comment
model defined, it’s time to start interacting with it — that is, creating instances, making API requests, and persisting data. Let’s start by creating an instance of Comment
.
For clarity, model instances in this documentation are represented like #<Comment> instead of Comment.*/
Creating instances
For demonstration purposes, make your Comment
model available to the window.
import ReactiveRecord from "reactiverecord"
window.Comment = ReactiveRecord.model("Comment")
Familiarize yourself with model instances by trying out the following in a browser JavaScript console and inspecting what is returned.
> var comment = new Comment()
=> 'Comment {body: null, ...}'
> var comment = new Comment({ body: "Hello world!" })
=> 'Comment {body: "Hello world!", ...}'
> JSON.stringify(comment, null, 2)
=> '{
"createdAt": null,
"updatedAt": null,
"id": null,
"body": "Hello world!",
"email": null,
"name": null,
"postId": null
}'
Making API requests
Configuration
We’re going to experiment by making API calls to a placeholder API on a different origin. To do that, we’ll need to tell Reactive Record how to build routes for this API by setting an API prefix.
By default, Reactive Record will assume your Comment
model has API endpoints available at /comments
on the same origin. You will learn more about configuring routes later.
Set the API prefix using ReactiveRecord.
import ReactiveRecord from "reactiverecord"
window.Comment = ReactiveRecord.model("Comment")
ReactiveRecord.setAPI({ prefix: "https://jsonplaceholder.typicode.com" })
Get the index
In a browser JavaScript console, log your first request to the comments endpoint of this API.
> Comment.all().then(console.log)
=> 'Collection(500) [Comment, Comment, Comment, ...]'
┗ GET
https://jsonplaceholder.typicode.com/comments
200 OK
Wow! The API request returned 500 results! So exciting! Let’s try creating data.
Create a record
> var comment = new Comment({ body: "Hello world!" })
=> 'Comment {body: "Hello world!", ...}'
> comment.save().then(console.log)
=> 'Comment {id: 501, ...}'
> comment.id
=> '501'
┗ POST
https://jsonplaceholder.typicode.com/comments
201 Created
It looks like our #<Comment>
was persisted with an ID of 501! How exciting? Now, let’s find an individual comment.
Show a record
> var comment
> Comment.find(13).then(response => comment = response)
> comment.name
=> 'lorem ipsum...'
> comment.id
=> '13'
┗ GET
https://jsonplaceholder.typicode.com/comments/13
200 OK
Calling .find(13)
on the model made an API request to load a comment with ID 13. Now, let’s update this record.
Update a record
> comment.updateAttributes({ name: "Abraham Lincoln" })
> comment.name
=> 'Abraham Lincoln'
┗ PUT
https://jsonplaceholder.typicode.com/comments/13
202 Accepted
This is what we want. You’ll learn more about the different methods that update a record later. Calling updateAttributes
is a simple way. Now, let’s destroy this record.
Destroy a record
> comment.destroy()
┗ DELETE
https://jsonplaceholder.typicode.com/comments/501
200 OK
Calling .destroy()
on the #<Comment>
triggered a DELETE
request to the API. Sweet! This test API returns all the right responses for testing purposes, but don’t expect it to remember what you’ve done.
Summary
At this point, you’ve created your first model and started making some API requests to a test API. The possibilities must seem endless. If your wheels aren’t spinning already, just wait. We’re about to demonstrate how to perform these same type of requests in JSX, using the included components!