diff --git a/.gitignore b/.gitignore
index 0cb6eeb..31cc3fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@
/pkg/
/spec/reports/
/tmp/
+.sass-cache
diff --git a/.ruby-version b/.ruby-version
new file mode 100644
index 0000000..2bf1c1c
--- /dev/null
+++ b/.ruby-version
@@ -0,0 +1 @@
+2.3.1
diff --git a/.sass-cache/bf6bcf5c2b198613d6e505b1ecdfe1fe9a7efb59/_mixins.scssc b/.sass-cache/bf6bcf5c2b198613d6e505b1ecdfe1fe9a7efb59/_mixins.scssc
new file mode 100644
index 0000000..75bf5f9
Binary files /dev/null and b/.sass-cache/bf6bcf5c2b198613d6e505b1ecdfe1fe9a7efb59/_mixins.scssc differ
diff --git a/Gemfile b/Gemfile
index b30a3a9..c554fb3 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,4 +1,21 @@
-source "http://artprod.dev.bloomberg.com/artifactory/api/gems/bb-ruby-repos/"
+source "https://rubygems.org"
+ruby RUBY_VERSION
-# Specify your gem's dependencies in jsonapi_suite.gemspec
-gemspec
+# Hello! This is where you manage which Jekyll version is used to run.
+# When you want to use a different version, change it below, save the
+# file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
+#
+# bundle exec jekyll serve
+#
+# This will help ensure the proper Jekyll version is running.
+# Happy Jekylling!
+gem "jekyll", "3.3.1"
+
+# If you want to use GitHub Pages, remove the "gem "jekyll"" above and
+# uncomment the line below. To upgrade, run `bundle update github-pages`.
+# gem "github-pages", group: :jekyll_plugins
+
+# If you have any plugins, put them here!
+group :jekyll_plugins do
+ gem "jekyll-feed", "~> 0.6"
+end
diff --git a/LICENSE.txt b/LICENSE.txt
deleted file mode 100644
index 1a7a41d..0000000
--- a/LICENSE.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2016 Lee Richmond
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
diff --git a/README.md b/README.md
index 99eb853..0b0b6e4 100644
--- a/README.md
+++ b/README.md
@@ -1,41 +1,17 @@
-# JsonapiSuite
+JSONAPI Suite Documentation
+==========
-Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/jsonapi_suite`. To experiment with that code, run `bin/console` for an interactive prompt.
+This is a static website generated by [jekyll](https://jekyllrb.com). To
+contribute:
-TODO: Delete this and the text above, and describe your gem
-
-## Installation
-
-Add this line to your application's Gemfile:
-
-```ruby
-gem 'jsonapi_suite'
+```bash
+$ git checkout gh-pages
+$ bundle install --binstubs
+$ bin/jekyll serve
```
-And then execute:
-
- $ bundle
-
-Or install it yourself as:
-
- $ gem install jsonapi_suite
-
-## Usage
-
-TODO: Write usage instructions here
-
-## Development
-
-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.
-
-To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
-
-## Contributing
-
-Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/jsonapi_suite.
-
-
-## License
-
-The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
+To add a how-to, edit `_data/how-tos`, and add a corresponding markdown
+file. See the existing how-tos for reference.
+Always make sure files are compiled before pushing. You can do this with
+`jekyll serve` or `jekyll build`
diff --git a/Rakefile b/Rakefile
deleted file mode 100644
index b7e9ed5..0000000
--- a/Rakefile
+++ /dev/null
@@ -1,6 +0,0 @@
-require "bundler/gem_tasks"
-require "rspec/core/rake_task"
-
-RSpec::Core::RakeTask.new(:spec)
-
-task :default => :spec
diff --git a/_config.yml b/_config.yml
new file mode 100644
index 0000000..070ec0e
--- /dev/null
+++ b/_config.yml
@@ -0,0 +1,40 @@
+# Welcome to Jekyll!
+#
+# This config file is meant for settings that affect your whole blog, values
+# which you are expected to set up once and rarely edit after that. If you find
+# yourself editing this file very often, consider using Jekyll's data files
+# feature for the data you need to update frequently.
+#
+# For technical reasons, this file is *NOT* reloaded automatically when you use
+# 'bundle exec jekyll serve'. If you change this file, please restart the server process.
+
+# Site settings
+# These are used to personalize your new site. If you look in the HTML files,
+# you will see them accessed via {{ site.title }}, {{ site.email }}, and so on.
+# You can create any custom variable you would like, and they will be accessible
+# in the templates via {{ site.myvariable }}.
+title: JSONAPI Suite
+email: richmolj@gmail.com
+description: > # this means to ignore newlines until "baseurl:"
+ Collection of Ruby libraries for powering JSONAPI-compliant APIs.
+baseurl: "" # the subpath of your site, e.g. /blog
+url: "" # the base hostname & protocol for your site, e.g. http://example.com
+twitter_username: jekyllrb
+github_username: jekyll
+
+# Build settings
+markdown: kramdown
+gems:
+ - jekyll-feed
+exclude:
+ - Gemfile
+ - Gemfile.lock
+
+github:
+ #url: 'http://localhost:4000'
+ url: 'https://jsonapi-suite.github.io/jsonapi_suite_deprecated'
+defaults:
+ - scope:
+ path: "assets/img"
+ values:
+ image: true
diff --git a/_data/docs.yml b/_data/docs.yml
new file mode 100644
index 0000000..4f47e27
--- /dev/null
+++ b/_data/docs.yml
@@ -0,0 +1,12 @@
+-
+ title: 'Resources'
+ id: 'resources'
+-
+ title: 'Serializers'
+ id: 'serializers'
+-
+ title: 'Sideloading'
+ id: 'sideloading'
+-
+ title: 'Sideposting'
+ id: 'sideposting'
diff --git a/_data/howtos.yml b/_data/howtos.yml
new file mode 100644
index 0000000..4c789d8
--- /dev/null
+++ b/_data/howtos.yml
@@ -0,0 +1,36 @@
+-
+ path: '/how-to-scope-to-current-user'
+ title: 'Scope to Current User'
+-
+ path: '/how-to-cause-side-effects'
+ title: 'Cause Side-Effects'
+-
+ path: '/how-to-use-without-activerecord'
+ title: 'Use Alternate ORMs'
+-
+ path: '/how-to-build-an-adapter'
+ title: 'Build an Adapter'
+-
+ path: '/how-to-code-many-to-many-associations'
+ title: 'Many-to-Many Relationships'
+-
+ path: '/how-to-code-polymorphic-associations'
+ title: 'Polymorphic Relationships'
+-
+ path: '/how-to-add-defaults'
+ title: 'Add Default Behavior'
+-
+ path: '/how-to-conditionally-render-fields'
+ title: 'Conditionally Render Fields'
+-
+ path: '/how-to-customize-error-responses'
+ title: 'Customize Error Responses'
+-
+ path: '/how-to-write-specs'
+ title: 'Write Specs'
+-
+ path: '/how-to-return-statistics'
+ title: 'Statistics'
+-
+ path: '/how-to-autodocument'
+ title: 'Autodocument with Swagger'
diff --git a/_includes/docs/resources/description.html b/_includes/docs/resources/description.html
new file mode 100644
index 0000000..c2d0851
--- /dev/null
+++ b/_includes/docs/resources/description.html
@@ -0,0 +1,33 @@
+
+ Resources are where you define how to query and persist a given Model.
+ We'll wire-up the JSONAPI contract for you, so you can customize hooks rather
+ than spend your time parsing query parameters.
+
+
+
+To the right, you'll see potential overrides of default behavior. Our defaults are based on ActiveRecord
+ and Rails conventions - so customizations like this are often not required.
+
+
+
+ If you find yourself writing the same customizations over and over again, DRY them up by writing an
+ Adapter.
+
+
diff --git a/_includes/docs/resources/example.html b/_includes/docs/resources/example.html
new file mode 100644
index 0000000..f74ed3c
--- /dev/null
+++ b/_includes/docs/resources/example.html
@@ -0,0 +1,31 @@
+{% highlight ruby %}
+class PostResource < ApplicationResource
+ # Use default logic (specified in adapter)
+ # when no block passed
+ allow_filter :active
+
+ allow_filter :title_prefix do |scope, value|
+ # Example:
+ # scope.where(["title LIKE ?", "#{value}%"])
+ end
+
+ sort do |scope, att, dir|
+ # Example:
+ # scope.order(att => dir)
+ end
+
+ paginate do |scope, current_page, per_page|
+ # Example:
+ # scope.per(per_page).page(current_page)
+ end
+
+ # Example: let's say we're hitting an HTTP
+ # service instead of ActiveRecord. Maybe our
+ # scope is a big hash we build up, and now
+ # we need to use a client to take that hash and
+ # execute the HTTP request.
+ def resolve(scope)
+ MyHTTPClient.request(scope)
+ end
+end
+{% endhighlight %}
diff --git a/_includes/docs/serializers/description.html b/_includes/docs/serializers/description.html
new file mode 100644
index 0000000..a63adf1
--- /dev/null
+++ b/_includes/docs/serializers/description.html
@@ -0,0 +1,19 @@
+
+ Serializers define the structure of the response.
+
+
+
+ We use jsonapi-rb for serialization. If you're familiar
+ with active_model_serializers,
+ the interface will seem very familiar. In fact, jsonapi-rb was created by one of the owners of that
+ project.
+
+
diff --git a/_includes/docs/serializers/example.html b/_includes/docs/serializers/example.html
new file mode 100644
index 0000000..68e0da2
--- /dev/null
+++ b/_includes/docs/serializers/example.html
@@ -0,0 +1,29 @@
+{% highlight ruby %}
+class PostSerializer < ApplicationSerializer
+ type 'foos'
+
+ attribute :title
+
+ attribute :description do
+ @object.active? ? 'Active Post' : 'Inactive Post'
+ end
+
+ extra_attribute :net_worth do
+ @object.assets.sum(&:value)
+ end
+
+ has_many :comments do
+ data do
+ @object.published_comments
+ end
+
+ link :related do
+ @url_helpers.comments_url(filter: { post_id: @object.id })
+ end
+ end
+
+ meta do
+ { featured: true }
+ end
+end
+{% endhighlight %}
diff --git a/_includes/docs/sideloading/description.html b/_includes/docs/sideloading/description.html
new file mode 100644
index 0000000..3216140
--- /dev/null
+++ b/_includes/docs/sideloading/description.html
@@ -0,0 +1,58 @@
+
+ You're probably already familiar with the concept of "sideloading", documented in
+ the
+
+ "Inclusion of Related Resources"
+
+ section of the JSONPI spec.
+
+
+
+ If using an
+
+ Adapter
+ , such as the default
+ ActiveRecord adapter,
+ you can do this with simple
+ has_many-style macros.
+
+
+
+ If not using an adapter, or to implement a one-off customization,
+ you can drop down to the lower-level
+ allow_sideload DSL. Adapter
+ macros are simply wrapping this DSL.
+
+
+
+ allow_sideload specifies two simple
+ configuration options:
+
+
+ scope: specifies how to build a scope that satisifes the
+ association.
+
+
+ assign: specifies how to associate the resulting records with
+ their parent association.
+
+
+
+
+ You can see the default
+ ActiveRecord implementation on the right.
+
+
diff --git a/_includes/docs/sideloading/example.html b/_includes/docs/sideloading/example.html
new file mode 100644
index 0000000..44bddc0
--- /dev/null
+++ b/_includes/docs/sideloading/example.html
@@ -0,0 +1,24 @@
+{% highlight ruby %}
+# Adapter example
+has_many :comments,
+ foreign_key: :post_id,
+ resource: CommentResource,
+ scope: -> { Comment.all } # base scope, like you'd see in a controller
+
+
+
+# Lower-level customization example
+allow_sideload :blog, resource: BlogResource do
+ # Return a scope so further query parameters can be applied
+ # and the Resource logic can be re-used
+ scope do |posts|
+ Blog.where(id: posts.map(&:blog_id))
+ end
+
+ assign do |posts, blogs|
+ posts.each do |p|
+ p.blog = blogs.find { |b| b.id == p.blog_id }
+ end
+ end
+end
+{% endhighlight %}
diff --git a/_includes/docs/sideposting/description.html b/_includes/docs/sideposting/description.html
new file mode 100644
index 0000000..ad06272
--- /dev/null
+++ b/_includes/docs/sideposting/description.html
@@ -0,0 +1,48 @@
+
+ We want to be able to *write* a nested relationship graph.
+ You can do this by mirroring the same
+ sideloading payload
+ you see in read requests. To the right, you'll see we're updating a
+ Post, changing the name of its associated
+ Blog, creating a
+ Tag, deleting one
+ Comment, and disassociating (null foreign key) a different
+ Comment, all in a single request.
+
+
+
+ When we send RESTful resources to the server, we typically send a corresponding HTTP verb.
+ That's the
+ method section of the payload.
+ method can be either
+ create,
+ update,
+ destroy, or
+ disassociate.
+
+
+
+ When creating, there is no
+ id to pass. Instead, pass
+ temp-id, a random uuid used to
+ link the relevant sections of the payload, and which tells clients how to associate
+ their in-memory objects with the
+ ids returned from the server. Clients like
+ JSORM will take care of this for you automatically.
+
+
+
+ When we actually perform these operations, everything will run within a transaction. The
+ transaction will be rolled back if an error is raised or the
+ Models, do not pass validation.
+
+
diff --git a/_includes/highlight.html b/_includes/highlight.html
new file mode 100644
index 0000000..8dd6f8c
--- /dev/null
+++ b/_includes/highlight.html
@@ -0,0 +1,43 @@
+
diff --git a/_includes/homepage/features-grid-section.html b/_includes/homepage/features-grid-section.html
new file mode 100644
index 0000000..0fef3a1
--- /dev/null
+++ b/_includes/homepage/features-grid-section.html
@@ -0,0 +1,38 @@
+
+
+
+
+
+ Rails Standards
+
If you choose to use Ruby on Rails, you'll find all the normal expectations of MVC development in place. We help you deal with the API contract, then get out of your way.
+
+
+
+ Easy Microservices
+
Because the API contract is standardized, the microservice premium is drastically lowered. Build quick, lightweight APIs without the hassle.
+
+
+
+ One API, Many Clients
+
The API for your website should be the same API for your mobile app or ETL process. Flexible APIs enable easy re-use across a variety of use-cases.
+
+
+
+
+
+ Client Support
+
We offer an ActiveRecord-like isomorphic javascript client in JSORM. Ruby and Python clients are upcoming.
+
+
+
+ Error Handling
+
Always render valid JSONAPI-compliant error responses. Use a simple DSL to customize messages and response codes.
+
+
+
+ Automatic Documentation
+
Full Swagger documentation enabled with one line of code. Use swagger-diff for an extra level of backwards-compatibility check.
+
+
+
+
diff --git a/_includes/homepage/features-tabs-section.html b/_includes/homepage/features-tabs-section.html
new file mode 100644
index 0000000..7e2ffe7
--- /dev/null
+++ b/_includes/homepage/features-tabs-section.html
@@ -0,0 +1,79 @@
+
+ Everything you want from an API - filtering, sorting, pagination, statistics and more - comes "out of the box". Common problems are solved in a common way, leaving you more time to tend to your domain and focus on client needs.
+
+
+ Don't worry - this isn't a straightjacket, just a grouping of sensible defaults.
+
+
+
+
+
+
+
+
+
+
+
+
Custom sorting logic? MongoDB? No problem.
+
+ JSONAPI Suite was built for customization. We provide out-of-the-box defaults, but everything can be overridden. You can even package your overrides into an adapter to DRY up code.
+
+
+ We take care of wiring up the API contract. You take care of your domain.
+
+
+
+
+
+
Full-Stack RSpec Integration Tests
+
+ Whether you're a hardcore TDD engineer or throwing up a quick prototype, we'll provide patterns for easy-to-write integration tests than ensure backwards-compatibility.
+
+
+ We're not just validating schemas or stubbing critical integration points. Test your API fully - a real server, a real database, and robust assertions on full JSON payloads.
+
+
+
+
+
+
+
+
+
+
+
+
Problem Solved.
+
+ One of the problems with REST has always been compound documents. What if you need to save your Post and its Tags within the same transaction? What if you need a single request to fetch the Post and its last three active Tags?
+
+
+ JSONAPI Suite standardizes patterns for cross-relationship reads and writes, providing out-of-the-box functionality and one more thing you no longer have to worry about.
+
+
+
+
+
+
+
+
diff --git a/_includes/js-code-tabs.html b/_includes/js-code-tabs.html
new file mode 100644
index 0000000..490ae7f
--- /dev/null
+++ b/_includes/js-code-tabs.html
@@ -0,0 +1,8 @@
+
+
+ Typescript
+
+
+ Javascript
+
+
diff --git a/_includes/js-header.html b/_includes/js-header.html
new file mode 100644
index 0000000..3429229
--- /dev/null
+++ b/_includes/js-header.html
@@ -0,0 +1,4 @@
+
+ JSORM
+ the isomorphic, framework-agnostic Javascript ORM
+
diff --git a/_includes/js-toc.html b/_includes/js-toc.html
new file mode 100644
index 0000000..9a3b93d
--- /dev/null
+++ b/_includes/js-toc.html
@@ -0,0 +1,25 @@
+
+ This API now supports
+ Sparse Fieldsets,
+ Sorting,
+ Pagination,
+ statistics, and
+ Filtering.
+
+ Though we're using
+ ActiveRecord
+ in these examples, the same patterns
+ apply to
+ ANY ORM or DATASTORE
+ including
+ HTTP calls.
+ Blend SQL and NoSQL in a
+ single
+ request.
+
+
+
+ Let's access the API using our
+ Javascript Client,
+ which you can think of as
+
+ Associations are
+ deep queryable. In other words,
+ you could fetch the Post and only its
+ active comments,
+ sorted by
+ created_at descending.
+ This applies to your
+ entire graph of data.
+ The server-side code would be nothing more than:
+
+ ...and
+ automatically documented
+ in Swagger:
+
+
+
+
+
+
+
+ There's
+ so much more
+ to talk about. To get your feet wet, check out
+ our
+ Quickstart,
+ or step-by-step
+ Tutorial.
+ We also have
+ comprehensive documentation
+ on the
+ Server
+ and on the
+ Client.
+ Join our
+ Slack Chat
+ to ask questions or say hi - we'd
+ love to meet you and hear what you think ❤️
+
+
diff --git a/_layouts/page.html b/_layouts/page.html
new file mode 100644
index 0000000..40a12f4
--- /dev/null
+++ b/_layouts/page.html
@@ -0,0 +1,18 @@
+
+
+
+ {% include head.html %}
+
+
+
+
+ Resources are where you define how to query and persist a given Model.
+ We'll wire-up the JSONAPI contract for you, so you can customize hooks rather
+ than spend your time parsing query parameters.
+
+
+
+To the right, you'll see potential overrides of default behavior. Our defaults are based on ActiveRecord
+ and Rails conventions - so customizations like this are often not required.
+
+
+
+ If you find yourself writing the same customizations over and over again, DRY them up by writing an
+ Adapter.
+
classPostResource<ApplicationResource
+ # Use default logic (specified in adapter)
+ # when no block passed
+ allow_filter:active
+
+ allow_filter:title_prefixdo|scope,value|
+ # Example:
+ # scope.where(["title LIKE ?", "#{value}%"])
+ end
+
+ sortdo|scope,att,dir|
+ # Example:
+ # scope.order(att => dir)
+ end
+
+ paginatedo|scope,current_page,per_page|
+ # Example:
+ # scope.per(per_page).page(current_page)
+ end
+
+ # Example: let's say we're hitting an HTTP
+ # service instead of ActiveRecord. Maybe our
+ # scope is a big hash we build up, and now
+ # we need to use a client to take that hash and
+ # execute the HTTP request.
+ defresolve(scope)
+ MyHTTPClient.request(scope)
+ end
+end
+
+
+
+
+
+
+
+
+
+
Serializers
+
+ Serializers define the structure of the response.
+
+
+
+ We use jsonapi-rb for serialization. If you're familiar
+ with active_model_serializers,
+ the interface will seem very familiar. In fact, jsonapi-rb was created by one of the owners of that
+ project.
+
classPostSerializer<ApplicationSerializer
+ type'foos'
+
+ attribute:title
+
+ attribute:descriptiondo
+ @object.active??'Active Post':'Inactive Post'
+ end
+
+ extra_attribute:net_worthdo
+ @object.assets.sum(&:value)
+ end
+
+ has_many:commentsdo
+ datado
+ @object.published_comments
+ end
+
+ link:relateddo
+ @url_helpers.comments_url(filter: {post_id: @object.id})
+ end
+ end
+
+ metado
+ {featured: true}
+ end
+end
+
+
+
+
+
+
+
+
+
+
Sideloading
+
+ You're probably already familiar with the concept of "sideloading", documented in
+ the
+
+ "Inclusion of Related Resources"
+
+ section of the JSONPI spec.
+
+
+
+ If using an
+
+ Adapter
+ , such as the default
+ ActiveRecord adapter,
+ you can do this with simple
+ has_many-style macros.
+
+
+
+ If not using an adapter, or to implement a one-off customization,
+ you can drop down to the lower-level
+ allow_sideload DSL. Adapter
+ macros are simply wrapping this DSL.
+
+
+
+ allow_sideload specifies two simple
+ configuration options:
+
+
+ scope: specifies how to build a scope that satisifes the
+ association.
+
+
+ assign: specifies how to associate the resulting records with
+ their parent association.
+
+
+
+
+ You can see the default
+ ActiveRecord implementation on the right.
+
# Adapter example
+has_many:comments,
+ foreign_key: :post_id,
+ resource: CommentResource,
+ scope: ->{Comment.all}# base scope, like you'd see in a controller
+
+
+
+# Lower-level customization example
+allow_sideload:blog,resource: BlogResourcedo
+ # Return a scope so further query parameters can be applied
+ # and the Resource logic can be re-used
+ scopedo|posts|
+ Blog.where(id: posts.map(&:blog_id))
+ end
+
+ assigndo|posts,blogs|
+ posts.eachdo|p|
+ p.blog=blogs.find{|b|b.id==p.blog_id}
+ end
+ end
+end
+
+
+
+
+
+
+
+
+
+
Sideposting
+
+ We want to be able to *write* a nested relationship graph.
+ You can do this by mirroring the same
+ sideloading payload
+ you see in read requests. To the right, you'll see we're updating a
+ Post, changing the name of its associated
+ Blog, creating a
+ Tag, deleting one
+ Comment, and disassociating (null foreign key) a different
+ Comment, all in a single request.
+
+
+
+ When we send RESTful resources to the server, we typically send a corresponding HTTP verb.
+ That's the
+ method section of the payload.
+ method can be either
+ create,
+ update,
+ destroy, or
+ disassociate.
+
+
+
+ When creating, there is no
+ id to pass. Instead, pass
+ temp-id, a random uuid used to
+ link the relevant sections of the payload, and which tells clients how to associate
+ their in-memory objects with the
+ ids returned from the server. Clients like
+ JSORM will take care of this for you automatically.
+
+
+
+ When we actually perform these operations, everything will run within a transaction. The
+ transaction will be rolled back if an error is raised or the
+ Models, do not pass validation.
+
+ Whether you want to fill this paragraph with some text like I'm doing right now, this place is perfect to describe some features or anything you want - React has a complete solution for you.
+
+
+ You have complete control over the look & feel of your website, we offer the best quality so you take your site up and running in no time.
+
+
+
+
+
+
+
+
+
+
+
+
You don't need to have any advanced technical
+
+ Whether you want to fill this paragraph with some text like I'm doing right now, this place is perfect to describe some features or anything you want - React has a complete solution for you.
+
+
+
+
+
+
You don't need to have any advanced technical
+
+ Whether you want to fill this paragraph with some text like I'm doing right now, this place is perfect to describe some features or anything you want - React has a complete solution for you.
+
+
+ You have complete control over the look & feel of your website, we offer the best quality so you take your site up and running in no time.
+
+
+
+
+
+
+
+
+
+
+
+
You don't need to have any advanced technical
+
+ Whether you want to fill this paragraph with some text like I'm doing right now, this place is perfect to describe some features or anything you want - React has a complete solution for you.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/_site/feed.xml b/_site/feed.xml
new file mode 100644
index 0000000..7ec58e5
--- /dev/null
+++ b/_site/feed.xml
@@ -0,0 +1,2 @@
+Jekyll2019-01-06T14:12:19-05:00/JSONAPI SuiteCollection of Ruby libraries for powering JSONAPI-compliant APIs.
+
\ No newline at end of file
diff --git a/_site/how-to-add-defaults.html b/_site/how-to-add-defaults.html
new file mode 100644
index 0000000..dc128dc
--- /dev/null
+++ b/_site/how-to-add-defaults.html
@@ -0,0 +1,201 @@
+
+
+
+
+
+
+
+
+ JSONAPI Suite
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
You may need to change the default behavior or your API - perhaps you
+want a default of 10 per page instead of 20. JSONAPI Suite provides
+facilities that enable defaults that can be overridden - 10 per
+page, unless elsewise specified by the user.
These can all be overriden by the user. In other words, hitting
+/posts will only show active Posts, hitting
+/posts?filter[active]=false will show inactive Posts. The same applies
+for sorting and pagination.
+
+
A common pattern is for default filters to apply for all users, but
+allow overrides for administrators. You can use the :if option to
+restrict the override:
This suite uses DSLs to specify inputs (strong_resources, filters, etc), and outputs (jsonapi-rb serializers).
+We can introspect that DSL to provide automatic documentation. Not only
+does this save a lot of time, it ensures your code and documentation are
+never out of sync.
+
+
Here we’ll be using swagger, a popular open-source
+documentation framework.
+
+
+
+
+
+
+To get this UI, we need to install two things: a controller that
+generates a schema (swagger.json), and a static website
+in public. Let’s start by adding dependencies:
+
+
+ Note: here we’re moving jsonapi_spec_helpers out of the
+ test-specific bundle group. Introspecting spec helpers is part of
+ autodocumenting, so we’ll require them manually when our documentation
+ controller is loaded.
+
+
+
Our web UI is a Glimmer App,
+but we’ll only deal with the already-compiled static files glimmer builds.
+Let’s copy those files and expose them to the web by putting them in
+public:
Our documentation will be accessible at /api/docs, so we put the files
+in public/api/docs. You may want a different directory depending on
+your own routing rules. In either case, our next step is to edit
+index.html: make sure any javascript and css has the correct URL.
+There are also a few configuration options, such as providing a link to
+Github.
If you find yourself repeatedly making customizations to a group of
+Resources and seek DRYer code, package those customizations into an
+Adapter. Here we’ll be starting from a previous how-to, How to Use
+Alternate ORMs.
Adapters are simpler than you might think. It’s little more than
+copy-pasting those low-level customizations into a common class.
+
+
Start by creating lib/elasticsearch_adapter.rb. Cut/past the sorting,
+pagination, and #resolve overrides from EmployeeResource into the adapter,
+turning into def methods along the way:
Bounce your server. You can still hit the /api/v1/employees endpoint
+with the same sort and paginate functionality, but the code has been
+moved to an adapter.
For all the methods and functionality an adapter supports, see the
+Adapter documenation.
+
+
We probably also want has_many-style macros to avoid writing similar
+allow_sideload code time after time. Start by specifying where this
+functionality is defined, and add a has_many macro:
+
+
moduleSideloading
+ defhas_many(association_name,
+ scope:,
+ resource:,
+ foreign_key:,
+ primary_key: :id,
+ &blk)
+ # our code will go here
+ instance_eval(&blk)ifblk
+ end
+end
+
+defsideloading_module
+ Sideloading
+end
+
+
The instance_eval is there so we can always drop down to a lower-level
+customization in our Resource.
+
+
We can basically cut/paste our existing sideload code and rewrite it as
+variables:
You can now remove any customizations from your Resource classes. You
+can continue to build the adapter, adding belongs_to, statistics, and
+more. View the adapter documentation for the full API.
Side effects scenarios come up often. What if we want to send an email
+notification every time a Comment is created?
+
+
It’s important to note that there are three overall categories of side effects,
+and each requires a different solution:
+
+
+
Side effects internal to the Model: For example, setting a
+published_at attribute.
+
Side effects that should only occur on a specific request: For
+example, only send an email update if we’re creating a Post for the
+first time, at the /posts endpoint.
+
Side effects that should occur on every type of request: For
+example, send an email notification every time a Comment is created -
+but not updated - whether it was created at the /comments endpoint or sideposted at the
+/posts endpoint.
+
+
+
Internal Side-Effects
+
+
For the first scenario, it’s OK to use ActiveRecord callbacks (or the
+equivalent functionality in a different ORM):
+
+
# app/models/user.rb
+classPost<ApplicationRecord
+ before_save:set_published_at,
+ on: :update,
+ if: :publishing?
+
+ private
+
+ defset_published_at
+ self.published_at=Time.now
+ end
+
+ defpublishing?
+ status_changed?&&status=='published'
+ end
+end
+
+
Side-Effects on Specific Action
+
+
Just like you would with vanilla Rails, use the controller. Here we’ll
+only send an email to our subscribers when the Post is first created.
+Keep in mind we could also “sidepost” Post objects at the /blogs
+endpoint, but this will only fire at the /posts endpoint.
Let’s add some special logging every time we create a Post. Note this
+will fire every time we create a Post - whether we create it at the
+/posts endpoint or “sidepost” at the /blogs endpoint.
+
+
We do not need to worry about this side-effect in our model specs,
+or rake tasks, as the functionality is only relevant to the API.
+
+
Edit your Resource:
+
+
# app/resources/post_resource.rb
+defcreate(attributes)
+ Rails.logger.info"Post begin created by #{context.current_user.email}..."
+ super
+ Rails.logger.info"Success!"
+end
has_and_belongs_to_many are possible, but maybe not desirable
+depending on your use case. Following the example from the tutorial,
+let’s say an Employee has many Teams and a Team has many
+Employees. We could wire-up our EmployeeResource like so:
The only difference here is the foreign_key - we’re passing a hash
+instead of a symbol. employee_teams is our join table, and
+employee_id is the true foreign key.
+
+
This will work, and for simple many-to-many relationships you can move
+on. But what if we want to add the property primary, a boolean, to the
+employee_teams table?
+
+
As this is metadata about the relationship it should go on the meta
+section of the corresponding relationship object.
+While supporting such an approach is on the JSONAPI Suite roadmap, many
+clients do not currently support this per-object level of functionality.
+
+
For now, it might be best to simply expose the intermediate table to the
+API. Using a client like
+JSORM, the overhead of this
+approach is minimal.
Now we need to wire-up our resource. Usually you’d see something like
+has_many with a few options. But here, we may actually want to change
+our configuration based on Workspace#type - maybe each type of data is
+stored in a separate table, for instance.
+
+
We want to pass the same configuration, but on a type-by-type basis. In
+other words, we need to group workspaces and define how to associate
+each group:
Let’s say our API was returning 10 Employees, sideloading their
+corresponding Workspace. The underlying code would:
+
+
+
Fetch the employees
+
Group the employees by the given key: employees.group_by { |e|
+e.workspace_type }
+
Use the Office configuration for all Employees where
+workspace_type is Office, and use the HomeOffice configuration
+for all Employees where workspace_type is HomeOffice.
Of course, JSONAPI already has the concept of sparse fieldsets built-in.
+This behavior comes out-of-the-box at URLs like
+/people?fields[people]=title,active.
+
+
Sometimes it’s necessary to conditionally render an extra field as
+well. For instance, maybe rendering out the net_worth attribute is
+computationally expensive and not often requested.
This field will not be rendered when we hit /people. It will only be
+rendered when we hit /people?extra_fields[people]=net_worth. The URL
+signature is the same as sparse fieldsets.
+
+
We may want to eager load some data, only when a specific extra field is
+requested. We can do that by customizing the Resource:
Your application will automatically return a JSONAPI-compliant error
+object whenever an error is raised.
+That’s due to this code in ApplicationController:
This can all be customized. Let’s say for all
+ActiveRecord::RecordNotFound errors we want a 404 response code, with
+the error detail providing a custom message:
+
+
# app/controllers/application_controlle.rb
+register_exceptionActiveRecord::RecordNotFound,
+ status: 422,
+ message: ->(e){"Couldn't find record with id #{e.id}"}
+
+
Would output:
+
+
{
+ errors: [
+ code: 'not_found',
+ status: '404',
+ title: 'Error',
+ detail: "Couldn't find record with id 123",
+ meta: {}
+ ]
+}
+
+
You can register exceptions in ApplicationController, or any subclass
+if you want a specific controller to handle a given error differently.
A few ‘default calculations’ are provided: count, sum, average,
+maximum and minimum. These will work out-of-the-box with ActiveRecord.
+Alternatively, override these calculation functions:
Given Employees with attribute under_performance_review, do not allow clients to find all employees under performance review.
+
+
+
Occasionally you need to guard filters based on the current user. Use
+the :if option on allow_filter. This will execute in the context of
+your controller:
Though we’ll be hitting elasticsearch in this
+example, remember that this is just an HTTP API underneath the hood. The
+same pattern applies to a variety of use cases.
+
+
First we need a Client for elasticsearch. Though you can feel free
+to use a variety of clients, this example will use trample.
Also keep in mind, we’ll be showing a one-off customization here. You
+probably want to extract this code into an Adapter if this is going to
+become a core component of your application.
+
+
Start by installing trample:
+
+
# Gemfile
+gem'trample_search',require: 'trample'
+
+
Tell searchkick that we want to
+index Employees and Positions:
In our controller, we need to pass a base scope. Before, we were passing
+an ActiveRecord::Relation (Post.all). Let’s pass an instance
+of Trample::Search instead. Since by default search results come back
+as Hashie::Mashes, we’ll also specify our serializer directly. You
+could also use a generic SearchResult serializer, it’s up to you.
Since we are now passing a non-default base scope, we need to tell our
+Resource how to query and resolve this new scope. Start by switching to
+the pass-through adapter, and resolve using trample’s query API:
+
+
# app/resources/employee_resource.rb
+use_adapterJsonapiCompliable::Adapters::Null
+# ... code ...
+defresolve(scope)
+ scope.query!
+ scope.results
+end
+
+# remove the belongs_to for now
+
+
You can now hit http://localhost:3000/api/v1/employees - the exact
+same payload is coming back, but is now sourced from elasticsearch!
View the Resource and Adapter documentation for additional overrides, like statistics.
+
+
The last step is adding the positions association. If we want
+has_many-style macros we need to create an Adapter, but for now
+let’s simply use the lower-level allow_sideload DSL. We need to define
+two functions: how to build a scope for the association, and how to
+associate the resulting objects:
+
+
# app/resources/employee_resource.rb
+allow_sideload:positions,resource: PositionResourcedo
+ scopedo|employees|
+ scope=PositionSearch.new
+ scope.condition(:employee_id).or(employees.map(&:id))
+ end
+
+ assigndo|employees,positions|
+ employees.eachdo|e|
+ e.positions=positions.select{|p|p.employee_id=e.id}
+ end
+ end
+end
+
+
Convert the PositionResource to use elasticsearch, just like we did
+for Employee:
We can now sideload positions - check out the results at
+http://localhost:3000/api/v1/employees?include=positions. We’re
+fetching employees and their corresponding positions in a single
+request, via elasticsearch. Any filters/changes/default sort/etc that
+apply to PositionResource can be re-used at this endpoint.
+
+
If this was a one-off section of our application, we can call this good
+enough and move on. But as we continue to use this pattern, it’s going
+to get monotonous writing the same filter overrides, allow_sideload
+wiring code, etc. To DRY up this code, we can package our changes into
+an Adapter.
NB: before writing specs, make sure you are set up correctly by
+following the Quickstart
+
+
+
Validating verbose JSON API responses in tests can be a pain. We could
+use something like json_matchers to validate a schema, but we hope to do one better - let’s validate full payloads with a few simple helpers, using full-stack rspec request specs.
+
+
Let’s say we’re testing the show action of our employees controller, sideloading the employee’s department. Follow the Quickstart to make sure your rails_helper.rb is setup correctly first.
+
+
Let’s say we’re testing the show action of our employees controller, sideloading the employee’s department.
+
+
Let’s begin with vanilla RSpec of what the test might look like:
The name of a payload we’ve defined (we haven’t done this yet).
+
The record we want to compare against
+
The relevant slice of json. json_item and json_includes are
+helpful methods to target the right slice. You can see all helpers in
+the documentation for jsonapi_spec_helpers.
+
+
+
OK, so we want to take a record, response JSON, and compare them against
+something pre-defined. Let’s write those definitions; they look very similar to
+something you’d write for factory_girl:
Optionally, validate against a type as well. If both the expected and
+actual values match, but are the incorrect type, the test will fail:
+
+
key(:salary,Integer)
+
+
You can also customize/override payloads at runtime in your test. Let’s
+say we only serialize salary when the current user is an admin. Your
+test could look something like:
+ This API now supports
+ Sparse Fieldsets,
+ Sorting,
+ Pagination,
+ statistics, and
+ Filtering.
+
+ Though we're using
+ ActiveRecord
+ in these examples, the same patterns
+ apply to
+ ANY ORM or DATASTORE
+ including
+ HTTP calls.
+ Blend SQL and NoSQL in a
+ single
+ request.
+
+
+
+ Let's access the API using our
+ Javascript Client,
+ which you can think of as
+
+ Associations are
+ deep queryable. In other words,
+ you could fetch the Post and only its
+ active comments,
+ sorted by
+ created_at descending.
+ This applies to your
+ entire graph of data.
+ The server-side code would be nothing more than:
+
+
+
+
+
allow_filter:active
+
+
+
+
+
+ At this point, You may be thinking:
+
+
+
+ "Is this just a bunch of incomprehensible ruby magic
+
+ 😬
+
+ ?"
+
+
+
+
+ No.
+
+
+ We're simply parsing the request, removing boilerplate, and supplying
+ sensible defaults because we believe in
+ convention over configuration.
+
+
+
+ Let's make our filter a prefix query:
+
+
+
+
+
allow_filter:title_prefixdo|scope,value|
+ scope.where(["title LIKE ?","#{value}%"])
+end
+
+
+
+
+
+ From filtering to pagination, these are all just
+ customizable lambdas.
+ You have
+ complete control
+ of the query.
+
+
+
+ Just as you can
+ Query
+ the full graph of data, you can also
+ Persist
+ the full graph of data in a
+ single request:
+
+ ...and
+ automatically documented
+ in Swagger:
+
+
+
+
+
+
+
+ There's
+ so much more
+ to talk about. To get your feet wet, check out
+ our
+ Quickstart,
+ or step-by-step
+ Tutorial.
+ We also have
+ comprehensive documentation
+ on the
+ Server
+ and on the
+ Client.
+ Join our
+ Slack Chat
+ to ask questions or say hi - we'd
+ love to meet you and hear what you think ❤️
+
Finally, if your server returns a refreshed JWT within the X-JWT
+header, it will be used in all subsequent requests (and localStorage
+will be updated automatically if you’re using it).
It’s a popular pattern to pass data down to components, avoid modifying state within the component, and instead pass actions up to modify state. This can make complex applications easier to track and reason about, and you’ll see it in client-side frameworks from React to Ember.
+
+
To follow this pattern, use #dup() when passing down to your component:
+
+
<my-component something="model.dup()" />
+
+
This will create a new instance of the model with all the same state.
+Avoid modifying this instance in your component and instead pass
+actions up.
+
+
When opting-in to state-syncing these instances will sync-up whenever one of these is instances is persisted. You won’t have to worry about updating the child component when the parent instance is saved.
…or, if you’re avoiding JS modules, jsorm will be available as a global in
+the browser.
+
+
Defining Models
+
+
Connecting to the API
+
+
Just like ActiveRecord, our models will inherit from a base class that
+holds connection information (ApplicationRecord, or
+ActiveRecord::Base in Rails < 5):
As you can see above, typically baseUrl and apiNamespace are set on
+a top-level ApplicationRecord (though any subclass can override).
+jsonapiType, however, is set per-model:
With the above configuration, all Person endpoints will begin
+http://my-api.com/api/v1/people.
+
+
+
TIP: Avoid CORS and use relative paths by simply setting baseUrl to
+""
+
+
+
+
TIP: You can always use the endpoint option to override this pattern
+and set the endpoint manually.
+
+
+
Defining Attributes
+
+
ActiveRecord automatically sets attributes by introspecting database
+columns. We could do the same - swagger.json is our schema - but tend
+to agree with those who feel this aspect of ActiveRecord is a bit too
+“magical”. In addition, explicitly defining our attributes can be used
+to track which applications are using which attributes of the API.
+
+
Though this is configurable, by default we expect the API to be
+under_scored and attributes to be camelCased.
By default, we expect the relationship name to correspond to a
+pluralized jsonapiType on a separate Model. If your models don’t
+use this convention, feel free to supply it explicitly:
Saved in a single request .save({ with: 'dogs' }) (see
+writes)
+
+
+
+
+ Typescript
+
+
+ Javascript
+
+
+
+
+
+
letdog=newDog({name:"Fido"})
+letperson=newPerson({dogs:[dog]})
+person.dogs[0].name// "Fido"
+
+letperson=newPerson()
+person.dogs=[dog]
+person.dogs[0].name// "Fido"
+
+// Will auto-create Dog instance
+letperson=newPerson({dogs:[{name:"Scooby"}]})
+person.dogs[0].name// "Scooby"
+
+letperson=(awaitPerson.includes('dogs')).data
+person.dogs// array of Dog instances from the server
+
+
+
vardog=newDog({name:"Fido"})
+varperson=newPerson({dogs:[dog]})
+person.dogs[0].name// "Fido"
+
+letperson=newPerson()
+person.dogs=[dog]
+person.dogs[0].name// "Fido"
+
+// Will auto-create Dog instance
+varperson=newPerson({dogs:[{name:"Scooby"}]})
+person.dogs[0].name// "Scooby"
+
+Person.includes('dogs').then((response)=>{
+ varperson=response.data
+ person.dogs// array of Dog instances from the server
+})
+
Contracts like JSONAPI and GraphQL treat the API like a database. When querying a
+database, we have two options:
+
+
+
Type the low-level query language directly (in the database world,
+this would be hand-typing SQL).
+
Use an ORM (like Rails’s ActiveRecord, Phoenix’s Ecto, Django’s
+DjangoORM, or Node’s Sequelize).
+
+
+
While both options have pros and cons, we tend to think ORMs
+have two overwhelming benefits: ease of use and composable queries.
+We’ll explore both these concepts in other sections.
+
+
So, we want a javascript ORM for our JSONAPI “database”. Because
+ActiveRecord is arguably the most well-known ORM, we’ve tried to match
+its interface to make this library accessible to new users. That said,
+you’ll find we’ve tried to favor explicitness over implicitness in
+order to avoid common ActiveRecord pitfalls.
Middleware is helpful whenever you want to globally intercept request.
+This is accomplished by assigning a MiddlewareStack to your
+ApplicationRecord. Each stack has beforeFilters and afterFilters
+where you can globally modify requests. If you throw("abort"), the
+promise will be rejected.
+
+
Example: redirecting to the login page every time the server returns 401:
Use #select() to limit the fields returned by the server:
+
+
Post.select(['title','status']).all()
+
+
+
/posts?fields[posts]=title,status
+
+
+
When dealing with relationships, it may be easier to pass an object,
+where the key is the corresponding JSONAPI type. This will be exactly
+what’s sent to the server in ?fields:
#where() clauses can be chained together. If the same key is seen
+twice, it will be overridden:
+
+
Post
+ .where({important:true})
+ .where({ranking:10})
+ .where({important:false})
+ .all()
+
+
+
/posts?filter[important]=false&filter[ranking]=10
+
+
+
#where() clauses are based on server implementation. The key
+should be exactly as the server understands it. Here are some common
+conventions we promote:
+
+
// id greater than 5
+Post.where({id_gt:5}).all()
+
+// id greater than or equal to 5
+Post.where({id_gte:5}).all()
+
+// id less than 5
+Post.where({id_lt:5}).all()
+
+// id less or equal to 5
+Post.where({id_lte:5}).all()
+
+// title starts with "foo"
+Post.where({title_prefix:"foo"}).all()
+
+// OR these two values
+Post.where({status_or:['draft','review']})
+
+// AND these two values (default)
+Post.where({status:['draft','review']})
The interface for read operations is a simpler version of the
+ActiveRecord Query Interface.
+Instead of generating SQL, we’ll be generating JSONAPI requests.
+
+
Basic Finders
+
+
Execute queries with .all(), find(), or .first():
+
+
+
+ Typescript
+
+
+ Javascript
+
+
+
+
+
+
letresponse=awaitPost.all()
+response.data// array of Post instances
+
+
+
Post.all().then(function(response){
+ response.data// array of Post instances
+});
+
+
+
+
+
GET /posts
+
+
+
+
+ Typescript
+
+
+ Javascript
+
+
+
+
+
+
letresponse=awaitPost.find(123)
+response.data// Post instance
+
+
+
Post.find(123).then(function(response){
+ response.data// Post instance
+});
+
+
+
+
+
GET /posts/123
+
+
+
+
+ Typescript
+
+
+ Javascript
+
+
+
+
+
+
letresponse=awaitPost.first()
+response.data// Post instance
+
+
+
Post.first().then(function(response){
+ response.data// Post instance
+});
+
+
+
+
+
GET /posts?page[size]=1
+
+
+
Composable Queries with Scopes
+
+
The beauty of ORMs is their ability to compose queries. We’ll be doing
+this by chaining together Scopes (query fragments). All of the methods
+you see on this page can be chained together - the request will not fire
+until the chain ends with all(), first(), or find. Example:
In practice, you’ll probably have some scopes you want to re-use across
+different contexts. A best practice is to store these scopes as class
+methods (static methods) in the model:
+
+
+
+ Typescript
+
+
+ Javascript
+
+
+
+
+
+
classPostextendsApplicationRecord{
+ // ... code ...
+ staticsuperImportant(){
+ returnthis
+ .where({ranking_gt:8})
+ .order({ranking:'desc'})
+ .stats({total'count'})
+ }
+}
+
+// get 10 super important posts
+letscope=Post.superImportant().per(10)
+scope.all()// fire query
+
+
+
constPost=ApplicationRecord.extend({
+ // ... code ...
+ static:{
+ superImportant(){
+ returnthis
+ .where({ranking_gt:8})
+ .order({ranking:'desc'})
+ .stats({total'count'})
+ }
+ }
+})
+
+// get 10 super important posts
+varscope=Post.superImportant().per(10);
+scope.all()// fire query
+
The result of all(), first() or find is a Promise. The promise will resolve to a Response object.
+
+
A Response object has three keys - data, meta, and raw. data - the one
+you’ll be using the most - will be a Model instance (or array of
+Model) instances. meta will be the Meta Information returned by the API (mostly used for statistics in our case). raw is only used to introspect the raw response document.
+
+
+
+ Typescript
+
+
+ Javascript
+
+
+
+
+
+
Post.all().then((response)=>{
+ response.data// array of Post instances
+ response.meta// js object from the server
+ response.raw// js response document
+})
+
+
+
Post.all().then(function(response){
+ response.data// array of Post instances
+ response.meta// js object from the server
+ response.raw// js response document
+});
+
+
+
+
+
/posts
+
+
+
Hopefully you’re running in an environment that supports
+ES7’s Async/Await. This makes things even easier:
+
+
let{data}=awaitPost.all()
+data// array of Post instances
+
+// alternatively
+
+letposts=(awaitPost.all()).data
+posts// array of Post instances
We can nest all read operations at any level of the graph. Let’s say we wanted to
+fetch all Posts and their Comments…but only return comments that
+are active, sorted by created_at descending. We can create a
+Comment scope as normal, then #merge() it into our Post scope:
Any number of scopes can be merged in. Just remember to #include()
+and #merge() relationship names as the server understands them:
+
+
+
+ Typescript
+
+
+ Javascript
+
+
+
+
+
+
classDogextendsApplicationRecord{
+ @BelongsTo()person:Person
+}
+
+// We've modeled this as Dog > person in javascript
+// And Person is jsonapiType "people"
+// But the server defined the relationship as "owner"
+Dog.includes("owner").merge({owner:Person.limitedFields()})
+
+
constDog=ApplicationRecord.extend({
+ // ... code ...
+ methods:{
+ person:belongsTo()
+ }
+})
+
+// We've modeled this as Dog > person in javascript
+// And Person is jsonapiType "people"
+// But the server defined the relationship as "owner"
+Dog.includes("owner").merge({owner:Person.limitedFields()})
Use #stats() to request statistics. Access stats within meta:
+
+
+
+ Typescript
+
+
+ Javascript
+
+
+
+
+
+
let{data}=awaitPost.stats({total:"count"}).all()
+data.meta.stats.total.count// the total count
+
+
Post.stats({total:"count"}).all().then(function(response){
+ response.meta.stats.total.count// the total count
+})
+
+
+
+
/posts?stats[total]=count
+
+
+
Stats are always independent of pagination. If you request the total count, you’ll get the total count even if you’re limiting to 10 per page. This means to get only statistics - avoid returning Post instances altogether - simply request 0 results per page:
+
+
+
+ Typescript
+
+
+ Javascript
+
+
+
+
+
+
let{data}=awaitPost.per(0)stats({total:"count"}).all()
+data.meta.stats.total.count// the total count
+
+
Post
+ .per(0)
+ .stats({total:"count"})
+ .all().then(function(response){
+ response.meta.stats.total.count// the total count
+ })
You may have encountered state management libraries like Flux,
+Redux or Vuex. These are fantastic libraries, but you likely won’t need them with JSORM. As a full-fledged model layer, JSORM manages state for you, automatically.
+
+
If you opt-in to this feature:
+
+
ApplicationRecord.sync=true
+
+
Instances will sync up whenever the server tells us about updated state.
+Consider the scenario where an instance is initially loaded, then separately polled in the background:
Note that our poll() function never assigns or updates person.
+But if the server returns an updated name attribute, person.name
+will be automatically updated. This is true even if person.name
+is bound in 17 different nested components.
+
+
Instances can still update their attributes independently - we only sync
+when the server returns updated data:
Under the hood, instances are listening for updates from a central data
+store. This means that you’ll want to remove listeners whenever you no
+longer need the instance - otherwise it will never be garbage collected
+properly. To remove a listener:
+
+
instance.unlisten()
+
+
In practice, when developing in a SPA, you’ll want to #unlisten()
+whenever a view is destroyed and model instances no longer need to be referenced. If
+you are using VueJS, this is done automatically by adding jsorm-vue
+to your application.
When an attribute has been modified, but has not yet been saved to the
+server, it is considered “dirty”. Use #isDirty() to see if any attribute is dirty, use the #changes() method to see all dirty attributes.
Similar to ActiveRecord, you can simply call #save() on a model
+instance. JSORM will create (POST) or update (PATCH) as needed.
+
+
#save() returns a Promise that will resolve a boolean - true
+when the server returns a 200-ish response code, false when the server
+returns a 422 response code (see
+validations). As always, anything else will
+reject the promise.
After saving, the instance will automatically pick up any
+server-assigned attributes:
+
+
+
+ Typescript
+
+
+ Javascript
+
+
+
+
+
+
letpost=newPost()
+ awaitpost.save()
+ post.id// server-assigned value
+ post.createdAt// server-assigned value
+
+
+
varpost=newPost();
+ post.save().then(function(success){
+ post.id// server-assigned value
+ post.createdAt// server-assigned value
+ });
+
+
+
+
+
If a Model was instantiated with data from the server, isPersisted
+will return true. This means that we can assign IDs on the client
+without any adverse behavior; we can also manually mark objects as
+persisted for testing purposes:
+
+
+
+ Typescript
+
+
+ Javascript
+
+
+
+
+
+
letblog=newBlog({id:123})
+ blog.isPersisted// false
+ awaitblog.save()// POST /blogs
+ blog.isPersisted// true
+ blog.id// 123
+
+ // Manually mark an instance as persisted
+ blog=newBlog({id:123})
+ blog.isPersisted=true
+ awaitblog.save()// PUT /blogs/123
+
+
+
varblog=newBlog({id:123});
+ blog.isPersisted// false
+ // POST /blogs
+ blog.save().then(function(response){
+ blog.isPersisted// true
+ blog.id// 123
+ });
+
+ // Manually mark an instance as persisted
+ varblog=newBlog({id:123});
+ blog.isPersisted=true
+ blog.save()// PUT /blogs/123
+
+
+
+
+
Notably, only dirty (changed) attributes will be sent to the server. This prevents race conditions and unexpected side-effects. In the following example, Post has attributes title, description, and createdAt:
+
+
+
+ Typescript
+
+
+ Javascript
+
+
+
+
+
+
letpost=(awaitPost.first())
+ post.title="updated"
+ // ONLY title sent to the server
+ awaitpost.save()
+ // Title is now synced with the server
+ post.description="updated"
+ // ONLY description sent to the server
+ awaitpost.save()
+
+
+
Post.first().then(function(response){
+ varpost=response.data;
+ post.title="updated";
+ // ONLY title sent to the server
+ post.save().then(function(response){
+ // Title is now synced with the server
+ post.description="updated";
+ // ONLY description sent to the server
+ post.save();
+ });
+ });
+
You can write a Model and all of its relationships in a single
+request. Keep in mind normal dirty tracking rules still apply - nothing
+is sent to the server unless it is dirty.
Use model.isMarkedForDestruction = true to delete the associated
+object. Use model.isMarkedForDisassociation = true to remove the association
+without deleting the underlying object:
+
+
+
+ Typescript
+
+
+ Javascript
+
+
+
+
+
+
letpost=(awaitPost.includes("comments").first()).data
+ post.comments[0].isMarkedForDestruction=true
+ post.comments[1].isMarkedForDisassociation=true
+
+ // destroys the first comment
+ // disassociates the second comment
+ awaitpost.save({with:"comments"})
+
+
+
Post.includes("comments").first().then(function(response){
+ varpost=response.data;
+ post.comments[0].isMarkedForDestruction=true;
+ post.comments[1].isMarkedForDisassociation=true;
+
+ // destroys the first comment
+ // disassociates the second comment
+ post.save({with:"comments"})
+ });
+
+
+
+
+
You may want to send only the id of the related object to the server - ensuring the models are associated without updating attributes by
+accident. Just add .id to the relationship name:
JSONAPI Suite is already set up to return validation errors with a
+422 response code and JSONAPI-compliant errors payload. Those errors will be automatically assigned, and removed on subsequent requests:
Let’s start with a classic Rails blog. We’ll use a template to handle some of the boilerplate. Just run this command and accept all the defaults for now:
+
+
$ rails new blog --api -m https://raw.githubusercontent.com/jsonapi-suite/rails_template/master/all.rb
+
+
Feel free to run git diff if you’re interested in the
+particulars; this is mostly just installing gems and including modules.
+
+
+
Note: if a network issue prevents you from pointing to this URL
+directly, you can download the file and and run this command as -m
+/path/to/all.rb
A Resource defines how to query and persist your Model. In other
+words: a Model is to the database as Resource is to the API. So
+first, let’s define our model:
+
+
$ bundle exec rails generate model Post title:string active:boolean
+$ bundle exec rake db:migrate
+
+
Now we can use the built-in generator to define our Resource,
+controller, and specs:
+
+
$ bundle exec rails g jsonapi:resource Post title:string active:boolean
+
+
You’ll see a number of files created. If you open each one, you’ll see
+comments explaining what’s going on. Head over to the
+tutorial for a more in-depth understanding. For now, let’s
+focus on two key concepts you’ll see over and over again: inputs (via
+strong_resources),
+and outputs (via jsonapi-rb).
+
+
Our API Inputs are defined in
+config/initializers/strong_resources.rb. You can think of these as
+strong parameter templates.
Our API Outputs are defined in
+app/serializers/serializable_post.rb. The DSL is very similar to
+active_model_serializers and full documentation can be found at jsonapi-rb.org:
We can seed data in two ways: the usual db/seeds.rb, or using an HTTP
+client. Using the client helps get your feet wet with client-side
+development, or you can avoid the detour and plow right ahead.
There are a variety of JSONAPI Clients
+out there. We’ll be using JSORM
+which is built to work with Suite-specific functionality like nested
+payloads. It can be used from the browser, but for now we’ll call
+it using a simple Node script.
This should be pretty straightforward if you’re familiar with
+ActiveRecord. We define Model objects, putting configuration on
+class attributes. We instatiating instances of those Models, and call
+save() to persist. For more information, see the JSORM Documentation.
+
+
Run the script:
+
+
$ node index.js
+
+
Now load http://localhost:3000/api/v1/posts. You should have 3 Posts in
+your database!
Now that we’ve defined our Resource and seeded some data, let’s see
+what query functionality we have. We’ve listed all Posts at
+http://localhost:3000/api/v1/posts. Let’s see what we can do:
+
+
+
Sort
+
+
By title, ascending:
+
+
URL: /api/v1/posts?sort=title
+
SQL: SELECT * FROM posts ORDER BY title ASC
+
+
+
By title, descending:
+
+
URL: /api/v1/posts?sort=-title
+
SQL: SELECT * FROM posts ORDER BY title DESC
+
+
+
+
+
Paginate:
+
+
2 Per page:
+
+
URL: /api/v1/posts?page[size]=2
+
SQL: SELECT * FROM posts LIMIT 2
+
+
+
2 Per page, second page:
+
+
URL: /api/v1/posts?page[size]=2&page[number]=2
+
SQL: SELECT * FROM posts LIMIT 2 OFFSET 2
+
+
+
+
+
Sparse Fieldsets:
+
+
Only render title, not active:
+
+
URL: /api/v1/posts?fields[posts]=title
+
SQL: SELECT * from posts (optimizing this query is on the roadmap)
JSONAPI Suite supports full querying of relationships (“fetch me this
+Post and 3 active Comments sorted by creation date”), as well as
+persistence (“save this Post and 3 Comments in a single request”).
Will use CommentResource for querying logic (so we can say things
+like “only return the latest 3 active comments”)
+
Uses an unfiltered base scope (Comment.all). If we wanted, we could
+do things like Comment.active here to ensure only active comments are
+ever returned.
+
+
+
You should now be able to hit /api/v1/comments with all the same
+functionality as before. We just need to seed data.
+
+
Start by clearing out your database:
+
+
$ bundle exec rake db:migrate:reset
+
+
Again, you can seed your data using a NodeJS client or the traditional
+db/seeds.rb.
Now let’s fetch a Post and filtered Comments in a single request: /api/v1/posts?include=comments.
+
+
Any logic in CommentResource is available to us. Let’s sort the
+comments by created_at descending: /api/v1/posts?include=comments&sort=-comments.created_at. This should work out-of-the-box.
We have a full CRUD API with robust querying functionality, and the
+ability to combine relationships for both reads and writes. But what
+happens when you need to customize the sorting logic? What about replacing
+ActiveRecord with an alternate persistence layer, or avoiding Rails
+altogether?
+
+
These are important topics that JSONAPI Suite was built to address. To
+learn more about advanced usage and customization, we suggest following
+the tutorial. There are also a number of how-tos on this
+site, a good one to start with is How
+to Use without ActiveRecord
factory_girl for
+ seeding our test database with fake data.
+
faker for generating fake values,
+ such as e-mail addresses, names, avatar URLs, etc.
+
database_cleaner
+ to ensure our fake data gets cleaned up between test runs.
+
+
+
By default we rescue exceptions and return a valid error response.
+In tests, this can be confusing - we probably want to raise errors in
+tests. So note our exception handling is disabled by default:
Finally, we need to define a Payload. Payloads use a
+factory_girl-style DSL to define expected JSON. A Payload compares a
+Model instance and JSON output, ensuring:
We can now run specs. Let’s start with the Post specs:
+
+
$ bundle exec rspec spec/api/v1/posts
+
+
You should see five specs, with one failing (spec/api/v1/posts/create_spec.rb),
+and one pending (spec/api/v1/posts/update_spec.rb).
+
+
The reason for the failure is simple: our payload defined in
+spec/payloads/post.rb specifies that a Post JSON should include the
+key title. However, that spec is currently creating a Post with no
+attributes…which means in the response JSON, title is null. null
+values will fail assert_payload unless elsewise configured.
+
+
So, let’s update our spec to POST attributes, not just an empty object:
Your specs should now pass. The only pending spec is due to a similar
+issue - we need to specify attributes in spec/api/v1/posts/update_spec.rb as
+well. Follow the comments in that file to apply a similar change.
+
+
You should now have 5 passing request specs! These specs spin up a fake
+server, then execute full-stack requests that hit the database and
+return JSON. You’re asserting that JSON matches predefined payloads,
+without nulls or unknown key/values.
+
+
Go ahead and make the same changes to Comment specs to get 10 passing
+request specs.
+
+
It’s up to you how far you’d like to go with testing. Should you add a
+new spec to spec/api/v1/posts/index_spec.rb every time you add a
+filter with allow_filter? This boils down to personal preference and
+tolerance of failures. Try adding a few specs following the generated
+patterns to get a feel for what’s right for you.
We can autodocument our code using swagger documentation. Documenting an endpoint is one line of code:
+
+
jsonapi_resource'/v1/employees'
+
+
Visit http://localhost:3000/api/docs to see the swagger documentation. Our custom UI will show all possible query parameters (including nested
+relationships), as well as schemas for request/responses:
If you find yourself repeatedly making customizations to a group of
+Resources and seek DRYer code, package those customizations into an
+Adapter. Here we’ll be starting from a previous example,
+ElasticSearch.
+
+
Adapters are simpler than you might think. It’s little more than
+copy-pasting those low-level customizations into a common class.
+
+
Start by creating lib/elasticsearch_adapter.rb. Cut/past the sorting,
+pagination, and #resolve overrides from EmployeeResource into the adapter,
+turning into def methods along the way:
Bounce your server. You can still hit the /api/v1/employees endpoint
+with the same sort and paginate functionality, but the code has been
+moved to an adapter.
We probably also want has_many-style macros to avoid writing similar
+allow_sideload code time after time. Start by specifying where this
+functionality is defined, and add a has_many macro:
+
+
moduleSideloading
+ defhas_many(association_name,
+ scope:,
+ resource:,
+ foreign_key:,
+ primary_key: :id,
+ &blk)
+ # our code will go here
+ instance_eval(&blk)ifblk
+ end
+end
+
+defsideloading_module
+ Sideloading
+end
+
+
The instance_eval is there so we can always drop down to a lower-level
+customization in our Resource.
+
+
We can basically cut/paste our existing sideload code and rewrite it as
+variables:
You can now remove any customizations from your Resource classes. You
+can continue to build the adapter, adding belongs_to, statistics, and
+more. View the adapter documentation for the full API.
Though we’ll be hitting elasticsearch in this
+example, remember that this is just an HTTP API underneath the hood. The
+same pattern applies to a variety of use cases.
+
+
First we need a Client for elasticsearch. Though you can feel free
+to use a variety of clients, this example will use trample.
+
+
Also keep in mind, we’ll be showing a one-off customization here. You
+probably want to extract this code into an
+Adapter if this is going to
+become a core component of your application.
+
+
Pre-JSONAPI Setup
+
+
Start by installing trample:
+
+
# Gemfile
+gem'trample_search',require: 'trample'
+
+
Tell searchkick that we want to
+index Employees and Positions:
In our controller, we need to pass a base scope. In ActiveRecord examples, we’d pass an ActiveRecord::Relation (e.g. Post.all). Let’s pass an instance
+of Trample::Search instead.
Since we are now passing a non-default base scope, we need to tell our
+Resource how to query and resolve this new scope. Start by switching to
+the pass-through adapter, and resolve using trample’s query API:
+
+
# app/resources/employee_resource.rb
+use_adapterJsonapiCompliable::Adapters::Null
+# ... code ...
+defresolve(scope)
+ scope.query!
+ scope.records.compact
+end
+
+# no belongs_to for now
+
+
You can now hit http://localhost:3000/api/v1/employees - the exact
+same payload is coming back, but is now sourced from elasticsearch!
View the Resource and Adapter documentation for additional overrides, like statistics.
+
+
The last step is adding the positions association. If we want
+has_many-style macros we need to create an
+Adapter, but for now
+let’s simply use the lower-level allow_sideload DSL. We need to define
+two functions: how to build a scope for the association, and how to
+associate the resulting objects:
+
+
# app/resources/employee_resource.rb
+allow_sideload:positions,resource: PositionResourcedo
+ scopedo|employees|
+ scope=PositionSearch.new
+ scope.condition(:employee_id).or(employees.map(&:id))
+ end
+
+ assigndo|employees,positions|
+ employees.eachdo|e|
+ e.positions=positions.select{|p|p.employee_id=e.id}
+ end
+ end
+end
+
+
Convert the PositionResource to use elasticsearch, just like we did
+for Employee:
We can now sideload positions - check out the results at
+http://localhost:3000/api/v1/employees?include=positions. We’re
+fetching employees and their corresponding positions in a single
+request, via elasticsearch. Any filters/changes/default sort/etc that
+apply to PositionResource can be re-used at this endpoint.
+
+
If this was a one-off section of our application, we can call this good
+enough and move on. But as we continue to use this pattern, it’s going
+to get monotonous writing the same filter overrides, allow_sideload
+wiring code, etc. To DRY up this code, we can package our changes into
+an Adapter.
This is a commonly requested example. Instead of using a full-fledged
+client like ActiveRecord or Trample, we’ll show low-level usage that
+could apply to a variety of HTTP clients.
+
+
Remember, we always start with a “base scope” and modify that scope
+depending on incoming request parameters. This same pattern could apply
+to simply ruby hashes.
+
+
defindex
+ render_jsonapi({})
+end
+
+
Let’s start by specifying a Null adapter - a pass-through adapter that
+won’t do anything without us explicitly overriding:
Every time we get a request to sort, paginate, etc we’ll need to modify
+our hash. Here we’ll simply merge parameters in the format our HTTP
+client will accept:
Finally, we need to tell the resorce how to resolve the query. In our
+case, this means passing the built-up parameters into a method on our
+HTTP client.
+
+
# app/resources/post_resource.rb
+
+# Remember, 'scope' here is a hash
+defresolve(scope)
+ results=MyHTTPClient.get(scope)
+ results.map{|r|Post.new(r)}
+end
+
+
Note that #resolve must return an array of Model instances. These
+can be simple POROs, as you see above.
If we found ourselves typing similar Resource code - always merging in
+the same paramters to the hash - we’d probably want to package all this
+up into an
+Adapter.
It’s important to note that ActiveRecord is nothing but a sensible
+default. Because you have full control over the
+query JSONAPI Suite can be
+used with any datastore, from MongoDB to HTTP service calls.
+
+
In this section, we’ll show examples customizing resource logic, then
+packaging that logic into reusable Adapters.
+
+
+
Keep in mind, multiple datastores can be blended in a single request.
+We can load Posts from a SQL database, and “sideload” Comments
+from MongoDB seamlessly.
Before we deploy our code to production, we want to ensure we
+aren’t introducing any backwards incompatibilities that will break
+existing clients. Of course, tests are our first line of defense here.
+But a developer could always simply update the test and introduce a
+backwards-incompatibility. This is why JSONAPI Suite comes with an
+additional backwards-compatibility check you can run in your Continuous
+Integration pipeline.
+
+
In the course of writing our application, we autodocumented
+with Swagger. That means our
+swagger.json is effectively a schema - a definition of
+attributes, types, query parameters and payloads. We can compare the
+swagger.json of a given commit to what’s running in production to
+see if any backwards-incompatibilities were introduced.
+
+
If you used our generator to set up your application, you’ll have noticed this line added to Rakefile:
+
+
require'jsonapi_swagger_helpers'
+
+
This allows us to run the rake task
+
+
rake swagger_diff['my_api','http://example.com']
+
+
This task will:
+
+
+
Pull down the schema from http://example.com/my_api/swagger.json.
+
Compare it to the swagger.json generated locally.
+
+
+
This uses swagger-diff underneath the hood. You’ll get helpful output noting any missing filters, incorrect types, or other backwards incompatibilities.
We want to follow the common best-practice of raising a specific error class:
+
+
raiseErrors::NotAuthorized
+
+
The problem is, this error would cause our server to return a 500
+status code, without much helpful detail. Instead, we want to
+customize our responses based on the error thrown.
+
+
Enter jsonapi_errorable, which provides a simple DSL to do just that:
JSONAPI Suite is a collection of ruby libraries that facilitate adhering
+to the jsonapi.org specification. At its heart is
+a query builder that supports fully nested reads and writes, adaptable
+to any datastore, from ActiveRecord to MongoDB to raw HTTP service calls. We’ll autodocument those endpoints for you with Swagger, provide an error handling pattern, and test everything will full-stack integration request specs.
+
+
If you just want to get rolling quickly, check out the
+Quickstart. For step-by-step examples,
+check out the Tutorial. Or, for
+client-side usage, check out JSORM.
+
+
For any questions not addressed here, feel free to ask in our Slack
+Chat.
JSONAPI Suite comes with a Rails Application Template to get you up and running quickly. To apply a template, you can pass either a URL or path to a file.
+
+
To generate a new application with the template:
+
+
$ rails new myapp --api -m https://raw.githubusercontent.com/jsonapi-suite/rails_template/master/all.rb
+
+
If needed, this command can be run by downloading all.rb and
+pointing to it on your filesystem:
+
+
$ rails new myapp --api -m /path/to/all.rb
+
+
Run git status to see what the generator did.
+
+
Breaking down the generator
+
+
The generator mostly installs gems and types some boilerplate for you.
+But it can be helpful to understand everything that’s going on and
+customize to your needs (these are all customizable defaults).
+Here’s a line-by-line breakdown to explain what’s going on. Use git
+status to follow along.
+
+
+
app/controllers/application_controller.rb
+
+
Mixes in JsonapiSuite::ControllerMixin. This includes relevant
+modules that decorate our controllers with methods like
+render_jsonapi.
+
Sets up global error handling, ensuring that we always render a
+JSONAPI-compliant errors payload.
+Errors are handled through a DSL provided by jsonapi_errorable - throw an error and use the DSL to customize response codes, messages, etc. In this code we’ll follow a common Rails pattern and respond with 404 from show endpoints when a record is not found in the datastore.
+
+
+
config/routes.rb
+
+
Configures routing so all our endpoints will be under /<api_namespace>/v1. The <api_namespace> is so you can point something like HAProxy to various microservices based on the path. The v1 sets up a simple versioning pattern.
+
Adds a docs resource. This for automatic Swagger documentation. Swagger requires a schema - swagger.json - that is generated from our DocsController. For more on this, see the Autodocumentation section.
+
+
+
spec/rails_helper.rb
+
+
Adds jsonapi_spec_helpers. This gives us helper methods like json_item and assert_payload that lower the overhead of dealing with verbose JSONAPI responses. See more in the Testing section.
+
JsonapiErrorable.disable! disables global error handling before
+each test. This is because we usually don’t want errors to be
+swallowed during tests. You can always turn it back on in a per-test
+basis with JsonapiErrorable.enable!
Mixes in factory_bot methods. This gives us syntactic sugar of saying create(:person) instead of FactoryBot.create(:person). See more in the Testing section.
+
Removes some fixture-specific configuration that is now handled by
+database_cleaner.
Requires the ActiveRecord adapter, which comes with the Suite. Comment this line if you’d like
+to avoid ActiveRecord. Learn more in the Adapters section.
+
+
+
config/initializers/strong_resources.rb
+
+
Stores templates that whitelist API inputs. Learn more in the
+Strong Resources section.
+
+
+
Rakefile
+
+
Requires the swagger helpers library in order to run
+backwards-compatibility checks against production.
+
+
+
Gemfile
+
+
We’ve added some dependencies, most of which are discussed in other
+sections:
+
+
jsonapi-rails: used for serialization, this is the
+rails-specific extension for jsonapi-rb
+
jsonapi_swagger_helpers: used automatically generating Swagger
+documentation.
+
jsonapi_spec_helpers: easily deal with complex JSONAPI responses
+in tests.
+
kaminari: Default pagination gem.
+
rspec-rails: Testing framework.
+
factory_bot_rails: For easily seeding data in tests.
+
faker: for randomizing factory data.
+
swagger-diff: for backwards-compatibility checks.
+
database_cleaner: for cleaning the DB between tests.
JSONAPI Suite comes with an ActiveRecord adapter. Though other
+adapters can mimic this same interface, here’s what you’ll get
+out-of-the-box. The SQL here is roughly the same as using #includes.
+
+
+
Note: make sure to whitelist associations in your serializers or nothing will render!
The only difference here is the foreign_key - we’re passing a hash instead of a symbol. taggings is our join table, and tag_id is the true foreign key.
+
+
This will work, and for simple many-to-many relationships you can move on. But what if we want to add the property primary, a boolean, to the taggings table? Since we hid this relationship from the API, how will clients access it?
+
+
As this is metadata about the relationship it should go on the meta section of the corresponding relationship object. While supporting such an approach is on the JSONAPI Suite roadmap, we haven’t done so yet.
+
+
For now, it might be best to simply expose the intermediate table to the API. Using a client like JSORM, the overhead of this approach is minimal.
Here an Employee belongs to a Workspace. Workspaces have
+different types - HomeOffice, Office, CoworkingSpace, etc. The
+employees table has workspace_id and workspace_type columns
+to support this relationship.
+
+
We may need to query each workspace_type differently - perhaps
+they live in separate tables (home_offices, coworking_spaces, etc). So, when fetching the relationship, we’ll need to group our Employees by workspace_type and query differently for each group:
Let’s say our API was returning 10 Employees, sideloading their corresponding Workspace. The underlying code would:
+
+
+
Fetch the employees
+
Group the employees by the given key: employees.group_by { |e|
+e.workspace_type }
+
Use the Office configuration for all Employees where
+workspace_type is Office, and use the HomeOffice configuration
+for all Employees where workspace_type is HomeOffice, and so
+forth.
Our Model. As Martin Fowler puts it, “An object model of the domain that incorporates both behavior and data.”. In this case we’re using ActiveRecord, though other model patterns can be used. This is the M of MVC.
+
+
+
config/routes.rb
+
+
Sets up the endpoint /api/v1/posts, per Rails Routing.
+
+
+
app/serializers/serializable_post.rb
+
+
Given a Model, how do we want to represent that model as JSON? We
+might want to avoid exposing certain attributes, normalize values,
+or compute something specific to the view. This is the V of MVC.
+
We use the excellent jsonapi-rb library for
+serialization. If you’re familiar with active_model_serializers, this code will look very familiar.
+
+
+
app/resources/post_resource.rb
+
+
A Resource holds the logic for querying and persisting our
+Models based on the JSONAPI request. Learn about Resources
+here.
This method does two things: builds and resolves the “base scope”, and passes relevant options to the serialization layer.
+
+
In other words, this lower-level code would be the equivalent:
+
+
scope=jsonapi_scope(Post.all)# build up the scope
+posts=scope.resolve# fire the query
+renderjson: posts,
+ fields: params[:fields].split(','),
+ bunch: 'of',
+ other: 'options'
+
+
+
We’ve started with a base scope - Post.all - and passed it into our
+Resource, which will
+modify the scope based on incoming parameters.
+
We’ve passed a number of boilerplate options to the underlying
+jsonapi-rb serialization library.
+
+
+
There are times we want to manually build and resolve the scope prior to
+calling render_jsonapi. The show action is one example.
+
+
The #show action
+
+
Our #show action fetches one specific post by ID, rather than a list of posts. To accomodate this, we manually build and resolve the scope instead of applying the default logic in #render_jsonapi:
It’s a common convention in Rails to return a 404 response code from
+the show action when a record is not found. Typically you’d raise and
+rescue ActiveRecord::RecordNotFound…but we want to be agnostic to
+the database. Instead:
You may need to change the default behavior or your API - perhaps you
+want a default of 10 per page instead of 20. JSONAPI Suite provides
+facilities that enable defaults that can be overridden - 10 per
+page, unless elsewise specified by the user.
These can all be overriden by the user. In other words, hitting
+/posts will only show active Posts, hitting
+/posts?filter[active]=false will show inactive Posts. The same applies
+for sorting and pagination.
+
+
A common pattern is for default filters to apply for all users, but
+allow overrides for administrators. You can use the :if option to
+restrict the override:
The opposite of a “sparse fieldset” is an “extra fieldset”. Perhaps you
+have an attribute that is computationally expensive and should only be
+returned when explicitly requested. Perhaps the majority of your clients
+need the same fields (and can share the same cache) but one client needs
+extra data (and you’ll accept the cache miss).
+
+
To request an extra field, just specify it in your serializer:
Filters are usually one-liners, with the logic delegated to an Adapter.
+
+
allow_filter:title
+
+
You can view allow_filter like a whitelist. We wouldn’t want to
+automatically support filters - otherwise sneaky users might filter our
+Employees to only those making a certain salary. Hence the whitelist.
The first will display only active posts, the second will display only
+inactive posts.
+
+
Filter Conventions
+
+
There are some common naming conventions for supporting more complex filters:
+
+
# greater than
+allow_filter:id_gt
+
+# greater than or equal to
+allow_filter:id_gte
+
+# less than
+allow_filter:id_lt
+
+# less than or equal to
+allow_filter:id_lte
+
+# prefix queries
+allow_filter:title_prefix
+
+# OR queries
+allow_filter:active_or# true or false
+
+
+
NOTE: AND queries are supported by default - just pass a
+comma-delimited list of values.
+
+
+
Filter Guards
+
+
You can conditionally allow filters based on runtime context.
+Let’s say only managers should be allowed to filter employees by salary:
Aliases mostly come into play when supporting backwards
+compatibility. Let’s say we originally called the filter fname then
+later wanted the more-expressive first_name. An alias allows is to
+keep a one-liner with the correct naming, while still responding correctly
+to fname:
+
+
allow_filter:first_name,aliases: [:fname]
+
+
Accessing Runtime Context
+
+
allow_filter can access runtime context (in Rails, the controller) as
+the last argument:
Note: we’d have to whitelist comments in our serializer as well.
+
+
+
To understand this code, we first have to realize that this is a Macro -
+code that is generating lower-level code for the purposes of removing
+boilerplate. Let’s understand the lower-level DSL before breaking
+down the macro.
This is the lower-level allow_sideload DSL. There are four things
+going on. To begin with:
+
+
+
We’ve whitelisted comments. Without this, the request would raise
+the error JsonapiCompliable::Errors::InvalidInclude. This ensures
+clients can’t arbitrarily pull back data that could introduce performance
+problems or security risks.
+
We’ve said, “when retrieving comments, re-use the logic defined in
+CommentResource”. This way all the filter, sorting, etc query logic
+at the /comments endpoint can be reused when sideloading comments from
+the /posts?include=comments endpoint.
+
+
+
That brings us to the scope and assign hooks. When querying a
+relationship, we need to answer two questions:
+
+
+
Given a list of parents (posts), how should we scope the request for
+children (comments)? This is the scope block. In a relational
+database, we’d usually scope based on foreign and primary keys.
+
Given a list of parents (posts) and a list of children (comments),
+how do you want to assign these objects together? This is the assign
+block. In a relational database, we’d usually compare foreign and
+primary keys.
+
+
+
In other words, the code would look similar to this for ActiveRecord:
Note that scope hasn’t actually fired a query - we take the result of
+this block and pass it to CommentResource so that further query logic
+(filtering, sorting, etc) can be applied and re-used across endpoints.
+
+
Of course, the code above would be very tedious to write by hand every
+time. That’s why we have Macros like has_many, belongs_to etc -
+configure only the parts you need, and avoid the boilerplate:
Given the above options, we can auto-generate allow_sideload code. You
+can always write allow_sideload directly if you have highly customized
+logic. You can also pass a block to the macros to customize:
+
+
# app/resources/post_resource.rb
+has_many:comments,
+ scope: ->{Comment.all},
+ resource: CommentResource,
+ foreign_key: :post_iddo
+ assigndo|posts,comments|
+ # some custom code to associate these objects
+ Post.associate(posts,comments)
+ end
+ end
+
+
Again, nested queries come for free. This code allows for nested queries
+like “give me the post, and its active comments”:
Occasionally you may need to normalize, format, or elsewise transform
+your Model into an effective JSON representation. To do this, pass a
+block to attribute and reference the underlying @object being
+serialized:
+
+
attribute:titledo
+ @object.title.upcase
+end
+
+
+
Why not methods like AMS? To avoid collissions with native ruby methods like tap.
+
+
+
Keep in mind all serializers have access to @context - the calling
+controller in Rails.
+
+
Conditional Fields
+
+
You may want to render a field based on runtime context - for instance,
+only show the salary field if the user is a manager. Keeping in mind
+that @context will always be available as the calling controller:
Statistics are useful and common. Consider a datagrid listing posts - we
+might want a “Total Posts” count displayed above the grid without firing
+an additional request. Notably, that statistic should take into
+account filtering, but should not take into account pagination.
+
+
You can whitelist stats in your Resource:
+
+
allow_stattotal: [:count]
+
+
And request them like so:
+
+
/posts?stats[total]=count
+
+
They will be returned in the meta section of the response:
Imagine if we had to implement the JSONAPI specification by hand, ensuring our endpoints supported sorting, pagination, filtering, etc. You’d start seeing something along these lines:
+
+
# No query has fired yet, this is a blank ActiveRecord scope
+posts=Post.all
+
+iftitle=params[:filter].try(:title)
+ # Alter the scope if we're filtering
+ posts=posts.where(title: title)
+end
+
+# ... etc ...
+
+ifsort=params[:sort]
+ # Alter the scope if we're sorting
+ sort_dir=:asc
+ ifsort.starts_with?('-')
+ sort_dir=:desc
+ end
+ sort_att=sort.split('-')[1]
+ posts=posts.order(sort_att=>sort_dir)
+end
+
+# ... etc ...
+
+renderjson: posts.to_a# Finally!
+
+
In other words…it’d be a gross mess, especially when dealing with
+inclusion of related
+resources or swapping
+datastores. But the basic pattern - starting with a scope and then
+decorating it based on incoming parameters - is incredibly powerful.
+
+
Instead of writing this code by hand every time, let’s move the
+boilerplate into a library and leave developers with only the part they
+care about - how to modify the scope:
This code lives in a Resource. All we’re doing here is specifying Procs that modify the scope, leaving boilerplate to the underlying jsonapi_suite library.
+
+
Of course, with ActiveRecord, you’d see the same logic repeated here over
+and over again. Let’s supply defaults to DRY up this code and end
+with:
+
+
# Whitelist the filter
+allow_filter:title
+
+
…but allow developers to override those defaults whenever they’d like:
+
+
allow_filter:titledo|scope,value|
+ scope.where(["title LIKE ?","#{value}%"])
+end
+
+sortdo|attribute,direction|
+ # ... your custom sort logic ...
+end
+
+
The important thing is: you still have full control of the query.
+This is why JSONAPI Suite can easily work with any datastore, from SQL
+to MongoDB to HTTP requests. The “behind-the-scenes defaults” are stored
+in an Adapter.
+Supply blocks for one-off customizations, or package them up into an
+Adapter once those customizations become commonplace.
+
+
By default, JSONAPI Suite comes with an ActiveRecordAdapter.
+
+
Scopes - a Generic Query-Building Pattern
+
+
If you look closely at the above examples, you can see our code breaks
+down into three key parts:
+
+
+
Step 1: Start with a “base scope” - a default query object.
+
Step 2: Modify that scope based on incoming parameters.
+
Step 3: Actually fire the query.
+
+
+
This pattern applies to any ORM or datastore. Let’s try it with an HTTP
+client that accepts a hash of options. A generic Rails controller might
+look something like:
+
+
defindex
+ # Step 1: Our "base scope"
+ scope={}
+
+ # Step 2: Modify that scope based on the request
+ iftitle=params[:filter].try(:[],:title)
+ scope[:title]=title
+ end
+
+ # Step 3: actually fire the request + build some models
+ # Post here is a PORO (plain old ruby object)
+ hashes=HTTP.get('/posts',scope)
+ posts=hashes.map{|attr|Post.new(attrs)}
+
+ # render
+ renderjson: posts
+end
+
+
So our JSONAPI Suite equivalent would be:
+
+
# Step 1: Define the base scope in the controller
+defindex
+ base_scope={}
+ # Pass the base scope to the resource, which will
+ # build + fire the query.
+ #
+ # Then, render the results.
+ render_jsonapi(base_scope)
+end
+
+
# app/resources/post_resource.rb
+#
+# Step 2: Modify the scope in the Resource
+allow_filter:titledo|scope,value|
+ scope[:title]=value
+end
+
+# Step 3: Actually fire the query
+# This method must return an array of Model instances
+defresolve(scope)
+ hashes=HTTP.get('/posts',scope)
+ hashes.map{|attr|Post.new(attrs)}
+end
+
+
Again, you can package this logic into an Adapter if you found yourself repeating the same logic
+over and over. Adapters DRY-up Resources.
+
+
This pattern applies to sorting, pagination, statistics and such as
+well - view the Reads documentation
+for more.
+
+
Associations
+
+
In the prior section, we noted the 3 key parts of query building. For
+associations, we need to answer 2 key questions:
+
+
+
Question 1: Given an array of parents, what should the “base scope” be
+in order to query only relevant children?
+
Question 2: Once we’ve resolved both the parents and the children, how do
+we associate these objects together?
+
+
+
Let’s switch back to vanilla ActiveRecord for a second. We’ve resolved
+the Posts and need to fetch the Comments. Here’s how we’d answer
+these questions:
+
+
allow_sideload:comments,resource: CommentResourcedo
+ # Question 1: What's a "base scope" that will return only
+ # relevant comments?
+ scopedo|posts|
+ Comment.where(post_id: posts.map(&:id))
+ end
+
+ # Question 2: How do we assign these objects together?
+ assigndo|posts,comments|
+ posts.eachdo|post|
+ post.comments=comments.select{|c|c.post_id==post.id}
+ end
+ end
+end
+
+
Just like in our prior sections, we can see the same logic would repeat
+over and over again each time we added a new relationship…with some slight tweaks based on
+has_many/belongs_to, non-standard foreign keys and such. So our
+default ActiveRecord adapter comes with macros that generate this
+lower-level code for us:
Let’s go back to HTTP calls. Imagine the CommentResource worked just
+like our HTTP-based PostResource from the prior section. Let’s see how
+those same questions would be answered:
+
+
# Step 1: What's a base scope that will return only
+# relevant comments?
+#
+# In the case of our HTTP client, the "base scope" is
+# nothing more than a ruby hash.
+#
+# Our final query would end up something like:
+#
+# HTTP.get('/comments', { post_id: [1,2,3] })
+scopedo|posts|
+ {post_id: posts.map(&:id)}
+end
+
+# Step 2: How do we assign these objects together?
+# This code is unchanged from the prior example
+assigndo|posts,comments|
+ posts.eachdo|post|
+ post.comments=comments.select{|c|c.post_id==post.id}
+ end
+end
+
+
The key lessons here:
+
+
+
scope must return a “base scope” that can be further modified.
+This way we can apply additional “deep query” logic - maybe we
+want to sort these comments - and re-use the query-building code
+defined in CommentResource. This allows the same logic at the
+/comments endpoint to apply to the /posts?include=comments
+endpoint.
+
If you’re not sure what the scope should be, look into the relevant
+Resource, particularly the #resolve method,
+to see how the query will actually be executed. If there is no
+#resolve method, it’s using the default of the relevant Adapter.
+
+
Typically, you’d define an ApplicationRecord who specifies the
+ Adapter. You’ll see this pattern if you use our generators.
+
+
+
Adapters can
+DRY-up this logic with has_many-style macros.
+
+
+
Writes
+
+
In the prior sections, we removed boilerplate and dropped down to only
+the important code of scope modification. The same basic premise applies to write operations as well. Rather than
+dealing with parsing the incoming payload and associating the graph of
+objects, Suite supplies hooks for just the parts you care about:
+actually persisting objects.
It’s highly encouraged you run these generators at least once, as
+you’ll get a bunch of helpful comments and understand baseline
+scenarios. You’re getting:
+
+
+
A controller (e.g. PostsController)
+
A route (/<api_namespace>/v1/posts)
+
A Resource (PostResource)
+
Integration spec boilerplate
+
+
including Factories
+
…and Payloads
+
+
+
A whitelist of incoming parameters for writes
+ (config/initializer/strong_resource.rb)
+
+
+
Type bin/rails g jsonapi:resource --help for details.
+
+
Wrapping Up
+
+
There’s more to learn about various ways Resources can be customized,
+but that’s the basic premise: no magic, just removal of boilerplate.
+
+
+
Note: the same Resource logic can be re-used across endpoints, to
+support logic like “fetch this Post and its Comments that are
+active”. Whether you’re sideloading comments from the /posts
+endpoint or accessing the /comments endpoint directly, the same
+Resource logic applies.
That’s it! Now only administrators can view hidden Posts.
+
+
Of course, this logic would only apply to the /posts endpoint and
+would not apply when we are sideloading from /blogs?include=posts. To
+ensure this logic runs all the time, add a default filter:
Given Employees with attribute under_performance_review, do not allow clients to find all employees under performance review.
+
+
+
Occasionally you need to guard filters based on the current user. Use
+the :if option on allow_filter. This will execute in the context of
+your controller:
This suite uses DSLs to specify inputs (strong_resources, filters, etc), and outputs (jsonapi-rb serializers).
+We can introspect that DSL to provide automatic documentation. Not only
+does this save a lot of time, it ensures your code and documentation are
+never out of sync.
+
+
Here we’ll be using swagger, a popular open-source
+documentation framework.
+
+
+
+
+
+
+To get this UI, we need to install two things: a controller that
+generates a schema (swagger.json), and a static website
+in public. Our generator installs these dependencies:
+
+
+ Note: here we’re moving jsonapi_spec_helpers out of the
+ test-specific bundle group. Introspecting spec helpers is part of
+ autodocumenting, so we’ll require them manually when our documentation
+ controller is loaded.
+
+
+
The generator also installs a Swagger UI in your Rails app’s public
+directory. If you haven’t already done so:
Our documentation will be accessible at /api/docs, so we put the files
+in public/api/docs. You may want a different directory depending on
+your own routing rules. In either case, our next step is to edit
+index.html: make sure any javascript and css has the correct URL.
+There are also a few configuration options, such as providing a link to
+Github.
If you want additional attribute-level documentation, you can add this
+to your spec payloads:
+
+
key(:name,String,description: 'The full name, e.g. "John Doe"')
+
+
+Will give you an output similar to:
+
+
+
+
+
+
+
+
Authentication
+
+
Your site may require authentication - for instance, sending a
+Authorization header in every request. We suggest using something like
+Request.ly to modify headers for every request to a given URL.
Validating verbose JSON API responses in tests can be a pain. We could
+use something like json_matchers to validate a schema, but we hope to do one better - let’s validate full payloads with a few simple helpers, using full-stack rspec request specs.
+
+
Let’s say we’re testing the show action of our employees controller, sideloading the employee’s department. Follow the Quickstart to make sure your rails_helper.rb is setup correctly first.
+
+
Let’s say we’re testing the show action of our employees controller, sideloading the employee’s department.
+
+
Let’s begin with vanilla RSpec of what the test might look like:
The name of a payload we’ve defined (we haven’t done this yet).
+
The record we want to compare against
+
The relevant slice of json. json_item and json_includes are
+helpful methods to target the right slice. You can see all helpers in
+the documentation for jsonapi_spec_helpers.
+
+
+
OK, so we want to take a record, response JSON, and compare them against
+something pre-defined. Let’s write those definitions; they look very similar to
+something you’d write for factory_girl:
Optionally, validate against a type as well. If both the expected and
+actual values match, but are the incorrect type, the test will fail:
+
+
key(:salary,Integer)
+
+
You can also customize/override payloads at runtime in your test. Let’s
+say we only serialize salary when the current user is an admin. Your
+test could look something like:
Here’s the code for a JSONAPI endpoint that supports creating,
+updating, and deleting resources, complete with validation errors. Keep in mind a small modification will
+enable nested creates/updates/deletes/disassociations as well.
+
+
We’ll be adding on to the code from the Basic
+Reads section.
We’ll expand on these topics in the rest of the “Writes” section.
+
+
Delegating Logic to Resources
+
+
With read operations, we supply hooks, essentially asking the developer
+“How do you want to modify the scope when a sort parameter comes in? How
+about when the title filter comes in?”.
+
+
The same logic applies to write operations - but instead of “how do you
+want to modify the scope?” the question is “how do you want to persist
+this data”?
Here we’ve update the Account and associated Person in a single
+request. You’ll see this is nothing more than a mirror of a
+“sideloading” payload, with one key addition - because HTTP verbs only apply to the top-level resource, we add a method key for all associated resources. Because we’re sticking to the convention of pairing a Resource with a verb, we call this “REST with Relationships”. Verbs can be one of create, update, destroy or disassociate.
There’s one final concept in sideposting, specific to create. We need
+to tell our clients how to update an in-memory object with the
+newly-minted id from the server. To do this, we pass temp-id (a random uuid) in the
+request instead of id:
+
+
# create an Account with a Person in a single request
+{
+ data: {
+ type"accounts",
+ attributes: {name: "new account"},
+ relationships: {
+ people: {
+ data: [
+ {:'temp-id'=>"abc123",type: "people"}
+ ]
+ }
+ },
+ included: [
+ {
+ type: "people",
+ :"temp-id"=>"abc123",
+ attributes: {name: "John Doe"}
+ }
+ ]
+}
+
+
Clients like JSORM will handle this for you
+automatically.
Side effects scenarios come up often. What if we want to send an email
+notification every time a Comment is created?
+
+
It’s important to note that there are three overall categories of side effects,
+and each requires a different solution:
+
+
+
Side effects internal to the Model: For example, setting a
+published_at attribute.
+
Side effects that should only occur on a specific request: For
+example, only send an email update if we’re creating a Post for the
+first time, at the /posts endpoint.
+
Side effects that should occur on every type of request: For
+example, send an email notification every time a Comment is created -
+but not updated - whether it was created at the /comments endpoint or sideposted at the
+/posts endpoint.
+
+
+
Internal Side-Effects
+
+
For the first scenario, it’s OK to use ActiveRecord callbacks (or the
+equivalent functionality in a different ORM):
+
+
# app/models/user.rb
+classPost<ApplicationRecord
+ before_save:set_published_at,
+ on: :update,
+ if: :publishing?
+
+ private
+
+ defset_published_at
+ self.published_at=Time.now
+ end
+
+ defpublishing?
+ status_changed?&&status=='published'
+ end
+end
+
+
Side-Effects on Specific Action
+
+
Just like you would with vanilla Rails, use the controller. Here we’ll
+only send an email to our subscribers when the Post is first created.
+Keep in mind we could also “sidepost” Post objects at the /blogs
+endpoint, but this will only fire at the /posts endpoint.
Let’s add some special logging every time we create a Post. Note this
+will fire every time we create a Post - whether we create it at the
+/posts endpoint or “sidepost” at the /blogs endpoint.
+
+
We do not need to worry about this side-effect in our model specs,
+or rake tasks, as the functionality is only relevant to the API.
+
+
Edit your Resource:
+
+
# app/resources/post_resource.rb
+defcreate(attributes)
+ Rails.logger.info"Post begin created by #{context.current_user.email}..."
+ super
+ Rails.logger.info"Success!"
+end
Rails 4 introduced the concept of Strong Parameters, a way to whitelist incoming parameters for a given write operation. The folks at Zendesk took it a step further with Stronger Parameters, which added type-checking to the strong parameter checks.
+
+
This works well for traditional REST endpoints that can put the logic in
+the controller. But JSONAPI Suite endpoints can “sidepost” objects at
+multiple endpoints - we might save a Person at the /people endpoint,
+but also sidepost from the /accounts endpoint. The strong parameters
+logic would need to be duplicated across controllers.
+
+
Enter Strong Resources. Define whitelist templates in one place, and re-use them across your application:
Now, whenever we POST or PUT to /accounts, the request
+attributes must come in this format. If an extra attribute is given -
+perhaps a read-only rate_limit attribute - the request will be
+rejected. If active comes in as a string instead of a boolean, the
+request will be rejected.
+
+
Let’s sidepost a Person record to the /accounts endpoint:
We can now sidepost Person records - via the people relationship -
+to the /accounts endpoint. If the Person attributes don’t match the
+:person strong resource template, the request will be rejected.
+
+
By default, we only allow create and update of associations, but you
+can opt-in to destroy and disassociate as well:
There are a variety of ways to customize strong resource templates -
+like allowing certain parameters only on update but not create. Head
+over to the strong_resources documentation for a more in-depth
+overview.
+
+
+Note: a common issue is allowing an input to be null. You can define your own types, or
+
+
After we’ve run the persistence logic - but before we close the
+transaction - we check model.errors. If errors are present anywhere in
+the graph, we rollback the transaction and return a JSONAPI-compliant Error response:
This is true for nested write operations as well. Let’s say we were
+saving an Employee and their Positions in a single request, but one
+of the positions had a validation error on a missing title:
Note: each of these repos has a separate branch for step one,
+step two, etc. View the latest branch for final code, or follow along
+step-by-step
+
+
The intent is to illustrate a variety of real-world use cases:
+
+
+
Turning 3 database tables into one cohesive search grid.
+
Customizing SQL queries.
+
Ability to filter, sort, and paginate data.
+
Total count
+
Custom Serialization
+
Nested CRUD of relationships, including validation errors.
+
+
+
Note: to better understand the underlying code, we’ll be avoiding use
+of generators. Head to the Quickstart for a guide on how
+to automate much of the legwork here.
We’ll be creating an API for an Employee Directory. An Employee has many Positions (one of which is the current position), and a Position belongs to a Department.
+
+
Let’s start with a basic foundation: an index endpoint (list multiple
+entities) and a show (single entity) endpoint for an Employee model.
A note on testing: these are full-stack request specs. We seed the database using factory_girl, randomizing data with faker, then assert on the resulting JSON using spec helpers.
+
+
You won’t have to write all the tests you see here, some are simply for demonstrating the functionality.
Sorting comes for free, but here’s a test for it. Decide as a team if we actually need to write a spec here, or if it’s considered tested within the libraries.
Sometimes we need more than a simple ORDER BY clause, for example maybe we need to join on another table. In this example, we switch from Postgres’s default case-sensitive query to a case in-sensitive one…but only for the first_name field.
Let’s say we wanted the employee’s age to serialize Thirty-Two instead of 32 in JSON. Here we use a library to get the friendly-word doppleganger, and change the test to recognize this custom logic.
In prior steps we created PositionResource and DepartmentResource. These objects may have custom sort logic, filter whitelists, etc - this configuration can be re-used if we need to add /api/v1/positions and /api/v1/departments endpoints.
In this example we add global error handling, so any random error will return a JSONAPI-compatible error response. Then we customize that response for a specific scenario (the requested employee does not exist).
The biggest problem with strong_parameters is that we might want to create an employee from the /employees endpoint, or we might want to create a position with an employee at the same time from /positions. Maintaining the same strong parameter hash across a number of places is difficult.
+
+
Instead we use strong_resources to define the parameter template once, and re-use. This has the added benefit of being built on top of stronger_parameters, which gives us type checking and coercion.
So far we’ve shown ActiveRecord. What if we wanted to use a different ORM, or ElasticSearch? What if we wanted ‘side effects’ such as “send a confirmation email after creating the user”?
+
+
This code shows how to customize create/update/destroy. In this example we’re simply logging the action, but you could do whatever you want here as long as you return an instance of the object. Just like with reads, if any of this code becomes duplicative across Resource objects you could move it into a common Adapter.
Think Rails’ accepts_nested_attributes_for, but not coupled to Rails or ActiveRecord. Here we create an Employee, a Position for the employee, and a Department for the position in one call. This is helpful when dealing with nested forms!
+
+
Once again, note how our strong_resources can be shared across controllers.
As you can see, we’re delving into a lower-level DSL to customize. You
+probably want to package up these changes into an Adapter. The ActiveRecord adapter is simple packaging up similar low-level defaults. Your app may require an HTTPAdapter or ServiceAdapter, or you can make one-off customizations as shown above.
There are number of jsonapi clients in a variety of languages. Here we’ll be using JSORM - an ActiveRecord-style ORM that can be used from Node or the browser. It’s been custom-built to work with JSONAPI Suite enhancements.
+
+
This will fetch an employee with id 123, their last 3 positions where the title starts with ‘dev’, and the departments for those positions.
+
+
We’ll use typescript for this example, though we could use vanilla JS just as well. First define our models (additional client-side business logic can go in these classes):
JSORM can be used with the client-side framework of your choice. To give an example of real-world usage, we’ve created a demo application using
+VueJS. Vue is lightweight and provides the
+bare-bones we need to illustrate JSONAPI and JSORM in action.
We’ll start by defining our models, which should look very familiar if
+you’ve worked with ActiveRecord. Again, see the JSORM
+documentation if any of this looks confusing to you.
We’ll add a simple data grid to our page listing all Employees. This
+will turn into a search grid, but for now we’re simply loading employees
+via Employee.all()
Here we’ve added a currentPosition relationship to avoid fetching
+excess data from the server. When the page loads we fetch the
+employees, current positions for those employees, and the departments for those positions.
Here we’ve added a bit of state to the page, query, which will be
+bound to the form inputs. We pass query to Employee.where to
+successfully query employees by first and last name.
Just like with filters, we add a bit of state to our page to track
+sorting parameters. When the user clicks a table header, we’ll update
+that state and pass it to our query.
A JSORM promise returns a response object with a data key - the
+model instance(s) we’ve been referencing so far. It also returns a
+meta key that reflects the meta section of the JSONAPI response.
+
+
Here we’ll request the total count of employees in our query to the
+server, grab that total count from meta, and bind it to the page as
+totalCount
Building on what we’ve already done, we can apply a similar pattern for
+pagination. We add the currentPage state and alter it when the user
+clicks pagination links. We use currentPage and totalCount to figure
+out if we should display previous/next page links.
Our form will submit employees, positions and departments in a single
+request. We associate a position to an existing department through a
+select dropdown. To populate that dropdown, we fetch all departments
+from the server and bind it to the select.
This step simply binds our instantiated models to the form. When the
+form is submitted, we save everything in a single one-line request.
+
+
After the form submission, we could edit the form and submit again -
+JSORM will know to PATCH an update to the appropriate URL
+automatically.
+
+
Note we could also edit our search to immediately reflect the new
+data…but this is more Vue-specific than anything to do with JSORM, so
+we’ll hold off until the last step.
Our server-side code will automatically handle validation errors and
+give us a well-formatted response. JSORM will read that response and
+automatically apply error objects to our model instances. Here we
+display simple error messages without involving any JS code.
This step adds buttons to our form that will add and remove positions
+for a given employee. To add, we simply push a new Employee onto the
+relationship array. To remove, we set isMarkedForDestruction = true,
+which allows for “unsaved deleted records”. This follows a similar
+pattern to one introduced in Ember Data, explained here.
+
+
Note that if we wanted to disassociate the position rather than
+destroying the underlying record, we could use
+position.isMarkedForDisassociation = true.
Our final step adds some Vue-specific functionality - we add an
+EventBus to allow selecting an employee from the grid and binding it
+to the form, and refresh the search grid after each form submission.
+ Whether you want to fill this paragraph with some text like I'm doing right now, this place is perfect to describe some features or anything you want - React has a complete solution for you.
+
+
+ You have complete control over the look & feel of your website, we offer the best quality so you take your site up and running in no time.
+
+
+
+
+
+
+
+
+
+
+
+
You don't need to have any advanced technical
+
+ Whether you want to fill this paragraph with some text like I'm doing right now, this place is perfect to describe some features or anything you want - React has a complete solution for you.
+
+
+
+
+
+
You don't need to have any advanced technical
+
+ Whether you want to fill this paragraph with some text like I'm doing right now, this place is perfect to describe some features or anything you want - React has a complete solution for you.
+
+
+ You have complete control over the look & feel of your website, we offer the best quality so you take your site up and running in no time.
+
+
+
+
+
+
+
+
+
+
+
+
You don't need to have any advanced technical
+
+ Whether you want to fill this paragraph with some text like I'm doing right now, this place is perfect to describe some features or anything you want - React has a complete solution for you.
+
+
+
+
+
+
+
+
diff --git a/how-to-add-defaults.md b/how-to-add-defaults.md
new file mode 100644
index 0000000..a2f710a
--- /dev/null
+++ b/how-to-add-defaults.md
@@ -0,0 +1,48 @@
+---
+layout: page
+---
+
+How to Add Defaults
+==========
+
+You may need to change the default behavior or your API - perhaps you
+want a default of 10 per page instead of 20. JSONAPI Suite provides
+facilities that enable ***defaults*** that can be ***overridden*** - 10 per
+page, unless elsewise specified by the user.
+
+You can see these defaults in the [Resource documentation](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Resource.html):
+
+{% highlight ruby %}
+default_filter :active do |scope|
+ scope.where(active: true)
+end
+
+default_page_size(10)
+
+default_sort([{ created_at: :desc }])
+{% endhighlight %}
+
+These can all be overriden by the user. In other words, hitting
+`/posts` will only show active `Post`s, hitting
+`/posts?filter[active]=false` will show inactive `Post`s. The same applies
+for sorting and pagination.
+
+A common pattern is for default filters to apply for all users, but
+allow overrides for administrators. You can use the `:if` option to
+restrict the override:
+
+{% highlight ruby %}
+# app/resources/post_resource.rb
+allow_filter :active, if: :admin?
+
+# app/controllers/posts_controller.rb
+def admin?
+ current_user.admin?
+end
+{% endhighlight %}
+
+Now the default behavior is to view only active `Post`s, but
+*administrators* can override this default.
+
+
+
diff --git a/how-to-autodocument.md b/how-to-autodocument.md
new file mode 100644
index 0000000..c050f31
--- /dev/null
+++ b/how-to-autodocument.md
@@ -0,0 +1,143 @@
+---
+layout: page
+---
+
+How to Autodocument with Swagger
+==========
+
+You can follow the steps below, or [view the diff on github](https://github.com/jsonapi-suite/employee_directory/compare/step_23_disassociation...step_24_autodocumentation)
+
+This suite uses DSLs to specify inputs (`strong_resources`, filters, etc), and outputs (`jsonapi-rb` serializers).
+We can introspect that DSL to provide automatic documentation. Not only
+does this save a lot of time, it ensures your code and documentation are
+never out of sync.
+
+Here we'll be using [swagger](https://swagger.io), a popular open-source
+documentation framework.
+
+
+
+
+
+
+To get this UI, we need to install two things: a controller that
+generates a schema (`swagger.json`), and a static website
+in `public`. Let's start by adding dependencies:
+
+
+
+{% highlight ruby %}
+# Gemfile
+# Below 'jsonapi_suite'
+gem 'jsonapi_spec_helpers'
+gem 'jsonapi_swagger_helpers'
+{% endhighlight %}
+
+
+ Note: here we're moving `jsonapi_spec_helpers` out of the
+ test-specific bundle group. Introspecting spec helpers is part of
+ autodocumenting, so we'll `require` them manually when our documentation
+ controller is loaded.
+
+
+Our web UI is a Glimmer App,
+but we'll only deal with the already-compiled static files glimmer builds.
+Let's copy those files and expose them to the web by putting them in
+`public`:
+
+{% highlight bash %}
+$ mkdir -p public/api/docs && cd public/api/docs
+$ git clone https://github.com/jsonapi-suite/swagger-ui.git && cp swagger-ui/prod-dist/* . && rm -rf swagger-ui
+{% endhighlight %}
+
+Our documentation will be accessible at `/api/docs`, so we put the files
+in `public/api/docs`. You may want a different directory depending on
+your own routing rules. In either case, our next step is to edit
+`index.html`: make sure any javascript and css has the correct URL.
+There are also a few configuration options, such as providing a link to
+Github.
+
+{% highlight javascript %}
+window.CONFIG = {
+ githubURL: "http://github.com/user/repo",
+ basePath: "/api" // basePath/swagger.json, basePath/v1/employees, etc
+}
+{% endhighlight %}
+
+This static website will make a request to `/api/swagger.json`. Let's
+build that endpoint now:
+
+{% highlight bash %}
+$ touch app/controllers/docs_controller.rb
+{% endhighlight %}
+
+{% highlight ruby %}
+# config/routes.rb
+scope path: '/api' do
+ resources :docs, only: [:index], path: '/swagger'
+ # ... code ...
+end
+{% endhighlight %}
+
+Our `DocsController` uses [swagger-blocks](https://github.com/fotinakis/swagger-blocks) to generate
+the swagger schema. Here's the minimal setup needed to configure
+swagger:
+
+{% highlight ruby %}
+require 'jsonapi_swagger_helpers'
+
+class DocsController < ActionController::API
+ include JsonapiSwaggerHelpers::DocsControllerMixin
+
+ swagger_root do
+ key :swagger, '2.0'
+ info do
+ key :version, '1.0.0'
+ key :title, ''
+ key :description, ''
+ contact do
+ key :name, ''
+ end
+ end
+ key :basePath, '/api'
+ key :consumes, ['application/json']
+ key :produces, ['application/json']
+ end
+end
+{% endhighlight %}
+
+That's it. Now, every time we add an endpoint, we can autodocument with
+one line of code (below the `swagger_root` block):
+
+{% highlight ruby %}
+jsonapi_resource '/v1/employees'
+{% endhighlight %}
+
+This endpoint will be introspected for all RESTful actions, outputting
+the full configuration. There are a few customization options:
+
+{% highlight ruby %}
+jsonapi_resource '/v1/employees',
+ only: [:create, :index],
+ except: [:destroy],
+ descriptions: {
+ index: "Some additional documentation"
+ }
+{% endhighlight %}
+
+If you want additional attribute-level documentation, you can add this
+to your spec payloads:
+
+{% highlight ruby %}
+key(:name, String, description: 'The full name, e.g. "John Doe"')
+{% endhighlight %}
+
+
+Will give you an output similar to:
+
+
+
+
+
+
+
diff --git a/how-to-build-an-adapter.md b/how-to-build-an-adapter.md
new file mode 100644
index 0000000..a4ad488
--- /dev/null
+++ b/how-to-build-an-adapter.md
@@ -0,0 +1,118 @@
+---
+layout: page
+---
+
+Building an Adapter
+==========
+
+If you find yourself repeatedly making customizations to a group of
+`Resource`s and seek DRYer code, package those customizations into an
+`Adapter`. Here we'll be starting from a previous how-to, [How to Use
+Alternate ORMs](how-to-use-without-activerecord).
+
+You can follow along with the below code snippets, or [view the diff on
+github](https://github.com/jsonapi-suite/employee_directory/compare/step_23_disassociation...elasticsearch_adapter).
+
+Adapters are simpler than you might think. It's little more than
+copy-pasting those low-level customizations into a common class.
+
+Start by creating `lib/elasticsearch_adapter.rb`. Cut/past the sorting,
+pagination, and `#resolve` overrides from `EmployeeResource` into the adapter,
+turning into `def` methods along the way:
+
+{% highlight ruby %}
+# lib/elasticsearch_adapter.rb
+class ElasticsearchAdapter
+ def paginate(scope, current_page, per_page)
+ scope.metadata.pagination.current_page = current_page
+ scope.metadata.pagination.per_page = per_page
+ scope
+ end
+
+ def order(scope, att, dir)
+ scope.metadata.sort = [{att: att, dir: dir}]
+ scope
+ end
+
+ def resolve(scope)
+ scope.query!
+ scope.results
+ end
+end
+{% endhighlight %}
+
+Ensure our adapter gets loaded:
+
+{% highlight ruby %}
+# config/initializers/jsonapi.rb
+require 'elasticsearch_adapter'
+{% endhighlight %}
+
+And switch to that adapter in `EmployeeResource`:
+
+{% highlight ruby %}
+use_adapter ElasticsearchAdapter
+{% endhighlight %}
+
+Bounce your server. You can still hit the `/api/v1/employees` endpoint
+with the same sort and paginate functionality, but the code has been
+moved to an adapter.
+
+Let's ensure our users can filter as well:
+
+{% highlight ruby %}
+def filter(scope, att, val)
+ scope.condition(att).eq(val)
+end
+{% endhighlight %}
+
+For all the methods and functionality an adapter supports, see the
+[Adapter documenation](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Adapters/Abstract.html).
+
+We probably also want `has_many`-style macros to avoid writing similar
+`allow_sideload` code time after time. Start by specifying where this
+functionality is defined, and add a `has_many` macro:
+
+{% highlight ruby %}
+module Sideloading
+ def has_many(association_name,
+ scope:,
+ resource:,
+ foreign_key:,
+ primary_key: :id,
+ &blk)
+ # our code will go here
+ instance_eval(&blk) if blk
+ end
+end
+
+def sideloading_module
+ Sideloading
+end
+{% endhighlight %}
+
+The `instance_eval` is there so we can always drop down to a lower-level
+customization in our `Resource`.
+
+We can basically cut/paste our existing sideload code and rewrite it as
+variables:
+
+{% highlight ruby %}
+scope do |parents|
+ parent_ids = parents.map { |p| p.send(primary_key) }
+ scope.call.condition(foreign_key).or(parent_ids.uniq.compact)
+end
+
+assign do |parents, children|
+ parents.each do |p|
+ relevant_children = children.select do |c|
+ c.send(foreign_key) == p.send(primary_key)
+ end
+ p.send(:"#{association_name}=", relevant_children)
+ end
+end
+{% endhighlight %}
+
+You can now remove any customizations from your `Resource` classes. You
+can continue to build the adapter, adding `belongs_to`, statistics, and
+more. [View the adapter documentation for the full API](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Adapters/Abstract.html).
diff --git a/how-to-cause-side-effects.md b/how-to-cause-side-effects.md
new file mode 100644
index 0000000..db35fa5
--- /dev/null
+++ b/how-to-cause-side-effects.md
@@ -0,0 +1,88 @@
+---
+layout: page
+---
+
+Side Effects
+==========
+
+Side effects scenarios come up often. What if we want to send an email
+notification every time a `Comment` is created?
+
+It's important to note that there are three overall categories of side effects,
+and each requires a different solution:
+
+* Side effects internal to the `Model`: For example, setting a
+ `published_at` attribute.
+* Side effects that should only occur on a specific request: For
+ example, only send an email update if we're creating a `Post` for the
+ first time, at the `/posts` endpoint.
+* Side effects that should occur on every *type* of request: For
+example, send an email notification every time a `Comment` is created -
+but not updated - whether it was created at the `/comments` endpoint or sideposted at the
+`/posts` endpoint.
+
+#### Internal Side-Effects
+
+For the first scenario, it's OK to use `ActiveRecord` callbacks (or the
+equivalent functionality in a different ORM):
+
+{% highlight ruby %}
+# app/models/user.rb
+class Post < ApplicationRecord
+ before_save :set_published_at,
+ on: :update,
+ if: :publishing?
+
+ private
+
+ def set_published_at
+ self.published_at = Time.now
+ end
+
+ def publishing?
+ status_changed? && status == 'published'
+ end
+end
+{% endhighlight %}
+
+#### Side-Effects on Specific Action
+
+Just like you would with vanilla Rails, use the controller. Here we'll
+only send an email to our subscribers when the `Post` is first created.
+Keep in mind we could also "sidepost" `Post` objects at the `/blogs`
+endpoint, but this will only fire at the `/posts` endpoint.
+
+{% highlight ruby %}
+class PostsController < ApplicationController
+ def create
+ post, success = jsonapi_create.to_a
+
+ if success
+ PostMailer.published_email.deliver_later
+ render_jsonapi(post, scope: false)
+ else
+ render_errors_for(post)
+ end
+ end
+end
+{% endhighlight %}
+
+#### Side-Effects on Every Request of a Given Type
+
+Let's add some special logging every time we create a `Post`. Note this
+will fire *every* time we create a `Post` - whether we create it at the
+`/posts` endpoint or "sidepost" at the `/blogs` endpoint.
+
+We **do not** need to worry about this side-effect in our model specs,
+or rake tasks, as the functionality is only relevant to the API.
+
+Edit your `Resource`:
+
+{% highlight ruby %}
+# app/resources/post_resource.rb
+def create(attributes)
+ Rails.logger.info "Post begin created by #{context.current_user.email}..."
+ super
+ Rails.logger.info "Success!"
+end
+{% endhighlight %}
diff --git a/how-to-code-many-to-many-associations.md b/how-to-code-many-to-many-associations.md
new file mode 100644
index 0000000..5cef123
--- /dev/null
+++ b/how-to-code-many-to-many-associations.md
@@ -0,0 +1,39 @@
+---
+layout: page
+---
+
+Many-to-Many Associations
+=========================
+
+`has_and_belongs_to_many` are *possible*, but maybe not desirable
+depending on your use case. Following the example from the [tutorial](/tutorial#many-to-many),
+let's say an `Employee` has many `Team`s and a `Team` has many
+`Employee`s. We could wire-up our `EmployeeResource` like so:
+
+{% highlight ruby %}
+has_and_belongs_to_many :teams,
+ scope: -> { Team.all },
+ foreign_key: { employee_teams: :employee_id },
+ resource: TeamResource
+{% endhighlight %}
+
+The only difference here is the `foreign_key` - we're passing a hash
+instead of a symbol. `employee_teams` is our join table, and
+`employee_id` is the true foreign key.
+
+This will work, and for simple many-to-many relationships you can move
+on. But what if we want to add the property `primary`, a boolean, to the
+`employee_teams` table?
+
+As this is *metadata about the relationship* it should go on the `meta`
+section of the corresponding [relationship object](http://jsonapi.org/format/#document-resource-object-relationships).
+While supporting such an approach is on the JSONAPI Suite roadmap, many
+clients do not currently support this per-object level of functionality.
+
+For now, it might be best to simply expose the intermediate table to the
+API. Using a client like
+[JSORM](https://github.com/jsonapi-suite/jsorm), the overhead of this
+approach is minimal.
+
+
+
diff --git a/how-to-code-polymorphic-associations.md b/how-to-code-polymorphic-associations.md
new file mode 100644
index 0000000..125829e
--- /dev/null
+++ b/how-to-code-polymorphic-associations.md
@@ -0,0 +1,62 @@
+---
+layout: page
+---
+
+Polymorphic Associations
+=========================
+
+Let's say an `Employee` belongs to a `Workspace`. `Workspace`s have
+different `type`s - `HomeOffice`, `Office`, `CoworkingSpace`, etc.
+
+Assuming we've already set up our database, let's add the associations
+to our `Model`s:
+
+{% highlight ruby %}
+# app/models/employee.rb
+belongs_to :workspace, polymorphic: true
+{% endhighlight %}
+
+{% highlight ruby %}
+# app/models/workspace.rb
+has_many :employees, as: :workspace
+{% endhighlight %}
+
+Now we need to wire-up our resource. Usually you'd see something like
+`has_many` with a few options. But here, we may actually want to change
+our configuration based on `Workspace#type` - maybe each type of data is
+stored in a separate table, for instance.
+
+We want to pass the same configuration, but on a type-by-type basis. In
+other words, we need to group workspaces and define how to associate
+each group:
+
+{% highlight ruby %}
+# app/resources/employee_resource.rb
+polymorphic_belongs_to :workspace,
+ group_by: :workspace_type,
+ groups: {
+ 'Office' => {
+ scope: -> { Office.all },
+ resource: OfficeResource,
+ foreign_key: :workspace_id
+ },
+ 'HomeOffice' => {
+ scope: -> { HomeOffice.all },
+ resource: HomeOfficeResource,
+ foreign_key: :workspace_id
+ }
+ }
+{% endhighlight %}
+
+Let's say our API was returning 10 `Employees`, sideloading their
+corresponding `Workspace`. The underlying code would:
+
+* Fetch the employees
+* Group the employees by the given key: `employees.group_by { |e|
+ e.workspace_type }`
+* Use the `Office` configuration for all `Employee`s where
+ `workspace_type` is `Office`, and use the `HomeOffice` configuration
+for all `Employee`s where `workspace_type` is `HomeOffice`.
+
+
+
diff --git a/how-to-conditionally-render-fields.md b/how-to-conditionally-render-fields.md
new file mode 100644
index 0000000..5f55d59
--- /dev/null
+++ b/how-to-conditionally-render-fields.md
@@ -0,0 +1,52 @@
+---
+layout: page
+---
+
+Extra Attributes
+==========
+
+Of course, JSONAPI already has the concept of sparse fieldsets built-in.
+This behavior comes out-of-the-box at URLs like
+`/people?fields[people]=title,active`.
+
+Sometimes it's necessary to conditionally render an *extra* field as
+well. For instance, maybe rendering out the `net_worth` attribute is
+computationally expensive and not often requested.
+
+Let's add a simple *extra attribute*:
+
+{% highlight ruby %}
+# app/serializers/serializable_person.rb
+extra_attribute :net_worth do
+ 1_000_000
+end
+{% endhighlight %}
+
+This field will not be rendered when we hit `/people`. It will only be
+rendered when we hit `/people?extra_fields[people]=net_worth`. The URL
+signature is the same as [sparse fieldsets](http://jsonapi.org/format/#fetching-sparse-fieldsets).
+
+We may want to eager load some data, only when a specific extra field is
+requested. We can do that by customizing the `Resource`:
+
+{% highlight ruby %}
+# app/resources/person_resource.rb
+extra_field :net_worth do |scope|
+ scope.includes(:assets)
+end
+{% endhighlight %}
+
+We will now eager load assets only when the `net_worth` extra field is
+specified in the request.
+
+Finally, additional conditionals can still be applied:
+
+{% highlight ruby %}
+# app/serializers/serializable_person.rb
+extra_attribute :net_worth, if: proc { @context.allow_net_worth? } do
+{% endhighlight %}
+
+If using Rails, `@context` is your controller.
+
+
+
diff --git a/how-to-customize-error-responses.md b/how-to-customize-error-responses.md
new file mode 100644
index 0000000..c18430c
--- /dev/null
+++ b/how-to-customize-error-responses.md
@@ -0,0 +1,81 @@
+---
+layout: page
+---
+
+Customizing Error Responses
+==========
+
+Your application will automatically return a JSONAPI-compliant [error
+object](http://jsonapi.org/format/#errors) whenever an error is raised.
+That's due to this code in `ApplicationController`:
+
+{% highlight ruby %}
+# app/controllers/application_controller.rb
+rescue_from Exception do |e|
+ handle_exception(e)
+end
+{% endhighlight %}
+
+If we put `raise 'foo'` in a controller somewhere, we'd see the
+response:
+
+{% highlight ruby %}
+{
+ errors: [
+ code: 'internal_server_error',
+ status: '500',
+ title: 'Error',
+ detail: "We've notified our engineers and hope to address this issue shortly.",
+ meta: {}
+ ]
+}
+{% endhighlight %}
+
+This can all be customized. Let's say for all
+`ActiveRecord::RecordNotFound` errors we want a 404 response code, with
+the error `detail` providing a custom message:
+
+{% highlight ruby %}
+# app/controllers/application_controlle.rb
+register_exception ActiveRecord::RecordNotFound,
+ status: 422,
+ message: ->(e) { "Couldn't find record with id #{e.id}" }
+{% endhighlight %}
+
+Would output:
+
+{% highlight ruby %}
+{
+ errors: [
+ code: 'not_found',
+ status: '404',
+ title: 'Error',
+ detail: "Couldn't find record with id 123",
+ meta: {}
+ ]
+}
+{% endhighlight %}
+
+You can register exceptions in `ApplicationController`, or any subclass
+if you want a specific controller to handle a given error differently.
+
+For more customization options, see the [jsonapi_errorable](https://github.com/jsonapi-suite/jsonapi_errorable) gem.
+
+You may want your test suite to throw errors, instead of returning
+this friendly output. Configure this using `JsonapiErrorable.disable!`:
+
+{% highlight ruby %}
+# spec/rails_helper.rb
+config.before :each do
+ JsonapiErrorable.disable!
+end
+
+# enable for specific test
+it 'does something' do
+ JsonapiErrorable.enable!
+ # ... code ...
+end
+{% endhighlight %}
+
+
+
diff --git a/how-to-return-statistics.md b/how-to-return-statistics.md
new file mode 100644
index 0000000..16d4b0a
--- /dev/null
+++ b/how-to-return-statistics.md
@@ -0,0 +1,60 @@
+---
+layout: page
+---
+
+Statistics
+==========
+
+Imagine a grid listing records. What if we want "X total records",
+above that grid, or "average cost" elsewhere in our UI?
+
+These calculations are supported via `allow_stat`:
+
+{% highlight ruby %}
+# app/resources/employee_resource.rb
+allow_stat total: [:count]
+{% endhighlight %}
+
+A GET to `/api/employees?stats[total]=count` would return:
+
+{% highlight ruby %}
+{
+ data: [...],
+ meta: {
+ stats: {
+ total: {
+ count: 100
+ }
+ }
+ }
+}
+{% endhighlight %}
+
+A few 'default calculations' are provided: `count`, `sum`, `average`,
+`maximum` and `minimum`. These will work out-of-the-box with `ActiveRecord`.
+Alternatively, override these calculation functions:
+
+{% highlight ruby %}
+allow_stat :salary do
+ average { |scope, attr| scope.average(attr) }
+end
+{% endhighlight %}
+
+Or support your own custom calculations:
+
+{% highlight ruby %}
+allow_stat salary: [:average] do
+ standard_deviation { |scope, attr| ... }
+end
+{% endhighlight %}
+
+Multiple stats are supported with one request:
+
+> GET /api/employees?stats[salary]=average,maximum&stats[total]=count
+
+If you want **only** stats, and no records (for performance), simple pass page size 0:
+
+> GET /api/employees?stats[salary]=average&page[size]=0
+
+
+
diff --git a/how-to-scope-to-current-user.md b/how-to-scope-to-current-user.md
new file mode 100644
index 0000000..2c47c7c
--- /dev/null
+++ b/how-to-scope-to-current-user.md
@@ -0,0 +1,93 @@
+---
+layout: page
+---
+
+Scoping to Current User
+==========
+
+### Scenario: Scoping Queries
+
+> Given a `Post` model with `hidden` attribute, only allow administrators
+to view hidden `Posts`.
+
+Let's start by adding a `visible` scope to `Post`, so we can easily
+retrive only `Post`s where `hidden` is `false`:
+
+{% highlight ruby %}
+# app/models/post.rb
+scope :visible, -> { where(hidden: false) }
+{% endhighlight %}
+
+As you know, we would typically use the base scope `Post.all` like so:
+
+{% highlight ruby %}
+def index
+ render_jsonapi(Post.all)
+end
+{% endhighlight %}
+
+Let's instead use the base scope `Post.visible` when the user is not an
+administrator:
+
+{% highlight ruby %}
+def index
+ scope = current_user.admin? ? Post.all : Post.visible
+ render_jsonapi(scope)
+end
+{% endhighlight %}
+
+That's it! Now only administrators can view hidden `Post`s.
+
+### Privileged Writes
+
+> Given `Post`s that have an `internal` attribute, only allow
+internal users to publish internal posts.
+
+Our controller context is available in our resource. Let's override
+`Resource#create` to ensure correct privileging:
+
+{% highlight ruby %}
+def create(attributes)
+ if !internal_user? && attributes[:internal] == true
+ raise "Hey you! YOU can't publish internal posts!"
+ else
+ super
+ end
+end
+
+private
+
+def internal_user?
+ context.current_user.internal?
+end
+{% endhighlight %}
+
+### Guarding Filters
+
+> Given `Employee`s with attribute `under_performance_review`, do not allow clients to find all employees under performance review.
+
+Occasionally you need to guard filters based on the current user. Use
+the `:if` option on `allow_filter`. This will execute in the context of
+your controller:
+
+{% highlight ruby %}
+# app/resources/employee_resource.rb
+allow_filter :name, if: :admin?
+{% endhighlight %}
+
+{% highlight ruby %}
+# app/controllers/employees_controller.rb
+class EmployeesController < ApplicationController
+ jsonapi resource: EmployeeResource
+
+ def index
+ render_jsonapi(Employee.all)
+ end
+
+ private
+
+ def admin?
+ current_user.admin?
+ end
+end
+{% endhighlight %}
diff --git a/how-to-use-without-activerecord.md b/how-to-use-without-activerecord.md
new file mode 100644
index 0000000..89c7c0a
--- /dev/null
+++ b/how-to-use-without-activerecord.md
@@ -0,0 +1,197 @@
+---
+layout: page
+---
+
+Usage Without ActiveRecord: ElasticSearch
+==========
+
+Though we'll be hitting [elasticsearch](https://www.elastic.co) in this
+example, remember that this is just an HTTP API underneath the hood. The
+same pattern applies to a variety of use cases.
+
+First we need a **Client** for `elasticsearch`. Though you can feel free
+to use a variety of clients, this example will use [trample](http://richmolj.github.io/trample).
+
+Though we'll show code snippets below, feel free to [view the diff on
+github](https://github.com/jsonapi-suite/employee_directory/compare/step_23_disassociation...elasticsearch).
+
+Also keep in mind, we'll be showing a one-off customization here. You
+probably want to extract this code into an `Adapter` if this is going to
+become a core component of your application.
+
+Start by installing trample:
+
+{% highlight ruby %}
+# Gemfile
+gem 'trample_search', require: 'trample'
+{% endhighlight %}
+
+Tell [searchkick](https://github.com/ankane/searchkick) that we want to
+index `Employee`s and `Position`s:
+
+{% highlight ruby %}
+# app/models/employee.rb
+searchkick text_start: [:first_name]
+
+# app/models/position.rb
+searchkick text_start: [:title]
+{% endhighlight %}
+
+Define our search classes. These tell trample the configuration of the
+search:
+
+{% highlight ruby %}
+# app/models/employee_search.rb
+class EmployeeSearch < Trample::Search
+ model Employee
+
+ condition :first_name, single: true
+ condition :last_name, single: true
+end
+
+# app/models/position_search.rb
+class PositionSearch < Trample::Search
+ model Position
+
+ condition :title, single: true
+ condition :employee_id
+end
+{% endhighlight %}
+
+In our controller, we need to pass a base scope. Before, we were passing
+an `ActiveRecord::Relation` (`Post.all`). Let's pass an instance
+of `Trample::Search` instead. Since by default search results come back
+as `Hashie::Mash`es, we'll also specify our serializer directly. You
+could also use a generic `SearchResult` serializer, it's up to you.
+
+{% highlight ruby %}
+# app/controllers/employees_controller.rb
+def index
+ render_jsonapi EmployeeSearch.new,
+ class: SerializableEmployeeSearchResult
+end
+{% endhighlight %}
+
+{% highlight ruby %}
+# app/serializers/serializable_employee_search_result.rb
+class SerializableEmployeeSearchResult < JSONAPI::Serializable::Resource
+ type :employees
+
+ attribute :first_name
+ attribute :last_name
+ attribute :created_at
+ attribute :updated_at
+
+ has_many :positions, class: 'SerializablePositionSearchResult'
+end
+{% endhighlight %}
+
+Since we are now passing a non-default base scope, we need to tell our
+`Resource` how to query and resolve this new scope. Start by switching to
+the pass-through adapter, and resolve using `trample`'s query API:
+
+{% highlight ruby %}
+# app/resources/employee_resource.rb
+use_adapter JsonapiCompliable::Adapters::Null
+# ... code ...
+def resolve(scope)
+ scope.query!
+ scope.results
+end
+
+# remove the belongs_to for now
+{% endhighlight %}
+
+
+
+You can now hit `http://localhost:3000/api/v1/employees` - the exact
+same payload is coming back, but is now sourced from `elasticsearch`!
+
+Let's add a prefix filter:
+
+{% highlight ruby %}
+# app/resources/employee_resource.rb
+allow_filter :first_name_prefix do |scope, value|
+ scope.condition(:first_name).starts_with(value)
+end
+{% endhighlight %}
+
+Hit `http://localhost:3000/api/v1/employees?filter[first_name]=hom`.
+You're now successfully querying the `elasticsearch` index.
+
+ If we want sorting and pagination, we need to tell the `Resource`
+ how to deal with that, too:
+
+{% highlight ruby %}
+# app/resources/employee_resource.rb
+paginate do |scope, current_page, per_page|
+ scope.metadata.pagination.current_page = current_page
+ scope.metadata.pagination.per_page = per_page
+ scope
+end
+
+sort do |scope, att, dir|
+ scope.metadata.sort = [{att: att, dir: dir}]
+ scope
+end
+{% endhighlight %}
+
+View the [Resource](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Resource.html) and [Adapter](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Adapters/Abstract.html) documentation for additional overrides, like statistics.
+
+The last step is adding the `positions` association. If we want
+`has_many`-style macros we need to create an `Adapter`, but for now
+let's simply use the lower-level `allow_sideload` DSL. We need to define
+two functions: how to build a scope for the association, and how to
+associate the resulting objects:
+
+{% highlight ruby %}
+# app/resources/employee_resource.rb
+allow_sideload :positions, resource: PositionResource do
+ scope do |employees|
+ scope = PositionSearch.new
+ scope.condition(:employee_id).or(employees.map(&:id))
+ end
+
+ assign do |employees, positions|
+ employees.each do |e|
+ e.positions = positions.select { |p| p.employee_id = e.id }
+ end
+ end
+end
+{% endhighlight %}
+
+Convert the `PositionResource` to use `elasticsearch`, just like we did
+for `Employee`:
+
+{% highlight ruby %}
+# app/resources/position_resource.rb
+use_adapter JsonapiCompliable::Adapters::Null
+
+def resolve(scope)
+ scope.query!
+ scope.results
+end
+{% endhighlight %}
+
+Create the `SerializablePositionSearchResult` class that we referenced
+in `app/serializers/serializable_employee.rb`:
+
+{% highlight ruby %}
+class SerializablePositionSearchResult < JSONAPI::Serializable::Resource
+ type :positions
+
+ attribute :title
+end
+{% endhighlight %}
+
+We can now sideload `positions` - check out the results at
+`http://localhost:3000/api/v1/employees?include=positions`. We're
+fetching employees and their corresponding `positions` in a single
+request, via `elasticsearch`. Any filters/changes/default sort/etc that
+apply to `PositionResource` can be re-used at this endpoint.
+
+If this was a one-off section of our application, we can call this good
+enough and move on. But as we continue to use this pattern, it's going
+to get monotonous writing the same filter overrides, `allow_sideload`
+wiring code, etc. To DRY up this code, we can package our changes into
+an [Adapter](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Adapters/Abstract.html).
diff --git a/how-to-write-specs.md b/how-to-write-specs.md
new file mode 100644
index 0000000..b732933
--- /dev/null
+++ b/how-to-write-specs.md
@@ -0,0 +1,120 @@
+---
+layout: page
+---
+
+Writing Specs
+==========
+
+> NB: before writing specs, make sure you are set up correctly by
+> following the [Quickstart]({{site.github.url}}/quickstart)
+
+Validating verbose JSON API responses in tests can be a pain. We could
+use something like [json_matchers](https://github.com/thoughtbot/json_matchers) to validate a schema, but we hope to do one better - let's validate full payloads with a few simple helpers, using full-stack [rspec request specs](https://github.com/rspec/rspec-rails#request-specs).
+
+Let's say we're testing the `show` action of our employees controller, sideloading the employee's department. Follow the [Quickstart]({{site.github.url}}/quickstart) to make sure your `rails_helper.rb` is setup correctly first.
+
+Let's say we're testing the `show` action of our employees controller, sideloading the employee's department.
+
+Let's begin with vanilla RSpec of what the test might look like:
+
+{% highlight ruby %}
+require 'rails_helper'
+
+RSpec.describe 'employees#show', type: :request do
+ let!(:homer) { Employee.create!(name: 'Homer Simpson') }
+ let!(:safety) { employee.create_department!(name: 'Safety') }
+
+ it 'renders an employee, sideloading department' do
+ get "/api/employees/#{homer.id}", params: {
+ include: 'department'
+ }
+ # ... code asserting json response ...
+ end
+end
+{% endhighlight %}
+
+To avoid painful json assertions, let's use [jsonapi_spec_helpers](https://github.com/jsonapi-suite/jsonapi_spec_helpers). Start by adding some setup code:
+
+{% highlight ruby %}
+# spec/rails_helper.rb
+require 'jsonapi_spec_helpers'
+
+RSpec.configure do |config|
+ config.include JsonapiSpecHelpers
+end
+{% endhighlight %}
+
+And now to validate the response, we'll call `assert_payload`:
+
+{% highlight ruby %}
+assert_payload(:employee, homer, json_item)
+assert_payload(:department, safety, json_include('departments'))
+{% endhighlight %}
+
+`assert_payload` takes three arguments:
+* The name of a payload we've defined (we haven't done this yet).
+* The record we want to compare against
+* The relevant slice of json. `json_item` and `json_includes` are
+ helpful methods to target the right slice. You can see all helpers in
+the documentation for `jsonapi_spec_helpers`.
+
+OK, so we want to take a record, response JSON, and compare them against
+something pre-defined. Let's write those definitions; they look very similar to
+something you'd write for [factory_girl](https://github.com/thoughtbot/factory_girl):
+
+{% highlight ruby %}
+# spec/payloads/employee.rb
+JsonapiSpecHelpers::Payload.register(:employee) do
+ key(:name)
+ key(:email)
+
+ timestamps!
+end
+
+# spec/payloads/department.rb
+JsonapiSpecHelpers::Payload.register(:department) do
+ key(:name)
+end
+{% endhighlight %}
+
+`assert_payload` will do four things:
+
+* Ensure keys that are not in the payload definition are **not** present.
+* Ensure all keys in the registered payload **are** present.
+* Ensures no value in a key/value pair is `nil` (this is overrideable).
+* Ensures each key matches the expected record value. In other words,
+ we're doing something like `expect(json['email']).to eq(homer.email)`.
+
+The comparison value can be customized. Let's say we serialize the
+`name` attribute as a combination of the employee's `first_name` and
+`last_name`:
+
+{% highlight ruby %}
+key(:name) { |record| "#{record.first_name} #{record.last_name}" }
+{% endhighlight %}
+
+Optionally, validate against a type as well. If both the expected and
+actual values match, but are the incorrect type, the test will fail:
+
+{% highlight ruby %}
+key(:salary, Integer)
+{% endhighlight %}
+
+You can also customize/override payloads at runtime in your test. Let's
+say we only serialize `salary` when the current user is an admin. Your
+test could look something like:
+
+{% highlight ruby %}
+sign_in(:user)
+assert_payload(:employee, homer, json_item)
+sign_in(:admin)
+assert_payload(:employee, homer, json_item) do
+ key(:salary)
+end
+{% endhighlight %}
+
+For documentation on all the spec helpers we provide, check out the
+[jsonapi_spec_helpers](https://github.com/jsonapi-suite/jsonapi_spec_helpers) gem.
+
+
+
diff --git a/index.md b/index.md
new file mode 100644
index 0000000..1eb5d67
--- /dev/null
+++ b/index.md
@@ -0,0 +1,6 @@
+---
+# You don't need to edit this file, it's empty on purpose.
+# Edit theme's home layout instead if you wanna make some changes
+# See: https://jekyllrb.com/docs/themes/#overriding-theme-defaults
+layout: home
+---
diff --git a/js/authentication.md b/js/authentication.md
new file mode 100644
index 0000000..fa8578b
--- /dev/null
+++ b/js/authentication.md
@@ -0,0 +1,73 @@
+---
+layout: page
+---
+
+{% include js-header.html %}
+{% include js-toc.html %}
+
+
+### Authentication
+
+JSORM supports [JSON Web Tokens](https://jwt.io/introduction). These can
+be set manually, or automatically fetched from `localStorage`.
+
+To set manually:
+
+{% highlight typescript %}
+ApplicationRecord.jwt = 'myt0k3n'
+{% endhighlight %}
+> All requests will now send the header:
+> `Authorization: Token token="myt0k3n"`.
+
+To set via `localStorage`, simply store the token with a key of `jwt`
+and it will be set automatically. To customize the `localStorage` key:
+
+{% highlight typescript %}
+ApplicationRecord.jwtStorage = "authtoken"
+{% endhighlight %}
+
+...or to opt-out of `localStorage` altogether:
+
+{% highlight typescript %}
+ApplicationRecord.jwtStorage = false
+{% endhighlight %}
+
+You can control the format of the header that is sent to the
+server:
+
+{% include js-code-tabs.html %}
+
+
+Finally, if your server returns a refreshed JWT within the `X-JWT`
+header, it will be used in all subsequent requests (and `localStorage`
+will be updated automatically if you're using it).
+
+
diff --git a/js/ddau.md b/js/ddau.md
new file mode 100644
index 0000000..46db22d
--- /dev/null
+++ b/js/ddau.md
@@ -0,0 +1,23 @@
+---
+layout: page
+---
+
+{% include js-header.html %}
+{% include js-toc.html %}
+
+
+### Data Down, Actions Up
+
+It's a [popular pattern](http://www.samselikoff.com/blog/data-down-actions-up) to pass data **down** to components, avoid modifying state within the component, and instead pass **actions up** to modify state. This can make complex applications easier to track and reason about, and you'll see it in client-side frameworks from React to Ember.
+
+To follow this pattern, use `#dup()` when passing down to your component:
+
+{% highlight bash %}
+
+{% endhighlight %}
+
+This will create a new instance of the model with all the same state.
+Avoid modifying this instance in your component and instead pass
+**actions up**.
+
+When opting-in to [state-syncing]({{ site.github.url }}/js/state-syncing) these instances will sync-up whenever one of these is instances is persisted. You won't have to worry about updating the child component when the parent instance is saved.
diff --git a/js/home.md b/js/home.md
new file mode 100644
index 0000000..1d4725f
--- /dev/null
+++ b/js/home.md
@@ -0,0 +1,107 @@
+---
+layout: page
+---
+
+
+ JSORM
+ the isomorphic, framework-agnostic Javascript ORM
+
+
+...or, if you're avoiding JS modules, `jsorm` will be available as a global in
+the browser.
+
+### Defining Models
+
+#### Connecting to the API
+
+Just like `ActiveRecord`, our models will inherit from a base class that
+holds connection information (`ApplicationRecord`, or
+`ActiveRecord::Base` in Rails < 5):
+
+{% include js-code-tabs.html %}
+
+{% highlight typescript %}
+class ApplicationRecord extends JSORMBase {
+ static baseUrl = "http://my-api.com"
+ static apiNamespace = "/api/v1"
+}
+{% endhighlight %}
+
+{% highlight javascript %}
+const ApplicationRecord = JSORMBase.extend({
+ static: {
+ baseUrl: "http://my-api.com",
+ apiNamespace: "/api/v1"
+ }
+})
+{% endhighlight %}
+
+All URLs follow the following pattern:
+
+ * `baseUrl` + `apiNamespace` + `jsonapiType`
+
+As you can see above, typically `baseUrl` and `apiNamespace` are set on
+a top-level `ApplicationRecord` (though any subclass can override).
+`jsonapiType`, however, is set per-model:
+
+{% include js-code-tabs.html %}
+
+
+With the above configuration, all `Person` endpoints will begin
+`http://my-api.com/api/v1/people`.
+
+> **TIP**: Avoid CORS and use relative paths by simply setting `baseUrl` to
+`""`
+
+> **TIP**: You can always use the `endpoint` option to override this pattern
+and set the endpoint manually.
+
+#### Defining Attributes
+
+`ActiveRecord` automatically sets attributes by introspecting database
+columns. We could do the same - `swagger.json` is our schema - but tend
+to agree with those who feel this aspect of `ActiveRecord` is a bit too
+"magical". In addition, explicitly defining our attributes can be used
+to track which applications are using which attributes of the API.
+
+Though this is configurable, by default we expect the API to be
+`under_scored` and attributes to be `camelCased`.
+
+{% include js-code-tabs.html %}
+
+
+#### Defining Relationships
+
+Just like `ActiveRecord`, there are `HasMany`, `BelongsTo`, and
+`HasOne` relationships:
+
+{% include js-code-tabs.html %}
+
+
+By default, we expect the relationship name to correspond to a
+pluralized `jsonapiType` on a separate `Model`. If your models don't
+use this convention, feel free to supply it explicitly:
+
+{% include js-code-tabs.html %}
+
+
+Relationships can be:
+
+* Assigned via constructor
+* Assigned directly
+* Automatically loaded via `.includes()` (see [reads](/js/reads))
+* Saved in a single request `.save({ with: 'dogs' })` (see
+[writes](/js/writes))
+
+{% include js-code-tabs.html %}
+
+ {% highlight typescript %}
+let dog = new Dog({ name: "Fido" })
+let person = new Person({ dogs: [dog] })
+person.dogs[0].name // "Fido"
+
+let person = new Person()
+person.dogs = [dog]
+person.dogs[0].name // "Fido"
+
+// Will auto-create Dog instance
+let person = new Person({ dogs: [{ name: "Scooby" }] })
+person.dogs[0].name // "Scooby"
+
+let person = (await Person.includes('dogs')).data
+person.dogs // array of Dog instances from the server
+ {% endhighlight %}
+
+ {% highlight javascript %}
+var dog = new Dog({ name: "Fido" })
+var person = new Person({ dogs: [dog] })
+person.dogs[0].name // "Fido"
+
+let person = new Person()
+person.dogs = [dog]
+person.dogs[0].name // "Fido"
+
+// Will auto-create Dog instance
+var person = new Person({ dogs: [{ name: "Scooby" }] })
+person.dogs[0].name // "Scooby"
+
+Person.includes('dogs').then((response) => {
+ var person = response.data
+ person.dogs // array of Dog instances from the server
+})
+ {% endhighlight %}
+
diff --git a/js/introduction.md b/js/introduction.md
new file mode 100644
index 0000000..b83e069
--- /dev/null
+++ b/js/introduction.md
@@ -0,0 +1,39 @@
+---
+layout: page
+---
+
+{% include js-header.html %}
+{% include js-toc.html %}
+
+
+### Why JSORM?
+
+Contracts like JSONAPI and GraphQL treat the API like a database. When querying a
+database, we have two options:
+
+ * Type the low-level query language directly (in the database world,
+ this would be hand-typing SQL).
+ * Use an ORM (like Rails's `ActiveRecord`, Phoenix's `Ecto`, Django's
+ `DjangoORM`, or Node's `Sequelize`).
+
+While both options have pros and cons, we tend to think ORMs
+have two overwhelming benefits: ***ease of use*** and ***composable queries***.
+We'll explore both these concepts in other sections.
+
+So, we want a javascript ORM for our JSONAPI "database". Because
+`ActiveRecord` is arguably the most well-known ORM, we've tried to match
+its interface to make this library accessible to new users. That said,
+you'll find we've tried to favor *explicitness* over *implicitness* in
+order to avoid common `ActiveRecord` pitfalls.
+
+
diff --git a/js/middleware.md b/js/middleware.md
new file mode 100644
index 0000000..afb9f3f
--- /dev/null
+++ b/js/middleware.md
@@ -0,0 +1,85 @@
+---
+layout: page
+---
+
+{% include js-header.html %}
+{% include js-toc.html %}
+
+
+### Middleware
+
+Middleware is helpful whenever you want to globally intercept request.
+This is accomplished by assigning a `MiddlewareStack` to your
+`ApplicationRecord`. Each stack has `beforeFilters` and `afterFilters`
+where you can globally modify requests. If you `throw("abort")`, the
+promise will be rejected.
+
+Example: redirecting to the login page every time the server returns `401`:
+
+{% include js-code-tabs.html %}
+
diff --git a/js/reads/fieldsets.md b/js/reads/fieldsets.md
new file mode 100644
index 0000000..2fd9144
--- /dev/null
+++ b/js/reads/fieldsets.md
@@ -0,0 +1,58 @@
+---
+layout: page
+---
+
+{% include js-header.html %}
+{% include js-toc.html %}
+
+
+### Sparse Fieldsets
+
+Use `#select()` to limit the fields returned by the server:
+
+{% highlight typescript %}
+Post.select(['title', 'status']).all()
+{% endhighlight %}
+
+
/posts?fields[posts]=title,status
+
+
+When dealing with relationships, it may be easier to pass an object,
+where the key is the corresponding JSONAPI type. This will be exactly
+what's sent to the server in `?fields`:
+
+{% highlight typescript %}
+Post.select({
+ posts: ['title', 'status'],
+ comments: ['created_at']
+}).all()
+{% endhighlight %}
+
+
+
+### Extra Fieldsets
+
+Use `#selectExtra()` to explicitly request a field that doesn't usually
+come back (often computationally expensive):
+
+{% highlight typescript %}
+Post.selectExtra(['highlights', 'cumulative_ranking']).all()
+{% endhighlight %}
+
+
+`#where()` clauses can be chained together. If the same key is seen
+twice, it will be overridden:
+
+{% highlight typescript %}
+Post
+ .where({ important: true })
+ .where({ ranking: 10 })
+ .where({ important: false })
+ .all()
+{% endhighlight %}
+
+
/posts?filter[important]=false&filter[ranking]=10
+
+
+`#where()` clauses are based on **server implementation**. The key
+should be exactly as the server understands it. Here are some common
+conventions we promote:
+
+{% highlight typescript %}
+// id greater than 5
+Post.where({ id_gt: 5 }).all()
+
+// id greater than or equal to 5
+Post.where({ id_gte: 5 }).all()
+
+// id less than 5
+Post.where({ id_lt: 5 }).all()
+
+// id less or equal to 5
+Post.where({ id_lte: 5 }).all()
+
+// title starts with "foo"
+Post.where({ title_prefix: "foo" }).all()
+
+// OR these two values
+Post.where({ status_or: ['draft', 'review'] })
+
+// AND these two values (default)
+Post.where({ status: ['draft', 'review'] })
+{% endhighlight %}
+
+
diff --git a/js/reads/index.md b/js/reads/index.md
new file mode 100644
index 0000000..15082f8
--- /dev/null
+++ b/js/reads/index.md
@@ -0,0 +1,221 @@
+---
+layout: page
+---
+
+{% include js-header.html %}
+{% include js-toc.html %}
+
+
+### Reads
+
+The interface for read operations is a simpler version of the
+[ActiveRecord Query Interface](http://guides.rubyonrails.org/active_record_querying.html).
+Instead of generating SQL, we'll be generating JSONAPI requests.
+
+### Basic Finders
+
+Execute queries with `.all()`, `find()`, or `.first()`:
+
+{% include js-code-tabs.html %}
+
+ {% highlight typescript %}
+let response = await Post.all()
+response.data // array of Post instances
+ {% endhighlight %}
+ {% highlight javascript %}
+Post.all().then(function(response) {
+ response.data // array of Post instances
+});
+ {% endhighlight %}
+
+
+### Composable Queries with Scopes
+
+The beauty of ORMs is their ability to compose queries. We'll be doing
+this by chaining together `Scope`s (query fragments). All of the methods
+you see on this page can be chained together - the request will not fire
+until the chain ends with `all()`, `first()`, or `find`. Example:
+
+{% include js-code-tabs.html %}
+
+
+In practice, you'll probably have some scopes you want to re-use across
+different contexts. A best practice is to store these scopes as class
+methods (static methods) in the model:
+
+{% include js-code-tabs.html %}
+
+ {% highlight typescript %}
+class Post extends ApplicationRecord {
+ // ... code ...
+ static superImportant() {
+ return this
+ .where({ ranking_gt: 8 })
+ .order({ ranking: 'desc' })
+ .stats({ total 'count' })
+ }
+}
+
+// get 10 super important posts
+let scope = Post.superImportant().per(10)
+scope.all() // fire query
+ {% endhighlight %}
+
+ {% highlight javascript %}
+const Post = ApplicationRecord.extend({
+ // ... code ...
+ static: {
+ superImportant() {
+ return this
+ .where({ ranking_gt: 8 })
+ .order({ ranking: 'desc' })
+ .stats({ total 'count' })
+ }
+ }
+})
+
+// get 10 super important posts
+var scope = Post.superImportant().per(10);
+scope.all() // fire query
+ {% endhighlight %}
+
+
+### Metadata
+
+The [meta information](http://jsonapi.org/format/#document-meta) of the
+JSONAPI response is available as a POJO on the response:
+
+{% include js-code-tabs.html %}
+
+
+### Promises and Async/Await
+
+The result of `all()`, `first()` or `find` is a [Promise](https://developers.google.com/web/fundamentals/primers/promises). The promise will resolve to a `Response` object.
+
+A `Response` object has three keys - `data`, `meta`, and `raw`. `data` - the one
+you'll be using the most - will be a `Model` instance (or array of
+`Model`) instances. `meta` will be the [Meta Information](http://jsonapi.org/format/#document-meta) returned by the API (mostly used for statistics in our case). `raw` is only used to introspect the raw response document.
+
+{% include js-code-tabs.html %}
+
+ {% highlight typescript %}
+Post.all().then((response) => {
+ response.data // array of Post instances
+ response.meta // js object from the server
+ response.raw // js response document
+})
+ {% endhighlight %}
+
+ {% highlight javascript %}
+Post.all().then(function(response) {
+ response.data // array of Post instances
+ response.meta // js object from the server
+ response.raw // js response document
+});
+ {% endhighlight %}
+
+
+
/posts
+
+
+Hopefully you're running in an environment that supports
+ES7's [Async/Await](https://hackernoon.com/6-reasons-why-javascripts-async-await-blows-promises-away-tutorial-c7ec10518dd9). This makes things even easier:
+
+{% highlight typescript %}
+let { data } = await Post.all()
+data // array of Post instances
+
+// alternatively
+
+let posts = (await Post.all()).data
+posts // array of Post instances
+{% endhighlight %}
+
diff --git a/js/reads/nested-queries.md b/js/reads/nested-queries.md
new file mode 100644
index 0000000..c371689
--- /dev/null
+++ b/js/reads/nested-queries.md
@@ -0,0 +1,118 @@
+---
+layout: page
+---
+
+{% include js-header.html %}
+{% include js-toc.html %}
+
+
+### Nested Queries
+
+We can nest all read operations at any level of the graph. Let's say we wanted to
+fetch all `Post`s and their `Comment`s...but only return comments that
+are `active`, sorted by `created_at` descending. We can create a
+`Comment` scope as normal, then `#merge()` it into our `Post` scope:
+
+{% include js-code-tabs.html %}
+
+
+Any number of scopes can be merged in. Just remember to `#include()`
+and `#merge()` relationship names **as the server understands them**:
+
+{% include js-code-tabs.html %}
+
+{% highlight typescript %}
+class Dog extends ApplicationRecord {
+ @BelongsTo() person: Person
+}
+
+// We've modeled this as Dog > person in javascript
+// And Person is jsonapiType "people"
+// But the server defined the relationship as "owner"
+Dog.includes("owner").merge({ owner: Person.limitedFields() })
+{% endhighlight %}
+
+{% highlight javascript %}
+const Dog = ApplicationRecord.extend({
+ // ... code ...
+ methods: {
+ person: belongsTo()
+ }
+})
+
+// We've modeled this as Dog > person in javascript
+// And Person is jsonapiType "people"
+// But the server defined the relationship as "owner"
+Dog.includes("owner").merge({ owner: Person.limitedFields() })
+{% endhighlight %}
+
diff --git a/js/reads/statistics.md b/js/reads/statistics.md
new file mode 100644
index 0000000..4ac3d1e
--- /dev/null
+++ b/js/reads/statistics.md
@@ -0,0 +1,60 @@
+---
+layout: page
+---
+
+{% include js-header.html %}
+{% include js-toc.html %}
+
+
+### Statistics
+
+Use `#stats()` to request statistics. Access stats within `meta`:
+
+{% include js-code-tabs.html %}
+
+{% highlight typescript %}
+let { data } = await Post.stats({ total: "count" }).all()
+data.meta.stats.total.count // the total count
+{% endhighlight %}
+
+{% highlight javascript %}
+Post.stats({ total: "count" }).all().then(function(response) {
+ response.meta.stats.total.count // the total count
+})
+{% endhighlight %}
+
+
+
/posts?stats[total]=count
+
+
+Stats are always independent of pagination. If you request the total count, you'll get the total count even if you're limiting to 10 per page. This means to get **only** statistics - avoid returning `Post` instances altogether - simply request `0` results per page:
+
+{% include js-code-tabs.html %}
+
+{% highlight typescript %}
+let { data } = await Post.per(0)stats({ total: "count" }).all()
+data.meta.stats.total.count // the total count
+{% endhighlight %}
+
+{% highlight javascript %}
+Post
+ .per(0)
+ .stats({ total: "count" })
+ .all().then(function(response) {
+ response.meta.stats.total.count // the total count
+ })
+{% endhighlight %}
+
diff --git a/js/state-syncing.md b/js/state-syncing.md
new file mode 100644
index 0000000..1021744
--- /dev/null
+++ b/js/state-syncing.md
@@ -0,0 +1,111 @@
+---
+layout: page
+---
+
+{% include js-header.html %}
+{% include js-toc.html %}
+
+
+### State Syncing
+
+You may have encountered state management libraries like [Flux](https://facebook.github.io/flux/docs/overview.html),
+[Redux](https://redux.js.org) or [Vuex](https://vuex.vuejs.org/en/intro.html). These are fantastic libraries, but you likely won't need them with JSORM. As a full-fledged model layer, JSORM manages state for you, automatically.
+
+If you opt-in to this feature:
+
+{% highlight typescript %}
+ApplicationRecord.sync = true
+{% endhighlight %}
+
+Instances will sync up whenever the server tells us about updated state.
+Consider the scenario where an instance is initially loaded, then separately polled in the background:
+
+{% include js-code-tabs.html %}
+
+
+Note that our `poll()` function **never assigns or updates `person`**.
+But if the server returns an updated `name` attribute, **`person.name`
+will be automatically updated**. This is true even if `person.name`
+is bound in 17 different nested components.
+
+Instances can still update their attributes independently - we only sync
+when the server returns updated data:
+
+{% include js-code-tabs.html %}
+
+
+#### Gotchas
+
+Under the hood, instances are listening for updates from a central data
+store. This means that you'll want to remove listeners whenever you no
+longer need the instance - otherwise it will never be garbage collected
+properly. To remove a listener:
+
+{% highlight typescript %}
+instance.unlisten()
+{% endhighlight %}
+
+In practice, when developing in a SPA, you'll want to `#unlisten()`
+whenever a view is destroyed and model instances no longer need to be referenced. If
+you are using VueJS, this is done automatically by adding [jsorm-vue](https://github.com/jsonapi-suite/jsorm-vue)
+to your application.
diff --git a/js/writes/dirty-tracking.md b/js/writes/dirty-tracking.md
new file mode 100644
index 0000000..5c65768
--- /dev/null
+++ b/js/writes/dirty-tracking.md
@@ -0,0 +1,130 @@
+---
+layout: page
+---
+
+{% include js-header.html %}
+{% include js-toc.html %}
+
+
+### Dirty Tracking
+
+When an attribute has been modified, but has not yet been saved to the
+server, it is considered "dirty". Use `#isDirty()` to see if any attribute is dirty, use the `#changes()` method to see all dirty attributes.
+
+{% include js-code-tabs.html %}
+
+
+> Remember, only dirty attributes are sent to the server when `#save()`
+> is called.
+
+`#isDirty()` *can* take into account relationships - just pass a
+string, array, or object or relationship names. A relationship is
+considered dirty if:
+
+* Any objects in the relationship have dirty attributes
+* An object was removed from a `hasMany` relationship
+* An object was added to a `hasMany` relationship
+* Any object within the relationship was replaced with a different
+object.
+
+{% include js-code-tabs.html %}
+
diff --git a/js/writes/index.md b/js/writes/index.md
new file mode 100644
index 0000000..2470b55
--- /dev/null
+++ b/js/writes/index.md
@@ -0,0 +1,142 @@
+---
+layout: page
+---
+
+{% include js-header.html %}
+{% include js-toc.html %}
+
+
+### Writes
+
+Similar to `ActiveRecord`, you can simply call `#save()` on a model
+instance. JSORM will [create](http://jsonapi.org/format/#crud-creating) (`POST`) or [update](http://jsonapi.org/format/#crud-updating) (`PATCH`) as needed.
+
+`#save()` returns a `Promise` that will resolve a `boolean` - `true`
+when the server returns a 200-ish response code, `false` when the server
+returns a `422` response code (see
+[validations](/js/writes/validations)). As always, anything else will
+reject the promise.
+
+{% include js-code-tabs.html %}
+
+ {% highlight typescript %}
+ let blog = new Blog({ title: "My Blog" })
+ let success = await blog.save() // POST /blogs
+ console.log(success) // true/false
+
+ blog.title = "Updated Title"
+ success = await blog.save() // PUT /blogs/:id
+ console.log(success) // true/false
+ {% endhighlight %}
+
+ {% highlight javascript %}
+ var blog = new Blog({ title: "My Blog" });
+ // POST /blogs
+ blog.save().then(function(success) {
+ console.log(success); // true/false
+
+ blog.title = "Updated Title":
+ // PUT /blogs/:id
+ blog.save().then(function(success) {
+ console.log(success) // true/false
+ });
+ });
+ {% endhighlight %}
+
+
+After saving, the instance will automatically pick up any
+server-assigned attributes:
+
+{% include js-code-tabs.html %}
+
+ {% highlight typescript %}
+ let post = new Post()
+ await post.save()
+ post.id // server-assigned value
+ post.createdAt // server-assigned value
+ {% endhighlight %}
+
+ {% highlight javascript %}
+ var post = new Post();
+ post.save().then(function(success) {
+ post.id // server-assigned value
+ post.createdAt // server-assigned value
+ });
+ {% endhighlight %}
+
+
+If a `Model` was instantiated with data from the server, `isPersisted`
+will return `true`. This means that we can assign IDs on the client
+without any adverse behavior; we can also manually mark objects as
+persisted for testing purposes:
+
+{% include js-code-tabs.html %}
+
+ {% highlight typescript %}
+ let blog = new Blog({ id: 123 })
+ blog.isPersisted // false
+ await blog.save() // POST /blogs
+ blog.isPersisted // true
+ blog.id // 123
+
+ // Manually mark an instance as persisted
+ blog = new Blog({ id: 123 })
+ blog.isPersisted = true
+ await blog.save() // PUT /blogs/123
+ {% endhighlight %}
+
+ {% highlight javascript %}
+ var blog = new Blog({ id: 123 });
+ blog.isPersisted // false
+ // POST /blogs
+ blog.save().then(function(response) {
+ blog.isPersisted // true
+ blog.id // 123
+ });
+
+ // Manually mark an instance as persisted
+ var blog = new Blog({ id: 123 });
+ blog.isPersisted = true
+ blog.save() // PUT /blogs/123
+ {% endhighlight %}
+
+
+Notably, **only dirty (changed) attributes will be sent to the server**. This prevents race conditions and unexpected side-effects. In the following example, `Post` has attributes `title`, `description`, and `createdAt`:
+
+{% include js-code-tabs.html %}
+
+ {% highlight typescript %}
+ let post = (await Post.first())
+ post.title = "updated"
+ // ONLY title sent to the server
+ await post.save()
+ // Title is now synced with the server
+ post.description = "updated"
+ // ONLY description sent to the server
+ await post.save()
+ {% endhighlight %}
+
+ {% highlight javascript %}
+ Post.first().then(function(response) {
+ var post = response.data;
+ post.title = "updated";
+ // ONLY title sent to the server
+ post.save().then(function(response) {
+ // Title is now synced with the server
+ post.description = "updated";
+ // ONLY description sent to the server
+ post.save();
+ });
+ });
+ {% endhighlight %}
+
diff --git a/js/writes/nested.md b/js/writes/nested.md
new file mode 100644
index 0000000..c2d2b4a
--- /dev/null
+++ b/js/writes/nested.md
@@ -0,0 +1,90 @@
+---
+layout: page
+---
+
+{% include js-header.html %}
+{% include js-toc.html %}
+
+
+### Nested Writes
+
+You can write a `Model` and all of its relationships in a single
+request. Keep in mind normal dirty tracking rules still apply - nothing
+is sent to the server unless it is dirty.
+
+{% include js-code-tabs.html %}
+
+ {% highlight typescript %}
+ let author = new Author()
+ let comment = new Comment({ author })
+ let post = new Post({ comments: [comment] })
+
+ // post.save({ with: "comments" })
+ // post.save({ with: ["comments", "blog"] })
+ post.save({ with: { comments: 'author' }})
+ {% endhighlight %}
+
+ {% highlight javascript %}
+ var author = new Author();
+ var comment = new Comment({ author: author });
+ var post = new Post({ comments: [comment] });
+
+ // post.save({ with: "comments" })
+ // post.save({ with: ["comments", "blog"] })
+ post.save({ with: { comments: "author" }});
+ {% endhighlight %}
+
+
+Use `model.isMarkedForDestruction = true` to delete the associated
+object. Use `model.isMarkedForDisassociation = true` to remove the association
+without deleting the underlying object:
+
+{% include js-code-tabs.html %}
+
+ {% highlight typescript %}
+ let post = (await Post.includes("comments").first()).data
+ post.comments[0].isMarkedForDestruction = true
+ post.comments[1].isMarkedForDisassociation = true
+
+ // destroys the first comment
+ // disassociates the second comment
+ await post.save({ with: "comments" })
+ {% endhighlight %}
+
+ {% highlight javascript %}
+ Post.includes("comments").first().then(function(response) {
+ var post = response.data;
+ post.comments[0].isMarkedForDestruction = true;
+ post.comments[1].isMarkedForDisassociation = true;
+
+ // destroys the first comment
+ // disassociates the second comment
+ post.save({ with: "comments" })
+ });
+ {% endhighlight %}
+
+
+You may want to send *only* the `id` of the related object to the server - ensuring the models are associated without updating attributes by
+accident. Just add `.id` to the relationship name:
+
+{% include js-code-tabs.html %}
+
diff --git a/js/writes/validations.md b/js/writes/validations.md
new file mode 100644
index 0000000..84f938c
--- /dev/null
+++ b/js/writes/validations.md
@@ -0,0 +1,47 @@
+---
+layout: page
+---
+
+{% include js-header.html %}
+{% include js-toc.html %}
+
+
+### Validations
+
+JSONAPI Suite is already set up to return validation errors with a
+`422` response code and JSONAPI-compliant [errors payload](http://jsonapi.org/format/#errors). Those errors will be automatically assigned, and removed on subsequent requests:
+
+{% include js-code-tabs.html %}
+
diff --git a/jsonapi_suite.gemspec b/jsonapi_suite.gemspec
deleted file mode 100644
index f37ac45..0000000
--- a/jsonapi_suite.gemspec
+++ /dev/null
@@ -1,41 +0,0 @@
-# coding: utf-8
-lib = File.expand_path('../lib', __FILE__)
-$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
-require 'jsonapi_suite/version'
-
-Gem::Specification.new do |spec|
- spec.name = "jsonapi_suite"
- spec.version = JsonapiSuite::VERSION
- spec.authors = ["Lee Richmond"]
- spec.email = ["lrichmond1@bloomberg.net"]
-
- spec.summary = %q{Collection of gems for jsonapi.org-compatible APIs}
- spec.description = %q{Handles automatic swagger documentation, error handling, serialization, etc}
- spec.license = "MIT"
-
- # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
- # to allow pushing to a single host or delete this section to allow pushing to any host.
- if spec.respond_to?(:metadata)
- spec.metadata['allowed_push_host'] = "http://artprod.dev.bloomberg.com/artifactory/api/gems/bb-gems-local"
- else
- raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
- end
-
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
- spec.bindir = "exe"
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
- spec.require_paths = ["lib"]
-
- spec.add_dependency 'strong_resources', '~> 0.1'
- spec.add_dependency 'jsonapi_compliable', '~> 0.3'
- spec.add_dependency 'jsonapi_errorable', '~> 0.1'
- spec.add_dependency 'jsonapi_spec_helpers', '~> 0.2'
- spec.add_dependency 'jsonapi_ams_extensions', '~> 0.1'
- spec.add_dependency 'jsonapi_swagger_helpers', '~> 0.1'
- spec.add_dependency 'active_model_serializers', '~> 0.10.x'
- spec.add_dependency 'nested_attribute_reassignable', '~> 0.6'
-
- spec.add_development_dependency "bundler", "~> 1.12"
- spec.add_development_dependency "rake", "~> 10.0"
- spec.add_development_dependency "rspec", "~> 3.0"
-end
diff --git a/lib/jsonapi_suite.rb b/lib/jsonapi_suite.rb
deleted file mode 100644
index 47aa205..0000000
--- a/lib/jsonapi_suite.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'active_model_serializers'
-require 'strong_resources'
-require 'jsonapi_compliable'
-require 'jsonapi_errorable'
-require 'jsonapi_ams_extensions'
-require 'jsonapi_swagger_helpers'
-require 'nested_attribute_reassignable'
-
-if ENV['RAILS_ENV'] == 'test'
- require 'rspec-rails'
- require 'jsonapi_spec_helpers'
-end
-
-require "jsonapi_suite/version"
-
-module JsonapiSuite
-end
diff --git a/lib/jsonapi_suite/version.rb b/lib/jsonapi_suite/version.rb
deleted file mode 100644
index 03d35b5..0000000
--- a/lib/jsonapi_suite/version.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-module JsonapiSuite
- VERSION = "0.1.0"
-end
diff --git a/quickstart.md b/quickstart.md
new file mode 100644
index 0000000..30076cd
--- /dev/null
+++ b/quickstart.md
@@ -0,0 +1,652 @@
+---
+layout: page
+---
+
+Quickstart
+==========
+
+##### Zero to API in 5 minutes
+
+This quickstart will use Rails with ActiveRecord. Head to the guides
+section for usage with alternate ORMs or avoiding Rails
+completely.
+
+If the below seems too "magical", don't worry - we're just applying some
+sensible defaults to get started quickly.
+
+# Installation
+
+Let's start with a classic Rails blog. We'll use a [template](http://guides.rubyonrails.org/rails_application_templates.html) to handle some of the boilerplate. Just run this command and accept all the defaults for now:
+
+{% highlight bash %}
+$ rails new blog --api -m https://raw.githubusercontent.com/jsonapi-suite/rails_template/master/all.rb
+{% endhighlight %}
+
+Feel free to run `git diff` if you're interested in the
+particulars; this is mostly just installing gems and including modules.
+
+> Note: if a network issue prevents you from pointing to this URL
+> directly, you can download the file and and run this command as `-m
+> /path/to/all.rb`
+
+# Defining a Resource
+
+A `Resource` defines how to query and persist your `Model`. In other
+words: a `Model` is to the database as `Resource` is to the API. So
+first, let's define our model:
+
+{% highlight bash %}
+$ bundle exec rails generate model Post title:string active:boolean
+$ bundle exec rake db:migrate
+{% endhighlight %}
+
+Now we can use the built-in generator to define our `Resource`,
+controller, and specs:
+
+{% highlight bash %}
+$ bundle exec rails g jsonapi:resource Post title:string active:boolean
+{% endhighlight %}
+
+You'll see a number of files created. If you open each one, you'll see
+comments explaining what's going on. Head over to the
+[tutorial]({{site.github.url}}/tutorial) for a more in-depth understanding. For now, let's
+focus on two key concepts you'll see over and over again: inputs (via
+[strong_resources](https://jsonapi-suite.github.io/strong_resources/)),
+and outputs (via [jsonapi-rb](http://jsonapi-rb.org)).
+
+Our **API Inputs** are defined in
+`config/initializers/strong_resources.rb`. You can think of these as
+[strong parameter](http://api.rubyonrails.org/v5.0/classes/ActionController/StrongParameters.html) templates.
+
+{% highlight ruby %}
+# config/initializers/strong_resources.rb
+strong_resource :post do
+ attribute :title, :string
+ attribute :active, :boolean
+end
+{% endhighlight %}
+
+Our **API Outputs** are defined in
+`app/serializers/serializable_post.rb`. The DSL is very similar to
+[active_model_serializers](https://github.com/rails-api/active_model_serializers) and full documentation can be found at [jsonapi-rb.org](http://jsonapi-rb.org):
+
+{% highlight ruby %}
+# app/serializers/serializable_post.rb
+class SerializablePost < JSONAPI::Serializable::Resource
+ type :posts
+
+ attribute :title
+ attribute :active
+end
+{% endhighlight %}
+
+Now run your app!:
+
+{% highlight bash %}
+$ bundle exec rails s
+{% endhighlight %}
+
+Verify `http://localhost:3000/api/v1/posts` renders JSON correctly.
+Now we just need data.
+
+# Seeding Data
+
+We can seed data in two ways: the usual `db/seeds.rb`, or using an HTTP
+client. Using the client helps get your feet wet with client-side
+development, or you can avoid the detour and plow right ahead.
+
+### Seeding With Ruby
+
+Edit `db/seeds.rb` to create a few `Post`s:
+
+{% highlight ruby %}
+Post.create!(title: 'My title!', active: true)
+Post.create!(title: 'Another title!', active: false)
+Post.create!(title: 'OMG! A title!', active: true)
+{% endhighlight %}
+
+And run the script:
+
+{% highlight bash %}
+$ bundle exec rake db:seed
+{% endhighlight %}
+
+### Seeding With JS Client
+
+There are a variety of [JSONAPI Clients](http://jsonapi.org/implementations/#client-libraries)
+out there. We'll be using [JSORM](/js/home)
+which is built to work with Suite-specific functionality like nested
+payloads. It can be used from the browser, but for now we'll call
+it using a simple Node script.
+
+Create the project:
+
+{% highlight bash %}
+$ mkdir node-seed && cd node-seed && touch index.js && npm init
+{% endhighlight %}
+
+Accept the default for all prompts. Now add the `JSORM` dependency, as
+well as a polyfill for `fetch`:
+
+{% highlight bash %}
+$ npm install --save jsorm isomorphic-fetch
+{% endhighlight %}
+
+Add this seed code to `index.js`:
+
+{% highlight javascript %}
+require("isomorphic-fetch");
+var jsorm = require("jsorm/dist/jsorm");
+
+// setup code
+
+var ApplicationRecord = jsorm.JSORMBase.extend({
+ static: {
+ baseUrl: "http://localhost:3000",
+ apiNamespace: "/api/v1"
+ }
+});
+
+var Post = ApplicationRecord.extend({
+ static: {
+ jsonapiType: "posts"
+ },
+
+ attrs: {
+ title: jsorm.attr(),
+ active: jsorm.attr()
+ }
+});
+
+// seed code
+
+var post1 = new Post({
+ title: "My title!",
+ active: true
+});
+
+var post2 = new Post({
+ title: "Another title!",
+ active: false
+});
+
+var post3 = new Post({
+ title: "OMG! A title!",
+ active: true
+});
+
+// Save sequentially only due to local development env
+post1.save().then(() => {
+ post2.save().then(() => {
+ post3.save();
+ });
+});
+{% endhighlight %}
+
+This should be pretty straightforward if you're familiar with
+`ActiveRecord`. We define `Model` objects, putting configuration on
+class attributes. We instatiating instances of those Models, and call
+`save()` to persist. For more information, see the [JSORM Documentation](https://jsonapi-suite.github.io/jsorm/).
+
+Run the script:
+
+{% highlight bash %}
+$ node index.js
+{% endhighlight %}
+
+Now load `http://localhost:3000/api/v1/posts`. You should have 3 `Post`s in
+your database!
+
+
+
+# Querying
+
+Now that we've defined our `Resource` and seeded some data, let's see
+what query functionality we have. We've listed all `Post`s at
+`http://localhost:3000/api/v1/posts`. Let's see what we can do:
+
+* **Sort**
+ * By title, ascending:
+ * URL: `/api/v1/posts?sort=title`
+ * SQL: `SELECT * FROM posts ORDER BY title ASC`
+ * By title, descending:
+ * URL: `/api/v1/posts?sort=-title`
+ * SQL: `SELECT * FROM posts ORDER BY title DESC`
+
+* **Paginate**:
+ * 2 Per page:
+ * URL: `/api/v1/posts?page[size]=2`
+ * SQL: `SELECT * FROM posts LIMIT 2`
+ * 2 Per page, second page:
+ * URL: `/api/v1/posts?page[size]=2&page[number]=2`
+ * SQL: `SELECT * FROM posts LIMIT 2 OFFSET 2`
+
+* **Sparse Fieldsets**:
+ * Only render `title`, not `active`:
+ * URL: `/api/v1/posts?fields[posts]=title`
+ * SQL: `SELECT * from posts` (*optimizing this query is on the roadmap*)
+
+* **Filter**:
+ * Add one line of code:
+
+{% highlight ruby %}
+# app/resources/post_resource.rb
+allow_filter :title
+{% endhighlight %}
+ * URL: `/api/v1/posts?filter[title]=My title!`
+ * SQL: `SELECT * FROM posts WHERE title = "My title!"`
+ * Any filter not whitelisted will raise `JsonapiCompliable::BadFilter`
+ error.
+ * All filter logic can be customized:
+
+{% highlight ruby %}
+# SELECT * from posts WHERE title LIKE 'My%'
+allow_filter :title_prefix do |scope, value|
+ scope.where(["title LIKE ?", "#{value}%"])
+end
+{% endhighlight %}
+ * Customizations can be DRYed up and packaged into `Adapter`s.
+
+* **Extra Fields**:
+ * Sometimes you want to request additional fields not part of a normal
+ response (perhaps they are computationally expensive).
+ * This can be done like so:
+
+{% highlight ruby %}
+# app/serializers/serializable_post.rb
+extra_attribute :description do
+ @object.active? ? 'Active Post' : 'Inactive Post'
+end
+{% endhighlight %}
+
+ * URL: `/api/v1/posts?extra_fields[posts]=description`
+ * SQL: `SELECT * FROM posts`
+ * You can conditionally eager load data or further customize this
+ logic. See the tutorial for more.
+
+* **Statistics**:
+ * Useful for search grids - "Find me the first 10 active posts, and
+ the total count of all posts".
+ * One line of code to whitelist the stat:
+
+{% highlight ruby %}
+# app/resources/post_resource.rb
+allow_stat total: [:count]
+{% endhighlight %}
+
+ * URL: `/api/v1/posts?stats[total]=count`
+ * SQL: `SELECT count(*) from posts`
+ * Combine with filters and the count will adjust accordingly.
+ * There are a number of built-in stats, you can also add your own.
+ * This is rendered in the `meta` section of the response:
+
+ 
+
+* **Error Handling**:
+ * Your app will always render a JSONAPI-compliable error response.
+ * Cause an error:
+
+{% highlight ruby %}
+# app/controllers/posts_controller.rb
+def index
+ raise 'foo'
+end
+{% endhighlight %}
+
+ * View the default payload:
+
+ 
+
+ * Different errors can be customized with different response codes,
+ JSON, and side-effects. View [jsonapi_errorable](https://jsonapi-suite.github.io/jsonapi_errorable/) for more.
+
+# Adding Relationships
+
+JSONAPI Suite supports full querying of relationships ("fetch me this
+`Post` and 3 active `Comment`s sorted by creation date"), as well as
+persistence ("save this `Post` and 3 `Comment`s in a single request").
+
+### Adding Relationships
+
+Let's start by defining our model:
+
+{% highlight bash %}
+$ bundle exec rails g model Comment post_id:integer body:text active:boolean
+$ bundle exec rake db:migrate
+{% endhighlight %}
+
+{% highlight ruby %}
+# app/models/post.rb
+has_many :comments
+
+# app/models/comment.rb
+belongs_to :post, optional: true
+{% endhighlight %}
+
+...and corresponding `Resource` object:
+
+{% highlight bash %}
+$ bundle exec rails g jsonapi:resource Comment body:text active:boolean
+{% endhighlight %}
+
+Configure the relationship in `PostResource`:
+
+{% highlight ruby %}
+has_many :comments,
+ foreign_key: :post_id,
+ resource: CommentResource,
+ scope: -> { Comment.all }
+{% endhighlight %}
+
+This code:
+
+* Whitelists the relationship.
+* Knows to link the objects via `post_id`.
+* Will use `CommentResource` for querying logic (so we can say things
+like "only return the latest 3 active comments")
+* Uses an unfiltered base scope (`Comment.all`). If we wanted, we could
+do things like `Comment.active` here to ensure only active comments are
+ever returned.
+
+You should now be able to hit `/api/v1/comments` with all the same
+functionality as before. We just need to seed data.
+
+Start by clearing out your database:
+
+{% highlight bash %}
+$ bundle exec rake db:migrate:reset
+{% endhighlight %}
+
+Again, you can seed your data using a NodeJS client or the traditional
+`db/seeds.rb`.
+
+#### Seeding with NodeJS
+
+Let's edit our `node-seed/index.js`. First add a `Comment` model:
+
+{% highlight javascript %}
+var Comment = ApplicationRecord.extend({
+ static: {
+ jsonapiType: 'comments'
+ },
+
+ attrs: {
+ body: jsorm.attr(),
+ active: jsorm.attr(),
+ createdAt: jsorm.attr()
+ }
+});
+{% endhighlight %}
+
+...and add the relationship to `Post`:
+
+{% highlight javascript %}
+// within attrs
+// ... code ...
+comments: jsorm.hasMany()
+// ... code...
+{% endhighlight %}
+
+Replace the existing `Post` instances with one `Post` and three
+`Comment`s:
+
+{% highlight javascript %}
+var comment1 = new Comment({
+ body: "comment one",
+ active: true
+});
+
+var comment2 = new Comment({
+ body: "comment two",
+ active: false
+});
+
+var comment3 = new Comment({
+ body: "comment three",
+ active: true
+});
+
+var post = new Post({
+ title: "My title!",
+ active: true,
+ comments: [comment1, comment2, comment3]
+});
+
+post.save({ with: ["comments"] });
+{% endhighlight %}
+
+Tell our controller it's OK to sidepost comments:
+
+{% highlight ruby %}
+# app/controllers/posts_controller.rb
+strong_resource :post do
+ has_many :comments
+end
+{% endhighlight %}
+
+And tell our serializer it's OK to render comments:
+
+{% highlight ruby %}
+# app/serializers/serializable_post.rb
+has_many :comments
+{% endhighlight %}
+
+Now run the script to persist the `Post` and its three `Comment`s in a
+single request:
+
+{% highlight bash %}
+$ node node-seed/index.js
+{% endhighlight %}
+
+#### Seeding with Ruby
+
+Replace your `db/seeds.rb` with this code to persist one `Post` and
+three `Comment`s:
+
+{% highlight ruby %}
+comment1 = Comment.new(body: 'comment one', active: true)
+comment2 = Comment.new(body: 'comment two', active: false)
+comment3 = Comment.new(body: 'comment three', active: true)
+
+Post.create! \
+ title: 'My title!',
+ active: true,
+ comments: [comment1, comment2, comment3]
+{% endhighlight %}
+
+## Usage
+
+Now let's fetch a `Post` and filtered `Comment`s in a single request: `/api/v1/posts?include=comments`.
+
+Any logic in `CommentResource` is available to us. Let's sort the
+comments by `created_at` descending: `/api/v1/posts?include=comments&sort=-comments.created_at`. This should work out-of-the-box.
+
+Now add a filter to `CommentResource`:
+
+{% highlight ruby %}
+allow_filter :active
+{% endhighlight %}
+
+That filter now works in two places:
+
+* `/api/v1/comments?filter[active]=true`
+* `/api/v1/posts?include=comments&filter[comments][active]=true`
+
+This is why `Resource` objects exist: they provide an interface to
+functionality shared across many different endpoints, with no extra
+code.
+
+# What's Next
+
+We have a full CRUD API with robust querying functionality, and the
+ability to combine relationships for both reads and writes. But what
+happens when you need to customize the sorting logic? What about replacing
+`ActiveRecord` with an alternate persistence layer, or avoiding Rails
+altogether?
+
+These are important topics that JSONAPI Suite was built to address. To
+learn more about advanced usage and customization, we suggest following
+the [tutorial](/tutorial). There are also a number of how-tos on this
+site, a good one to start with is How
+to Use without ActiveRecord
+
+For additional documentation, view the [YARD Docs](https://jsonapi-suite.github.io/jsonapi_compliable/).
+
+For help with specific use cases, [join our Slack chat](https://join.slack.com/t/jsonapi-suite/shared_invite/enQtMjkyMTA3MDgxNTQzLWVkMDM3NTlmNTIwODY2YWFkMGNiNzUzZGMzOTY3YmNmZjBhYzIyZWZlZTk4YmI1YTI0Y2M0OTZmZGYwN2QxZjg)!
+
+# Bonus: Testing
+
+### Installation
+
+Our generator applied some sensible defaults:
+
+ * [Rspec](https://github.com/rspec/rspec-rails) Test runner
+ * [jsonapi_spec_helpers](https://jsonapi-suite.github.io/jsonapi_spec_helpers) Helpers to parse and assert on JSONAPI payloads.
+ * [factory_girl](https://github.com/thoughtbot/factory_girl) for
+ seeding our test database with fake data.
+ * [faker](https://github.com/stympy/faker) for generating fake values,
+ such as e-mail addresses, names, avatar URLs, etc.
+ * [database_cleaner](https://github.com/DatabaseCleaner/database_cleaner)
+ to ensure our fake data gets cleaned up between test runs.
+
+By default we rescue exceptions and return a valid [error response](http://jsonapi.org/format/#errors).
+In tests, this can be confusing - we probably want to raise errors in
+tests. So note our exception handling is disabled by default:
+
+{% highlight ruby %}
+# spec/rails_helper.rb
+config.before :each do
+ JsonapiErrorable.disable!
+end
+{% endhighlight %}
+
+But you can enable it on a per-test basis:
+
+{% highlight ruby %}
+it "renders validation errors" do
+ JsonapiErrorable.enable!
+ post "/api/v1/employees", payload
+ expect(validation_errors[:name]).to eq("can't be blank")
+end
+{% endhighlight %}
+
+In following this guide, we generated `Post` and
+`Comment` resources. Let's edit our [factories](https://github.com/thoughtbot/factory_bot) to seed randomized data:
+
+{% highlight ruby %}
+# spec/factories/post.rb
+FactoryGirl.define do
+ factory :post do
+ title { Faker::Lorem.sentence }
+ active { [true, false].sample }
+ end
+end
+
+# spec/factories/comment.rb
+FactoryGirl.define do
+ factory :comment do
+ body { Faker::Lorem.paragraph }
+ active { [true, false].sample }
+ end
+end
+{% endhighlight %}
+
+Finally, we need to define a `Payload`. `Payload`s use a
+`factory_girl`-style DSL to define expected JSON. A `Payload` compares a
+`Model` instance and JSON output, ensuring:
+
+* No unexpected keys
+* No missing keys
+* No unexpected value types
+* No `null` values (this is overrideable)
+* Model attribute matches JSON attribute
+* This can all be customized. See [jsonapi_spec_helpers](https://github.com/jsonapi-suite/jsonapi_spec_helpers) for more.
+
+Let's define our payloads now:
+
+{% highlight ruby %}
+# spec/payloads/post.rb
+JsonapiSpecHelpers::Payload.register(:post) do
+ key(:title, String)
+ key(:active, [TrueClass, FalseClass])
+end
+
+# spec/payloads/comment.rb
+JsonapiSpecHelpers::Payload.register(:comment) do
+ key(:body, String)
+ key(:active, [TrueClass, FalseClass])
+ key(:created_at, Time)
+end
+{% endhighlight %}
+
+### Run
+
+We can now run specs. Let's start with the `Post` specs:
+
+{% highlight bash %}
+$ bundle exec rspec spec/api/v1/posts
+{% endhighlight %}
+
+You should see five specs, with one failing (`spec/api/v1/posts/create_spec.rb`),
+and one pending (`spec/api/v1/posts/update_spec.rb`).
+
+The reason for the failure is simple: our payload defined in
+`spec/payloads/post.rb` specifies that a `Post` JSON should include the
+key `title`. However, that spec is currently creating a `Post` with no
+attributes...which means in the response JSON, `title` is `null`. `null`
+values will fail `assert_payload` unless elsewise configured.
+
+So, let's update our spec to POST attributes, not just an empty object:
+
+{% highlight ruby %}
+let(:payload) do
+ {
+ data: {
+ type: 'posts',
+ attributes: {
+ title: 'My title!',
+ active: true
+ }
+ }
+ }
+end
+{% endhighlight %}
+
+Your specs should now pass. The only pending spec is due to a similar
+issue - we need to specify attributes in `spec/api/v1/posts/update_spec.rb` as
+well. Follow the comments in that file to apply a similar change.
+
+You should now have 5 passing request specs! These specs spin up a fake
+server, then execute full-stack requests that hit the database and
+return JSON. You're asserting that JSON matches predefined payloads,
+without `null`s or unknown key/values.
+
+Go ahead and make the same changes to `Comment` specs to get 10 passing
+request specs.
+
+It's up to you how far you'd like to go with testing. Should you add a
+new spec to `spec/api/v1/posts/index_spec.rb` every time you add a
+filter with `allow_filter`? This boils down to personal preference and
+tolerance of failures. Try adding a few specs following the generated
+patterns to get a feel for what's right for you.
+
+### Bonus: Documentation
+
+We can autodocument our code using [swagger documentation](https://swagger.io). Documenting an endpoint is one line of code:
+
+{% highlight ruby %}
+jsonapi_resource '/v1/employees'
+{% endhighlight %}
+
+Visit `http://localhost:3000/api/docs` to see the swagger documentation. Our custom UI will show all possible query parameters (including nested
+relationships), as well as schemas for request/responses:
+
+
+
+Our generator set up some boilerplate to enable this functionality, you
+can learn more at: How
+to Autodocument with Swagger
+
+
+
+
diff --git a/ruby-toc.md b/ruby-toc.md
new file mode 100644
index 0000000..85e6c14
--- /dev/null
+++ b/ruby-toc.md
@@ -0,0 +1,12 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Filtering
+
+asdf
+
+{% include highlight.html %}
diff --git a/ruby/alternate-datastores/adapters.md b/ruby/alternate-datastores/adapters.md
new file mode 100644
index 0000000..1f1d642
--- /dev/null
+++ b/ruby/alternate-datastores/adapters.md
@@ -0,0 +1,127 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Adapters
+
+> [Read First: Resources Overview]({{site.github.url}}/ruby/resources)
+
+> [View the Sample App](https://github.com/jsonapi-suite/employee_directory/compare/step_23_disassociation...elasticsearch_adapter)
+
+> [View the YARD Documentation](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Adapters/Abstract.html)
+
+If you find yourself repeatedly making customizations to a group of
+`Resource`s and seek DRYer code, package those customizations into an
+`Adapter`. Here we'll be starting from a previous example,
+[ElasticSearch]({{site.github.url}}/ruby/alternate-datastores/elasticsearch).
+
+Adapters are simpler than you might think. It's little more than
+copy-pasting those low-level customizations into a common class.
+
+Start by creating `lib/elasticsearch_adapter.rb`. Cut/past the sorting,
+pagination, and `#resolve` overrides from `EmployeeResource` into the adapter,
+turning into `def` methods along the way:
+
+{% highlight ruby %}
+# lib/elasticsearch_adapter.rb
+class ElasticsearchAdapter
+ def paginate(scope, current_page, per_page)
+ scope.metadata.pagination.current_page = current_page
+ scope.metadata.pagination.per_page = per_page
+ scope
+ end
+
+ def order(scope, att, dir)
+ scope.metadata.sort = [{att: att, dir: dir}]
+ scope
+ end
+
+ def resolve(scope)
+ scope.query!
+ scope.results
+ end
+end
+{% endhighlight %}
+
+Ensure our adapter gets loaded:
+
+{% highlight ruby %}
+# config/initializers/jsonapi.rb
+require 'elasticsearch_adapter'
+{% endhighlight %}
+
+And switch to that adapter in `EmployeeResource`:
+
+{% highlight ruby %}
+use_adapter ElasticsearchAdapter
+{% endhighlight %}
+
+Bounce your server. You can still hit the `/api/v1/employees` endpoint
+with the same sort and paginate functionality, but the code has been
+moved to an adapter.
+
+Let's ensure our users can filter as well:
+
+{% highlight ruby %}
+def filter(scope, att, val)
+ scope.condition(att).eq(val)
+end
+{% endhighlight %}
+
+For all the methods and functionality an adapter supports, see the
+[Adapter documenation](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Adapters/Abstract.html).
+
+#### Association Macros
+
+> [Read First: Nested Queries]({{site.github.url}}/ruby/reads/nested)
+
+We probably also want `has_many`-style macros to avoid writing similar
+`allow_sideload` code time after time. Start by specifying where this
+functionality is defined, and add a `has_many` macro:
+
+{% highlight ruby %}
+module Sideloading
+ def has_many(association_name,
+ scope:,
+ resource:,
+ foreign_key:,
+ primary_key: :id,
+ &blk)
+ # our code will go here
+ instance_eval(&blk) if blk
+ end
+end
+
+def sideloading_module
+ Sideloading
+end
+{% endhighlight %}
+
+The `instance_eval` is there so we can always drop down to a lower-level
+customization in our `Resource`.
+
+We can basically cut/paste our existing sideload code and rewrite it as
+variables:
+
+{% highlight ruby %}
+scope do |parents|
+ parent_ids = parents.map { |p| p.send(primary_key) }
+ scope.call.condition(foreign_key).or(parent_ids.uniq.compact)
+end
+
+assign do |parents, children|
+ parents.each do |p|
+ relevant_children = children.select do |c|
+ c.send(foreign_key) == p.send(primary_key)
+ end
+ p.send(:"#{association_name}=", relevant_children)
+ end
+end
+{% endhighlight %}
+
+You can now remove any customizations from your `Resource` classes. You
+can continue to build the adapter, adding `belongs_to`, statistics, and
+more. [View the adapter documentation for the full API](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Adapters/Abstract.html).
diff --git a/ruby/alternate-datastores/elasticsearch.md b/ruby/alternate-datastores/elasticsearch.md
new file mode 100644
index 0000000..a60798f
--- /dev/null
+++ b/ruby/alternate-datastores/elasticsearch.md
@@ -0,0 +1,215 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Usage Without ActiveRecord: ElasticSearch
+
+> [Read First: Resources Overview]({{site.github.url}}/ruby/resources)
+
+> [View the Sample App](https://github.com/jsonapi-suite/employee_directory/compare/step_23_disassociation...elasticsearch)
+
+> [View the YARD Documentation](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Resource.html)
+
+Though we'll be hitting [elasticsearch](https://www.elastic.co) in this
+example, remember that this is just an HTTP API underneath the hood. The
+same pattern applies to a variety of use cases.
+
+First we need a **Client** for `elasticsearch`. Though you can feel free
+to use a variety of clients, this example will use [trample](http://richmolj.github.io/trample).
+
+Also keep in mind, we'll be showing a one-off customization here. You
+probably want to extract this code into an
+[Adapter]({{site.github.url}}/ruby/alternate-datastores/adapters) if this is going to
+become a core component of your application.
+
+#### Pre-JSONAPI Setup
+
+Start by installing trample:
+
+{% highlight ruby %}
+# Gemfile
+gem 'trample_search', require: 'trample'
+{% endhighlight %}
+
+Tell [searchkick](https://github.com/ankane/searchkick) that we want to
+index `Employee`s and `Position`s:
+
+{% highlight ruby %}
+# app/models/employee.rb
+searchkick text_start: [:first_name]
+
+# app/models/position.rb
+searchkick text_start: [:title]
+{% endhighlight %}
+
+Define our search classes. These tell trample the configuration of the
+search:
+
+{% highlight ruby %}
+# app/models/employee_search.rb
+class EmployeeSearch < Trample::Search
+ model Employee
+
+ condition :first_name, single: true
+ condition :last_name, single: true
+end
+
+# app/models/position_search.rb
+class PositionSearch < Trample::Search
+ model Position
+
+ condition :title, single: true
+ condition :employee_id
+end
+{% endhighlight %}
+
+Finally, seed some data:
+
+{% highlight ruby %}
+# db/seeds.rb
+[Employee, Position].each(&:delete_all)
+bart = Employee.create!(first_name: 'Bart', last_name: 'Simpson')
+homer = Employee.create!(first_name: 'Homer', last_name: 'Simpson')
+monty = Employee.create!(first_name: 'Monty', last_name: 'Burns')
+
+bart.positions.create!(title: 'Junior Engineer')
+homer.positions.create!(title: 'Senior Engineer')
+monty.positions.create!(title: 'Manager')
+{% endhighlight %}
+
+#### JSONAPI Integration
+
+In our controller, we need to pass a base scope. In `ActiveRecord` examples, we'd pass an `ActiveRecord::Relation` (e.g. `Post.all`). Let's pass an instance
+of `Trample::Search` instead.
+
+{% highlight ruby %}
+# app/controllers/employees_controller.rb
+def index
+ render_jsonapi(EmployeeSearch.new)
+end
+{% endhighlight %}
+
+{% highlight ruby %}
+# app/serializers/serializable_employee.rb
+class SerializableEmployee < JSONAPI::Serializable::Resource
+ type :employees
+
+ attribute :first_name
+ attribute :last_name
+ attribute :created_at
+ attribute :updated_at
+
+ has_many :positions
+end
+{% endhighlight %}
+
+Since we are now passing a non-default base scope, we need to tell our
+`Resource` how to query and resolve this new scope. Start by switching to
+the pass-through adapter, and resolve using `trample`'s query API:
+
+{% highlight ruby %}
+# app/resources/employee_resource.rb
+use_adapter JsonapiCompliable::Adapters::Null
+# ... code ...
+def resolve(scope)
+ scope.query!
+ scope.records.compact
+end
+
+# no belongs_to for now
+{% endhighlight %}
+
+You can now hit `http://localhost:3000/api/v1/employees` - the exact
+same payload is coming back, but is now sourced from `elasticsearch`!
+
+Let's add a prefix filter:
+
+{% highlight ruby %}
+# app/resources/employee_resource.rb
+allow_filter :first_name_prefix do |scope, value|
+ scope.condition(:first_name).starts_with(value)
+end
+{% endhighlight %}
+
+Hit `http://localhost:3000/api/v1/employees?filter[first_name]=hom`.
+You're now successfully querying the `elasticsearch` index.
+
+ If we want sorting and pagination, we need to tell the `Resource`
+ how to deal with that, too:
+
+{% highlight ruby %}
+# app/resources/employee_resource.rb
+paginate do |scope, current_page, per_page|
+ scope.metadata.pagination.current_page = current_page
+ scope.metadata.pagination.per_page = per_page
+ scope
+end
+
+sort do |scope, att, dir|
+ scope.metadata.sort = [{att: att, dir: dir}]
+ scope
+end
+{% endhighlight %}
+
+View the [Resource](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Resource.html) and [Adapter](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Adapters/Abstract.html) documentation for additional overrides, like statistics.
+
+The last step is adding the `positions` association. If we want
+`has_many`-style macros we need to create an
+[Adapter]({{site.github.url}}/ruby/alternate-datastores/adapters), but for now
+let's simply use the lower-level [allow_sideload]({{site.github.url}}/) DSL. We need to define
+two functions: how to build a scope for the association, and how to
+associate the resulting objects:
+
+{% highlight ruby %}
+# app/resources/employee_resource.rb
+allow_sideload :positions, resource: PositionResource do
+ scope do |employees|
+ scope = PositionSearch.new
+ scope.condition(:employee_id).or(employees.map(&:id))
+ end
+
+ assign do |employees, positions|
+ employees.each do |e|
+ e.positions = positions.select { |p| p.employee_id = e.id }
+ end
+ end
+end
+{% endhighlight %}
+
+Convert the `PositionResource` to use `elasticsearch`, just like we did
+for `Employee`:
+
+{% highlight ruby %}
+# app/resources/position_resource.rb
+use_adapter JsonapiCompliable::Adapters::Null
+
+def resolve(scope)
+ scope.query!
+ scope.results
+end
+{% endhighlight %}
+
+Create the `SerializablePosition` class:
+
+{% highlight ruby %}
+class SerializablePosition < JSONAPI::Serializable::Resource
+ type :positions
+
+ attribute :title
+end
+{% endhighlight %}
+
+We can now sideload `positions` - check out the results at
+`http://localhost:3000/api/v1/employees?include=positions`. We're
+fetching employees and their corresponding `positions` in a single
+request, via `elasticsearch`. Any filters/changes/default sort/etc that
+apply to `PositionResource` can be re-used at this endpoint.
+
+If this was a one-off section of our application, we can call this good
+enough and move on. But as we continue to use this pattern, it's going
+to get monotonous writing the same filter overrides, `allow_sideload`
+wiring code, etc. To DRY up this code, we can package our changes into
+an [Adapter]({{site.github.url}}/ruby/alternate-datastores/adapters).
diff --git a/ruby/alternate-datastores/http.md b/ruby/alternate-datastores/http.md
new file mode 100644
index 0000000..58c9b43
--- /dev/null
+++ b/ruby/alternate-datastores/http.md
@@ -0,0 +1,103 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Usage Without ActiveRecord: HTTP Services
+
+> [Read First: Resources Overview]({{site.github.url}}/ruby/resources)
+
+> [View the YARD Documentation](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Resource.html)
+
+This is a commonly requested example. Instead of using a full-fledged
+client like `ActiveRecord` or `Trample`, we'll show low-level usage that
+could apply to a variety of HTTP clients.
+
+Remember, we always start with a "base scope" and modify that scope
+depending on incoming request parameters. This same pattern could apply
+to simply ruby hashes.
+
+{% highlight ruby %}
+def index
+ render_jsonapi({})
+end
+{% endhighlight %}
+
+Let's start by specifying a `Null` adapter - a pass-through adapter that
+won't do anything without us explicitly overriding:
+
+{% highlight ruby %}
+# config/initializers/jsonapi.rb
+require 'jsonapi_compliable/adapters/null'
+{% endhighlight %}
+
+{% highlight ruby %}
+# app/resources/post_resource.rb
+use_adapter JsonapiCompliable::Adapters::Null
+{% endhighlight %}
+
+Every time we get a request to sort, paginate, etc we'll need to modify
+our hash. Here we'll simply merge parameters in the format our HTTP
+client will accept:
+
+{% highlight ruby %}
+# app/resources/post_resource.rb
+allow_filter :title do |scope, value|
+ scope.merge!(conditions: { title: value })
+end
+
+sort do |scope, attribute, direction|
+ scope.merge!(order: { attribute => direction })
+end
+
+paginate do |scope, current_page, per_page|
+ offset = (current_page * per_page) - per_page
+ scope.merge!(limit: per_page, offset: offset)
+end
+{% endhighlight %}
+
+Finally, we need to tell the resorce how to resolve the query. In our
+case, this means passing the built-up parameters into a method on our
+HTTP client.
+
+{% highlight ruby %}
+# app/resources/post_resource.rb
+
+# Remember, 'scope' here is a hash
+def resolve(scope)
+ results = MyHTTPClient.get(scope)
+ results.map { |r| Post.new(r) }
+end
+{% endhighlight %}
+
+Note that [#resolve](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Resource.html#resolve-instance_method) must return an array of `Model` instances. These
+can be simple POROs, as you see above.
+
+The final request would look something like this:
+
+{% highlight ruby %}
+HTTPClient.get \
+ conditions: { title: "Hello World!" },
+ order: { created_at: :desc },
+ limit: 10,
+ offset: 20
+{% endhighlight %}
+
+In our controller, if we used the lower-level [jsonapi_scope](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Base.html#jsonapi_scope-instance_method) method to introspect our results, we'd see an array of `Post` instances:
+
+{% highlight ruby %}
+# app/controllers/posts_controller.rb
+def index
+ scope = jsonapi_scope({})
+ posts = scope.resolve
+ puts posts # [#, #, ...]
+ render_jsonapi(posts, scope: false)
+end
+{% endhighlight %}
+
+If we found ourselves typing similar `Resource` code - always merging in
+the same paramters to the hash - we'd probably want to package all this
+up into an
+[Adapter]({{site.github.url}}/ruby/alternate-datastores/adapters).
diff --git a/ruby/alternate-datastores/index.md b/ruby/alternate-datastores/index.md
new file mode 100644
index 0000000..90c4952
--- /dev/null
+++ b/ruby/alternate-datastores/index.md
@@ -0,0 +1,30 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Alternate Datastores
+
+It's important to note that `ActiveRecord` is nothing but a sensible
+default. Because you have [full control over the
+query]({{site.github.url}}/ruby/reads/resources) JSONAPI Suite can be
+used with any datastore, from MongoDB to HTTP service calls.
+
+In this section, we'll show examples customizing resource logic, then
+packaging that logic into reusable `Adapter`s.
+
+> Keep in mind, multiple datastores can be blended in a single request.
+> We can load `Post`s from a SQL database, and "sideload" `Comment`s
+> from MongoDB seamlessly.
+
+* [ElasticSearch
+ Example]({{site.github.url}}/ruby/alternate-datastores/elasticsearch)
+
+* [PORO Example]({{site.github.url}}/ruby/alternate-datastores/poro)
+
+* [HTTP Service
+ Example]({{site.github.url}}/ruby/alternate-datastores/http)
+
+* [Adapters]({{site.github.url}}/ruby/alternate-datastores/adapters)
diff --git a/ruby/backwards-compatibility.md b/ruby/backwards-compatibility.md
new file mode 100644
index 0000000..c7d0fa7
--- /dev/null
+++ b/ruby/backwards-compatibility.md
@@ -0,0 +1,42 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Backwards Compatibility
+
+Before we deploy our code to production, we want to ensure we
+aren't introducing any backwards incompatibilities that will break
+existing clients. Of course, tests are our first line of defense here.
+But a developer could always simply update the test and introduce a
+backwards-incompatibility. This is why JSONAPI Suite comes with an
+additional backwards-compatibility check you can run in your Continuous
+Integration pipeline.
+
+In the course of writing our application, we [autodocumented
+with Swagger]({{site.github.url}}/ruby/swagger). That means our
+`swagger.json` is effectively a **schema** - a definition of
+attributes, types, query parameters and payloads. We can compare the
+`swagger.json` of a given commit to what's running in production to
+see if any backwards-incompatibilities were introduced.
+
+If you used our [generator]({{site.github.url}}/ruby/installation) to set up your application, you'll have noticed this line added to `Rakefile`:
+
+{% highlight ruby %}
+require 'jsonapi_swagger_helpers'
+{% endhighlight %}
+
+This allows us to run the rake task
+
+{% highlight bash %}
+rake swagger_diff['my_api','http://example.com']
+{% endhighlight %}
+
+This task will:
+
+ * Pull down the schema from `http://example.com/my_api/swagger.json`.
+ * Compare it to the `swagger.json` generated locally.
+
+This uses [swagger-diff](https://github.com/civisanalytics/swagger-diff) underneath the hood. You'll get helpful output noting any missing filters, incorrect types, or other backwards incompatibilities.
diff --git a/ruby/error-handling.md b/ruby/error-handling.md
new file mode 100644
index 0000000..d839dd3
--- /dev/null
+++ b/ruby/error-handling.md
@@ -0,0 +1,42 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Error Handling
+
+> [View the jsonapi_errorable documentation](https://jsonapi-suite.github.io/jsonapi_errorable)
+
+We want to follow the common best-practice of raising a specific error class:
+
+{% highlight ruby %}
+raise Errors::NotAuthorized
+{% endhighlight %}
+
+The problem is, this error would cause our server to return a `500`
+status code, without much helpful detail. Instead, we want to
+customize our responses based on the error thrown.
+
+Enter [jsonapi_errorable](https://jsonapi-suite.github.io/jsonapi_errorable), which provides a simple DSL to do just that:
+
+{% highlight ruby %}
+# app/controllers/application_controller.rb
+register_exception Errors::NotAuthorized, status: 403
+{% endhighlight %}
+
+Here we've customized the [error response](http://jsonapi.org/format/#errors) to send the HTTP status code `403` ([forbidden](https://httpstatuses.com/403)) whenever this particular exception class is raised.
+
+Maybe our error class already has a message we want to display to the
+user:
+
+{% highlight ruby %}
+register_exception Errors::NotAuthorized,
+ status: 403,
+ title: "Not Authorized",
+ message: true,
+{% endhighlight %}
+
+For full documentation on everything you can do here, head over to
+the [jsonapi_errorable](https://jsonapi-suite.github.io/jsonapi_errorable/) documentation.
diff --git a/ruby/index.md b/ruby/index.md
new file mode 100644
index 0000000..2dfeb4d
--- /dev/null
+++ b/ruby/index.md
@@ -0,0 +1,21 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Server-Side
+
+JSONAPI Suite is a collection of ruby libraries that facilitate adhering
+to the [jsonapi.org](http://jsonapi.org) specification. At its heart is
+a query builder that supports fully nested reads and writes, adaptable
+to any datastore, from ActiveRecord to MongoDB to raw HTTP service calls. We'll autodocument those endpoints for you with [Swagger]({{site.github.url}}/ruby/swagger), provide an [error handling pattern]({{site.github.url}}/ruby/error-handling), and test everything will full-stack integration [request specs]({{site.github.url}}/ruby/testing).
+
+If you just want to get rolling quickly, check out the
+[Quickstart]({{site.github.url}}/quickstart). For step-by-step examples,
+check out the [Tutorial]({{site.github.url}}/tutorial). Or, for
+client-side usage, check out [JSORM]({{site.github.url}}/js/home).
+
+For any questions not addressed here, feel free to ask in our [Slack
+Chat](https://jsonapi-suite.slack.com/join/shared_invite/enQtMjkyMTA3MDgxNTQzLWVkMDM3NTlmNTIwODY2YWFkMGNiNzUzZGMzOTY3YmNmZjBhYzIyZWZlZTk4YmI1YTI0Y2M0OTZmZGYwN2QxZjg).
diff --git a/ruby/installation.md b/ruby/installation.md
new file mode 100644
index 0000000..380010c
--- /dev/null
+++ b/ruby/installation.md
@@ -0,0 +1,90 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Installation
+
+JSONAPI Suite comes with a [Rails Application Template](http://guides.rubyonrails.org/rails_application_templates.html) to get you up and running quickly. To apply a template, you can pass either a URL or path to a file.
+
+To generate a new application with the template:
+
+{% highlight bash %}
+$ rails new myapp --api -m https://raw.githubusercontent.com/jsonapi-suite/rails_template/master/all.rb
+{% endhighlight %}
+
+If needed, this command can be run by downloading `all.rb` and
+pointing to it on your filesystem:
+
+{% highlight bash %}
+$ rails new myapp --api -m /path/to/all.rb
+{% endhighlight %}
+
+Run `git status` to see what the generator did.
+
+### Breaking down the generator
+
+The generator mostly installs gems and types some boilerplate for you.
+But it can be helpful to understand everything that's going on and
+customize to your needs (these are all customizable defaults).
+Here's a line-by-line breakdown to explain what's going on. Use `git
+status` to follow along.
+
+* **app/controllers/application_controller.rb**
+ * Mixes in `JsonapiSuite::ControllerMixin`. This includes relevant
+ modules that decorate our controllers with methods like
+ `render_jsonapi`.
+ * Sets up global error handling, ensuring that we always render a
+ JSONAPI-compliant [errors payload](http://jsonapi.org/format/#errors).
+ Errors are handled through a DSL provided by [jsonapi_errorable](https://jsonapi-suite.github.io/jsonapi_errorable) - throw an error and use the DSL to customize response codes, messages, etc. In this code we'll follow a common Rails pattern and respond with `404` from `show` endpoints when a record is not found in the datastore.
+
+* **config/routes.rb**
+ * Configures routing so all our endpoints will be under `//v1`. The `` is so you can point something like [HAProxy](http://www.haproxy.org) to various microservices based on the path. The `v1` sets up a simple versioning pattern.
+ * Adds a `docs` resource. This for automatic [Swagger](https://swagger.io) documentation. Swagger requires a schema - `swagger.json` - that is generated from our `DocsController`. For more on this, see the [Autodocumentation]({{ site.github.url }}/ruby/swagger) section.
+
+* **spec/rails_helper.rb**
+ * Adds [jsonapi_spec_helpers](https://jsonapi-suite.github.io/jsonapi_spec_helpers). This gives us helper methods like `json_item` and `assert_payload` that lower the overhead of dealing with verbose JSONAPI responses. See more in the [Testing]({{ site.github.url }}/ruby/testing) section.
+ * `JsonapiErrorable.disable!` disables global error handling before
+ each test. This is because we usually don't want errors to be
+ swallowed during tests. You can always turn it back on in a per-test
+ basis with `JsonapiErrorable.enable!`
+ * Adds [database_cleaner](https://github.com/DatabaseCleaner/database_cleaner) to ensure a clean database between tests.
+ * Mixes in [factory_bot](https://github.com/thoughtbot/factory_bot_rails) methods. This gives us syntactic sugar of saying `create(:person)` instead of `FactoryBot.create(:person)`. See more in the [Testing]({{ site.github.url }}/ruby/testing) section.
+ * Removes some fixture-specific configuration that is now handled by
+ `database_cleaner`.
+
+* **app/controllers/docs_controller/rb**
+ * This is the controller that will generate our [Swagger](https://swagger.io) schema. It's using [Swagger Blocks](https://github.com/fotinakis/swagger-blocks) and [jsonapi_swagger_helpers](https://github.com/jsonapi-suite/jsonapi_swagger_helpers) under-the-hood. To learn more about Swagger documentation, see the the [Autodocumentation]({{ site.github.url }}/ruby/swagger) section.
+
+* **public/api**
+ * Adds a [Swagger UI]({{ site.github.url }}/ruby/swagger) to our project.
+
+* **config/initializers/jsonapi.rb**
+ * Requires the `ActiveRecord` adapter, which comes with the Suite. Comment this line if you'd like
+ to avoid `ActiveRecord`. Learn more in the [Adapters]({{ site.github.url }}/ruby/alternate-datastores/adapters) section.
+
+* **config/initializers/strong_resources.rb**
+ * Stores templates that whitelist API inputs. Learn more in the
+ [Strong Resources]({{ site.github.url }}/ruby/writes/strong-resources) section.
+
+* **Rakefile**
+ * Requires the swagger helpers library in order to run
+ [backwards-compatibility]({{ site.github.url }}/ruby/backwards-compatibility) checks against production.
+
+* **Gemfile**
+ * We've added some dependencies, most of which are discussed in other
+ sections:
+ * `jsonapi-rails`: used for serialization, this is the
+ rails-specific extension for [jsonapi-rb](http://jsonapi-rb.org)
+ * `jsonapi_swagger_helpers`: used automatically generating Swagger
+ documentation.
+ * `jsonapi_spec_helpers`: easily deal with complex JSONAPI responses
+ in tests.
+ * `kaminari`: Default pagination gem.
+ * `rspec-rails`: Testing framework.
+ * `factory_bot_rails`: For easily seeding data in tests.
+ * `faker`: for randomizing factory data.
+ * `swagger-diff`: for backwards-compatibility checks.
+ * `database_cleaner`: for cleaning the DB between tests.
diff --git a/ruby/reads/activerecord-associations.md b/ruby/reads/activerecord-associations.md
new file mode 100644
index 0000000..ac20647
--- /dev/null
+++ b/ruby/reads/activerecord-associations.md
@@ -0,0 +1,152 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### ActiveRecord Associations
+
+> [View the Sample App](https://github.com/jsonapi-suite/employee_directory/compare/step_9_associations...step_12_fsp_associations)
+
+> [Understanding Nested Queries]({{site.github.url}}/ruby/reads/nested}})
+
+JSONAPI Suite comes with an `ActiveRecord` adapter. Though other
+adapters can mimic this same interface, here's what you'll get
+out-of-the-box. The SQL here is roughly the same as using [#includes](http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations).
+
+> Note: make sure to whitelist associations in your [serializers]({{site.github.url}}/ruby/reads/serializers) or nothing will render!
+
+#### has_many
+
+{% highlight bash %}
+/posts?include=comments
+{% endhighlight %}
+
+{% highlight ruby %}
+# app/resources/post_resource.rb
+has_many :comments,
+ scope: -> { Comment.all },
+ resource: CommentResource,
+ foreign_key: :post_id
+{% endhighlight %}
+
+#### belongs_to
+
+{% highlight bash %}
+/comments?include=posts
+{% endhighlight %}
+
+{% highlight ruby %}
+# app/resources/comment_resource.rb
+belongs_to :post,
+ scope: -> { Post.all },
+ resource: PostResource,
+ foreign_key: :post_id
+{% endhighlight %}
+
+#### has_one
+
+{% highlight bash %}
+/posts?include=detail
+{% endhighlight %}
+
+{% highlight ruby %}
+# app/resources/post_resource.rb
+has_one :detail,
+ scope: -> { PostDetail.all },
+ resource: PostDetailResource,
+ foreign_key: :post_id
+{% endhighlight %}
+
+#### has_and_belongs_to_many
+
+{% highlight bash %}
+/posts?include=tags
+{% endhighlight %}
+
+{% highlight ruby %}
+# app/resources/post_resource.rb
+has_and_belongs_to_many :tags,
+ scope: -> { Tag.all },
+ resource: TagResource,
+ foreign_key: { taggings: :tag_id }
+{% endhighlight %}
+
+The only difference here is the foreign_key - we’re passing a hash instead of a symbol. `taggings` is our join table, and `tag_id` is the true foreign key.
+
+This will work, and for simple many-to-many relationships you can move on. But what if we want to add the property `primary`, a boolean, to the `taggings` table? Since we hid this relationship from the API, how will clients access it?
+
+As this is metadata about the relationship it should go on the meta section of the corresponding relationship object. While supporting such an approach is on the JSONAPI Suite roadmap, we haven't done so yet.
+
+For now, it might be best to simply expose the intermediate table to the API. Using a client like [JSORM]({{site.github.url}}/js/home), the overhead of this approach is minimal.
+
+#### polymorphic_belongs_to
+
+{% highlight ruby %}
+# app/models/employee.rb
+belongs_to :workspace, polymorphic: true
+{% endhighlight %}
+
+{% highlight ruby %}
+# app/models/workspace.rb
+has_many :employees, as: :workspace
+{% endhighlight %}
+
+{% highlight ruby %}
+# app/resources/employee_resource.rb
+polymorphic_belongs_to :workspace,
+ group_by: :workspace_type,
+ groups: {
+ 'Office' => {
+ scope: -> { Office.all },
+ resource: OfficeResource,
+ foreign_key: :workspace_id
+ },
+ 'HomeOffice' => {
+ scope: -> { HomeOffice.all },
+ resource: HomeOfficeResource,
+ foreign_key: :workspace_id
+ }
+ }
+{% endhighlight %}
+
+{% highlight bash %}
+/employees?include=workspace
+{% endhighlight %}
+
+Here an `Employee` belongs to a `Workspace`. `Workspace`s have
+different `type`s - `HomeOffice`, `Office`, `CoworkingSpace`, etc. The
+`employees` table has `workspace_id` and `workspace_type` columns
+to support this relationship.
+
+We may need to query each `workspace_type` differently - perhaps
+they live in separate tables (`home_offices`, `coworking_spaces`, etc). So, when fetching the relationship, we'll need to group our `Employees` by `workspace_type` and query differently for each group:
+
+{% highlight ruby %}
+# app/resources/employee_resource.rb
+polymorphic_belongs_to :workspace,
+ group_by: :workspace_type,
+ groups: {
+ 'Office' => {
+ scope: -> { Office.all },
+ resource: OfficeResource,
+ foreign_key: :workspace_id
+ },
+ 'HomeOffice' => {
+ scope: -> { HomeOffice.all },
+ resource: HomeOfficeResource,
+ foreign_key: :workspace_id
+ }
+ }
+{% endhighlight %}
+
+Let's say our API was returning 10 `Employees`, sideloading their corresponding `Workspace`. The underlying code would:
+
+* Fetch the employees
+* Group the employees by the given key: `employees.group_by { |e|
+ e.workspace_type }`
+* Use the `Office` configuration for all `Employee`s where
+ `workspace_type` is `Office`, and use the `HomeOffice` configuration
+for all `Employee`s where `workspace_type` is `HomeOffice`, and so
+forth.
diff --git a/ruby/reads/adapters.md b/ruby/reads/adapters.md
new file mode 100644
index 0000000..e495a89
--- /dev/null
+++ b/ruby/reads/adapters.md
@@ -0,0 +1,12 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Adapters
+
+> [View the Sample App](https://github.com/jsonapi-suite/employee_directory/compare/step_23_disassociation...elasticsearch_adapter)
+
+> [View the YARD Documentation](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Adapters/Abstract.html)
diff --git a/ruby/reads/basic-reads.md b/ruby/reads/basic-reads.md
new file mode 100644
index 0000000..55a745b
--- /dev/null
+++ b/ruby/reads/basic-reads.md
@@ -0,0 +1,145 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Basic Reads
+
+Here's the full code for a JSONAPI endpoint that supports sorting,
+pagination, sparse fieldsets and a [JSONAPI-compliant](http://jsonapi.org/format/#document-structure) response:
+
+{% highlight ruby %}
+# app/models/post.rb
+class Post < ApplicationRecord
+end
+{% endhighlight %}
+
+{% highlight ruby %}
+# config/routes.rb
+scope path: '/api' do
+ scope path: '/v1' do
+ resources :posts, only: [:index]
+ end
+end
+{% endhighlight %}
+
+{% highlight ruby %}
+# app/serializers/serializable_post.rb
+class SerializablePost < JSONAPI::Serializable::Resource
+ type :posts
+
+ attribute :title
+ attribute :description
+ attribute :body
+end
+{% endhighlight %}
+
+{% highlight ruby %}
+# app/resources/post_resource.rb
+class PostResource < ApplicationResource
+ type :posts
+ model Post
+end
+{% endhighlight %}
+
+{% highlight ruby %}
+# app/controllers/posts_controller.rb
+class PostsController < ApplicationController
+ jsonapi resource: PostResource
+
+ def index
+ posts = Post.all
+ render_jsonapi(posts)
+ end
+end
+{% endhighlight %}
+
+Let's walk through each of these files:
+
+* **app/models/post.rb**
+ * Our [Model](https://martinfowler.com/eaaCatalog/domainModel.html). As Martin Fowler puts it, "*An object model of the domain that incorporates both behavior and data.*". In this case we're using [ActiveRecord](http://guides.rubyonrails.org/active_record_basics.html), though other model patterns can be used. This is the **M** of [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller).
+
+* **config/routes.rb**
+ * Sets up the endpoint `/api/v1/posts`, per [Rails Routing](http://guides.rubyonrails.org/routing.html).
+
+* **app/serializers/serializable_post.rb**
+ * Given a `Model`, how do we want to represent that model as JSON? We
+ might want to avoid exposing certain attributes, normalize values,
+or compute something specific to the view. This is the **V** of [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller).
+ * We use the excellent [jsonapi-rb](http://jsonapi-rb.org) library for
+ serialization. If you're familiar with [active_model_serializers](https://github.com/rails-api/active_model_serializers), this code will look very familiar.
+
+* **app/resources/post_resource.rb**
+ * A `Resource` holds the logic for querying and persisting our
+ `Model`s based on the JSONAPI request. [Learn about Resources
+here]({{site.github.url}}/ruby/reads/resources).
+
+* **app/controllers/posts_controller.rb**
+ * This is a typical [Rails Controller](http://guides.rubyonrails.org/action_controller_overview.html), the **C** of [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller).
+ * We've added `jsonapi resource: PostResource` to tell our controller
+ to use query and persistence logic defined in our [Resource]({{site.github.url}}/ruby/resources).
+
+All of this leads up to the all-important `render_jsonapi` method.
+
+#### render_jsonapi
+
+> [View the YARD documentation](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Base.html#render_jsonapi-instance_method)
+
+This method does two things: builds and resolves the "base scope", and passes relevant options to the serialization layer.
+
+In other words, this lower-level code would be the equivalent:
+
+{% highlight ruby %}
+scope = jsonapi_scope(Post.all) # build up the scope
+posts = scope.resolve # fire the query
+render json: posts,
+ fields: params[:fields].split(','),
+ bunch: 'of',
+ other: 'options'
+{% endhighlight %}
+
+* We've started with a base scope - `Post.all` - and passed it into our
+ [Resource]({{site.github.url}}/ruby/resources), which will
+ modify the scope based on incoming parameters.
+* We've passed a number of boilerplate options to the underlying
+ [jsonapi-rb](http://jsonapi-rb.org) serialization library.
+
+There are times we want to manually build and resolve the scope prior to
+calling `render_jsonapi`. The `show` action is one example.
+
+#### The `#show` action
+
+
+Our [#show action](http://guides.rubyonrails.org/routing.html#crud-verbs-and-actions) fetches one specific post by ID, rather than a list of posts. To accomodate this, we manually build and resolve the scope instead of applying the default logic in `#render_jsonapi`:
+
+{% highlight ruby %}
+scope = jsonapi_scope(Post.where(id: params[:id]))
+post = scope.resolve.first
+render_jsonapi(post, scope: false)
+{% endhighlight %}
+
+Note the `scope: false` option - we've already resolved our models, so
+we tell `render_jsonapi` not to run the scoping logic again.
+
+> [View the YARD documentation for #jsonapi_scope](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Base.html#jsonapi_scope-instance_method)
+
+It's a common convention in Rails to return a `404` response code from
+the `show` action when a record is not found. Typically you'd raise and
+rescue `ActiveRecord::RecordNotFound`...but we want to be agnostic to
+the database. Instead:
+
+{% highlight ruby %}
+raise JsonapiCompliable::Errors::RecordNotFound unless post
+{% endhighlight %}
+
+{% highlight ruby %}
+# app/controllers/application_controller.rb
+rescue_exception JsonapiCompliable::Errors::RecordNotFound,
+ status: 404
+{% endhighlight %}
+
+We're throwing an exception, and using our [error handling
+library]({{site.github.url}}/ruby/error-handling) to
+customize the status code when that particular error is thrown.
diff --git a/ruby/reads/default-behavior.md b/ruby/reads/default-behavior.md
new file mode 100644
index 0000000..fda6568
--- /dev/null
+++ b/ruby/reads/default-behavior.md
@@ -0,0 +1,56 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Default Behavior
+
+You may need to change the default behavior or your API - perhaps you
+want a default of 10 per page instead of 20. JSONAPI Suite provides
+facilities that enable ***defaults*** that can be ***overridden*** - 10 per
+page, unless elsewise specified by the user.
+
+You can see these defaults in the [Resource documentation](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Resource.html):
+
+{% highlight ruby %}
+default_filter :active do |scope|
+ scope.where(active: true)
+end
+
+default_page_size(10)
+
+default_sort([{ created_at: :desc }])
+{% endhighlight %}
+
+These can all be overriden by the user. In other words, hitting
+`/posts` will only show active `Post`s, hitting
+`/posts?filter[active]=false` will show inactive `Post`s. The same applies
+for sorting and pagination.
+
+A common pattern is for default filters to apply for all users, but
+allow overrides for administrators. You can use the `:if` option to
+restrict the override:
+
+{% highlight ruby %}
+# app/resources/post_resource.rb
+allow_filter :active, if: :admin?
+
+# app/controllers/posts_controller.rb
+def admin?
+ current_user.admin?
+end
+{% endhighlight %}
+
+Now the default behavior is to view only active `Post`s, but
+*administrators* can override this default.
+
+You also have access to the context (in Rails, the controller) as the
+last argument to `default_filter`:
+
+{% highlight ruby %}
+default_filter :by_user do |scope, context|
+ scope.where(user_id: context.current_user.id)
+end
+{% endhighlight %}
diff --git a/ruby/reads/fieldsets.md b/ruby/reads/fieldsets.md
new file mode 100644
index 0000000..b213489
--- /dev/null
+++ b/ruby/reads/fieldsets.md
@@ -0,0 +1,75 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Fieldsets
+
+> [View the JSONAPI specification](http://jsonapi.org/format/#fetching-sparse-fieldsets)
+
+> [View the JS Documentation]({{ site.github.url }}/js/reads/fieldsets)
+
+#### Sparse Fieldsets
+
+You'll get this for free. Given a serializer:
+
+{% highlight ruby %}
+# app/serializers/serializable_post.rb
+class SerializablePost < JSONAPI::Serializable::Resource
+ type :posts
+
+ attribute :title
+ attribute :description
+ attribute :comment_count
+end
+{% endhighlight %}
+
+And the request:
+
+{% highlight bash %}
+/posts?fields[posts]=title,comment_count
+{% endhighlight %}
+
+The `description` field will not be returned in the response.
+
+#### Extra Fieldsets
+
+> [View the YARD documentation](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Scoping/ExtraFields.html)
+
+The opposite of a "sparse fieldset" is an "extra fieldset". Perhaps you
+have an attribute that is computationally expensive and should only be
+returned when explicitly requested. Perhaps the majority of your clients
+need the same fields (and can share the same cache) but one client needs
+extra data (and you'll accept the cache miss).
+
+To request an extra field, just specify it in your serializer:
+
+{% highlight ruby %}
+# app/serializers/serializable_employee.rb
+
+extra_attribute :net_worth do
+ 1_000_000
+end
+{% endhighlight %}
+
+In the URL, replace `fields` with `extra_fields`:
+
+{% highlight bash %}
+/posts?extra_fields[employees]=net_worth
+{% endhighlight %}
+
+The `net_worth` attribute will only be returned when explicitly
+requested.
+
+You may want to eager load some data only when a specific extra field is
+requested. There's a hook for this in your `Resource`;
+
+{% highlight ruby %}
+# app/resources/employee_resource.rb
+
+extra_field(employees: [:net_worth]) do |scope|
+ scope.includes(:assets)
+end
+{% endhighlight %}
diff --git a/ruby/reads/filtering.md b/ruby/reads/filtering.md
new file mode 100644
index 0000000..f9e281b
--- /dev/null
+++ b/ruby/reads/filtering.md
@@ -0,0 +1,151 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Filtering
+
+> [View the JSONAPI specification](http://jsonapi.org/format/#fetching-filtering)
+
+> [View the YARD Documentation](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Resource.html#allow_filter-class_method)
+
+> View the Sample App: [Server1](https://github.com/jsonapi-suite/employee_directory/commit/cf501dd95f8d4211973092a673a9a449bf467a46) \| [Server2](https://github.com/jsonapi-suite/employee_directory/compare/step_1_add_filter...step_2_add_custom_filter) \| [Client](https://github.com/jsonapi-suite/employee-directory-vue/compare/step_3_includes...step_4_filtering)
+
+> [View the JS Documentation]({{ site.github.url }}/js/reads/filtering)
+
+Filters are usually one-liners, with the logic delegated to an [Adapter]({{ site.github.url }}/ruby/alternate-datastores/adapters).
+
+{% highlight ruby %}
+allow_filter :title
+{% endhighlight %}
+
+You can view `allow_filter` like a whitelist. We wouldn't want to
+automatically support filters - otherwise sneaky users might filter our
+`Employee`s to only those making a certain salary. Hence the whitelist.
+
+To customize a filter:
+
+{% highlight ruby %}
+allow_filter :title do |scope, value|
+ scope.where(title: value)
+end
+{% endhighlight %}
+
+A real-life example might be a prefix query:
+
+{% highlight ruby %}
+allow_filter do |scope, value|
+ scope.where(["title LIKE ?", "#{value}%"])
+end
+{% endhighlight %}
+
+#### Filtering Relationships
+
+Prefix the filter parameter with the relevant [JSONAPI Type](http://jsonapi.org/format/#document-resource-identifier-objects) like so:
+
+{% highlight bash %}
+/blogs?include=posts&filter[posts][title]=foo
+{% endhighlight %}
+
+#### Default Filters
+
+> [View the YARD Documentation](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Scoping/DefaultFilter.html)
+
+You may want your scope to be filtered any time it is accessed - Perhaps
+you only want to show `active` posts by default:
+
+{% highlight ruby %}
+default_filter :active do |scope|
+ scope.where(active: true)
+end
+{% endhighlight %}
+
+Default filters can be overridden if there is a corresponding
+`allow_filter`. Given a `Resource`:
+
+{% highlight ruby %}
+allow_filter :active
+
+default_filter :active do |scope|
+ scope.where(active: true)
+end
+{% endhighlight %}
+
+And the following requests:
+
+{% highlight bash %}
+/posts
+/posts?filter[active]=false
+{% endhighlight %}
+
+The first will display only active posts, the second will display only
+inactive posts.
+
+#### Filter Conventions
+
+There are some common naming conventions for supporting more complex filters:
+
+{% highlight ruby %}
+# greater than
+allow_filter :id_gt
+
+# greater than or equal to
+allow_filter :id_gte
+
+# less than
+allow_filter :id_lt
+
+# less than or equal to
+allow_filter :id_lte
+
+# prefix queries
+allow_filter :title_prefix
+
+# OR queries
+allow_filter :active_or # true or false
+{% endhighlight %}
+
+> NOTE: **AND** queries are supported by default - just pass a
+> comma-delimited list of values.
+
+#### Filter Guards
+
+You can conditionally allow filters based on runtime context.
+Let's say only managers should be allowed to filter employees by salary:
+
+{% highlight ruby %}
+allow_filter :salary, if: :manager?
+
+def manager?
+ current_user.role == 'manager'
+end
+{% endhighlight %}
+
+#### Filter Aliases
+
+Aliases mostly come into play when supporting backwards
+compatibility. Let's say we originally called the filter `fname` then
+later wanted the more-expressive `first_name`. An alias allows is to
+keep a one-liner with the correct naming, while still responding correctly
+to `fname`:
+
+{% highlight ruby %}
+allow_filter :first_name, aliases: [:fname]
+{% endhighlight %}
+
+#### Accessing Runtime Context
+
+`allow_filter` can access runtime context (in Rails, the controller) as
+the last argument:
+
+{% highlight ruby %}
+allow_filter :my_siblings do |scope, value, context|
+ if value == true
+ scope.where(family_id: context.current_user.family_id)
+ else
+ scope
+ end
+end
+{% endhighlight %}
diff --git a/ruby/reads/nested.md b/ruby/reads/nested.md
new file mode 100644
index 0000000..85602d0
--- /dev/null
+++ b/ruby/reads/nested.md
@@ -0,0 +1,128 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Relationships and Nested Queries
+
+> [View the JSONAPI Specification](http://jsonapi.org/format/#fetching-includes)
+
+> [View the Sample App](https://github.com/jsonapi-suite/employee_directory/compare/step_9_associations...step_12_fsp_associations)
+
+> [View the JS Documentation]({{site.github.url}}/js/reads/nested-queries)
+
+Let's say we want to fetch a `Post` and all of its `Comment`s:
+
+{% highlight bash %}
+/posts?include=comments
+{% endhighlight %}
+
+Using the default `ActiveRecord` Adapter, we would add this code to our
+`PostResource`:
+
+{% highlight ruby %}
+# app/resources/post_resource.rb
+has_many :comments,
+ scope: -> { Comment.all },
+ resource: CommentResource,
+ foreign_key: :post_id
+{% endhighlight %}
+
+> Note: we'd have to whitelist `comments` in our [serializer]({{ site.github.url }}/ruby/reads/serializers) as well.
+
+To understand this code, we first have to realize that this is a Macro -
+code that is generating lower-level code for the purposes of removing
+boilerplate. Let's understand the lower-level DSL before breaking
+down the macro.
+
+{% highlight ruby %}
+allow_sideload :comments, resource: CommentResource do
+ scope do |posts|
+ # ... code ...
+ end
+
+ assign do |posts, comments|
+ # ... code ...
+ end
+end
+{% endhighlight %}
+
+This is the lower-level `allow_sideload` DSL. There are four things
+going on. To begin with:
+
+* We've whitelisted `comments`. Without this, the request would raise
+ the error `JsonapiCompliable::Errors::InvalidInclude`. This ensures
+ clients can't arbitrarily pull back data that could introduce performance
+ problems or security risks.
+* We've said, "when retrieving comments, re-use the logic defined in
+ `CommentResource`". This way all the filter, sorting, etc query logic
+at the `/comments` endpoint can be reused when sideloading comments from
+the `/posts?include=comments` endpoint.
+
+That brings us to the `scope` and `assign` hooks. When querying a
+relationship, we need to answer two questions:
+
+* Given a list of parents (`post`s), how should we scope the request for
+children (`comment`s)? This is the `scope` block. In a relational
+database, we'd usually scope based on foreign and primary keys.
+* Given a list of parents (`post`s) and a list of children (`comment`s),
+how do you want to assign these objects together? This is the `assign`
+block. In a relational database, we'd usually compare foreign and
+primary keys.
+
+In other words, the code would look similar to this for `ActiveRecord`:
+
+{% highlight ruby %}
+scope do |posts|
+ Comment.where(post_id: posts.map(&:id))
+end
+
+assign do |posts, comments|
+ posts.each do |post|
+ post.comments = comments.select { |c| c.post_id == post.id }
+ end
+end
+{% endhighlight %}
+
+Note that `scope` hasn't actually fired a query - we take the result of
+this block and pass it to `CommentResource` so that further query logic
+(filtering, sorting, etc) can be applied and re-used across endpoints.
+
+Of course, the code above would be very tedious to write by hand every
+time. That's why we have Macros like `has_many`, `belongs_to` etc -
+configure only the parts you need, and avoid the boilerplate:
+
+{% highlight ruby %}
+# app/resources/post_resource.rb
+has_many :comments,
+ scope: -> { Comment.all },
+ resource: CommentResource,
+ foreign_key: :post_id
+ # primary_key defaults to 'id'
+{% endhighlight %}
+
+Given the above options, we can auto-generate `allow_sideload` code. You
+can always write `allow_sideload` directly if you have highly customized
+logic. You can also pass a block to the macros to customize:
+
+{% highlight ruby %}
+# app/resources/post_resource.rb
+has_many :comments,
+ scope: -> { Comment.all },
+ resource: CommentResource,
+ foreign_key: :post_id do
+ assign do |posts, comments|
+ # some custom code to associate these objects
+ Post.associate(posts, comments)
+ end
+ end
+{% endhighlight %}
+
+Again, nested queries come for free. This code allows for nested queries
+like "give me the post, and its `active` comments":
+
+{% highlight bash %}
+/posts/1?include=comments&filter[comments][active]=true
+{% endhighlight %}
diff --git a/ruby/reads/pagination.md b/ruby/reads/pagination.md
new file mode 100644
index 0000000..9b1bf5b
--- /dev/null
+++ b/ruby/reads/pagination.md
@@ -0,0 +1,34 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Pagination
+
+> [View the JSONAPI specification](http://jsonapi.org/format/#fetching-pagination)
+
+> [View the YARD Documentation](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Resource.html#paginate-class_method)
+
+> View the Sample App: [Server1](https://github.com/jsonapi-suite/employee_directory/compare/step_4_custom_sorting...step_5_pagination) \| [Server2](https://github.com/jsonapi-suite/employee_directory/compare/step_5_pagination...step_6_custom_pagination) \| [Client](https://github.com/jsonapi-suite/employee-directory-vue/compare/step_6_stats...step_7_pagination)
+
+> [View the JS Documentation]({{ site.github.url }}/js/reads/pagination)
+
+Pagination usually happens with no developer intervention, instead handled
+automatically by an [Adapter]({{ site.github.url }}/ruby/alternate-datastores/adapters). To
+customize:
+
+{% highlight ruby %}
+paginate do |scope, current_page, per_page|
+ scope.page(current_page).per(per_page)
+end
+{% endhighlight %}
+
+A real-life example might be replacing the default [Kaminari](https://github.com/kaminari/kaminari) gem with [will_paginate](https://github.com/mislav/will_paginate):
+
+{% highlight ruby %}
+paginate do |scope, current_page, per_page|
+ scope.paginate(page: current_page, per_page: per_page)
+end
+{% endhighlight %}
diff --git a/ruby/reads/serializers.md b/ruby/reads/serializers.md
new file mode 100644
index 0000000..6174732
--- /dev/null
+++ b/ruby/reads/serializers.md
@@ -0,0 +1,110 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Serializers
+
+> [View the Sample App](https://github.com/jsonapi-suite/employee_directory/compare/step_24_autodocumentation...custom-serialization)
+
+> [View additional documentation at jsonapi-rb.org](http://jsonapi-rb.org)
+
+
+
+We use [jsonapi-rb](http://jsonapi-rb.org) for serialization. If you've used [active_model_serializers](https://github.com/rails-api/active_model_serializers) before, it will look incredibly familiar:
+
+{% highlight ruby %}
+# app/serializers/serializable_post.rb
+class SerializablePost < JSONAPI::Serializable::Resource
+ type :posts
+
+ attribute :title
+ attribute :description
+ attribute :body
+end
+{% endhighlight %}
+
+Would render the [JSONAPI Document](http://jsonapi.org/format/#document-structure):
+
+{% highlight ruby %}
+{
+ data: {
+ type: "posts",
+ id: "123",
+ attributes: {
+ title: "My Post",
+ description: "Some description",
+ body: "Blah blah blah"
+ }
+ }
+}
+{% endhighlight %}
+
+#### Associations
+
+To add an association:
+
+{% highlight ruby %}
+has_many :comments
+{% endhighlight %}
+
+Assuming there is a corresponding `SerializableComment`, you'd see:
+
+{% highlight ruby %}
+{
+ data: {
+ type: "posts",
+ id: "123",
+ attributes: { ... },
+ relationships: {
+ comments: {
+ data: [
+ { id: "1", type: "comments" }
+ ]
+ }
+ }
+ },
+ included: [
+ {
+ type: "comments",
+ id: "1"
+ }
+ ]
+}
+{% endhighlight %}
+
+> Note: Your `Resource` must [whitelist this sideload]({{site.github.url}}/ruby/reads/nested) as well.
+
+#### Customizing Serializers
+
+Occasionally you may need to normalize, format, or elsewise transform
+your `Model` into an effective JSON representation. To do this, pass a
+block to `attribute` and reference the underlying `@object` being
+serialized:
+
+{% highlight ruby %}
+attribute :title do
+ @object.title.upcase
+end
+{% endhighlight %}
+
+> Why not methods like AMS? To avoid collissions with native ruby methods like `tap`.
+
+Keep in mind all serializers have access to `@context` - the calling
+controller in Rails.
+
+#### Conditional Fields
+
+You may want to render a field based on runtime context - for instance,
+only show the `salary` field if the user is a manager. Keeping in mind
+that `@context` will always be available as the calling controller:
+
+{% highlight ruby %}
+attribute :salary, if: -> { @context.current_user.manager? }
+{% endhighlight %}
+
+> [View additional documentation at jsonapi-rb.org](http://jsonapi-rb.org)
+
+> [Visit the jsonapi-rb Gitter chatroom](https://gitter.im/jsonapi-rb)
diff --git a/ruby/reads/sorting.md b/ruby/reads/sorting.md
new file mode 100644
index 0000000..1ce259e
--- /dev/null
+++ b/ruby/reads/sorting.md
@@ -0,0 +1,50 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Sorting
+
+> [View the JSONAPI specification](http://jsonapi.org/format/#fetching-sorting)
+
+> [View the YARD Documentation](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Resource.html#sort-class_method)
+
+> View the Sample App: [Server1](https://github.com/jsonapi-suite/employee_directory/compare/step_2_add_custom_filter...step_3_basic_sorting) \| [Server2](https://github.com/jsonapi-suite/employee_directory/compare/step_3_basic_sorting...step_4_custom_sorting) \| [Client](https://github.com/jsonapi-suite/employee-directory-vue/compare/step_4_filtering...step_5_sorting)
+
+> [View the JS Documentation]({{ site.github.url }}/js/reads/sorting)
+
+Sorting usually happens with no developer intervention, instead handled
+automatically by an [Adapter]({{ site.github.url }}/ruby/alternate-datastores/adapters). To
+customize:
+
+{% highlight ruby %}
+sort do |scope, attribute, direction|
+ scope.order(attribute => direction)
+end
+{% endhighlight %}
+
+A real-life example might be sorting on a different table. Let's say an
+Employee has many Positions, and `title` lives in the `positions` table:
+
+{% highlight ruby %}
+sort do |scope, attribute, direction|
+ if attribute == :title
+ scope.joins(:positions).order("positions.title #{direction}")
+ else
+ scope.order(attribute => direction)
+ end
+end
+{% endhighlight %}
+
+> Note: the same `sort` proc will fire for every sort parameter supplied
+> in the request. In other words, yes - you can multisort!
+
+#### Sorting Relationships
+
+Prefix the sort parameter with the relevant [JSONAPI Type](http://jsonapi.org/format/#document-resource-identifier-objects) like so:
+
+{% highlight bash %}
+/blogs?include=posts&sort=posts.title
+{% endhighlight %}
diff --git a/ruby/reads/statistics.md b/ruby/reads/statistics.md
new file mode 100644
index 0000000..5671aa1
--- /dev/null
+++ b/ruby/reads/statistics.md
@@ -0,0 +1,64 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Statistics
+
+> [View the JS Documentation]({{ site.github.url }}/js/reads/statistics)
+
+> [View the YARD Documentation](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Resource.html#allow_stat-class_method)
+
+> View the Sample App: [Server1](https://github.com/jsonapi-suite/employee_directory/compare/step_6_custom_pagination...step_7_stats) \| [Server2](https://github.com/jsonapi-suite/employee_directory/compare/step_7_stats...step_8_custom_stats) \| [Client](https://github.com/jsonapi-suite/employee-directory-vue/compare/step_5_sorting...step_6_stats)
+
+Statistics are useful and common. Consider a datagrid listing posts - we
+might want a "Total Posts" count displayed above the grid without firing
+an additional request. Notably, that statistic **should** take into
+account filtering, but **should not** take into account pagination.
+
+You can whitelist stats in your `Resource`:
+
+{% highlight ruby %}
+allow_stat total: [:count]
+{% endhighlight %}
+
+And request them like so:
+
+{% highlight bash %}
+/posts?stats[total]=count
+{% endhighlight %}
+
+They will be returned in the [meta](http://jsonapi.org/format/#document-meta) section of the response:
+
+{% highlight ruby %}
+{
+ # ...
+ meta: {
+ stats: {
+ total: {
+ count: 100
+ }
+ }
+ }
+}
+{% endhighlight %}
+
+You can run stats over specific attributes rather than `total`:
+
+{% highlight ruby %}
+allow_stat rating: [:average]
+{% endhighlight %}
+
+Adapters support the following statistics out-of-the-box: `count`,
+`average`, `sum`, `maximum`, and `minimum`. You can also define custom
+statistics:
+
+{% highlight ruby %}
+allow_stat rating: [:average] do
+ standard_deviation do |scope, attr|
+ # your standard deviation code here
+ end
+end
+{% endhighlight %}
diff --git a/ruby/resources.md b/ruby/resources.md
new file mode 100644
index 0000000..a2fd56a
--- /dev/null
+++ b/ruby/resources.md
@@ -0,0 +1,343 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Resources
+
+> "A `Model` is to the `Database` what a `Resource` is to the `API`"
+
+Resources might look magical at first, but they are actually just a
+simple collection of a few common hooks.
+
+Consider a traditional Rails controller:
+
+{% highlight ruby %}
+def index
+ posts = Post.all
+ render json: posts
+end
+{% endhighlight %}
+
+Imagine if we had to implement the [JSONAPI specification](http://jsonapi.org) by hand, ensuring our endpoints supported sorting, pagination, filtering, etc. You'd start seeing something along these lines:
+
+{% highlight ruby %}
+# No query has fired yet, this is a blank ActiveRecord scope
+posts = Post.all
+
+if title = params[:filter].try(:title)
+ # Alter the scope if we're filtering
+ posts = posts.where(title: title)
+end
+
+# ... etc ...
+
+if sort = params[:sort]
+ # Alter the scope if we're sorting
+ sort_dir = :asc
+ if sort.starts_with?('-')
+ sort_dir = :desc
+ end
+ sort_att = sort.split('-')[1]
+ posts = posts.order(sort_att => sort_dir)
+end
+
+# ... etc ...
+
+render json: posts.to_a # Finally!
+{% endhighlight %}
+
+In other words...it'd be a gross mess, especially when dealing with
+[inclusion of related
+resources](http://jsonapi.org/format/#fetching-includes) or swapping
+datastores. But the basic pattern - starting with a scope and then
+decorating it based on incoming parameters - is incredibly powerful.
+
+Instead of writing this code by hand every time, let's move the
+boilerplate into a library and leave developers with only the part they
+care about - **how to modify the scope**:
+
+{% highlight ruby %}
+allow_filter :title do |scope, value|
+ scope.where(title: value)
+end
+
+sort do |att, dir|
+ scope.order(att => dir)
+end
+{% endhighlight %}
+
+This code lives in a `Resource`. All we're doing here is specifying [Procs](http://ruby-doc.org/core-2.1.1/Proc.html) that modify the scope, leaving boilerplate to the underlying `jsonapi_suite` library.
+
+Of course, with `ActiveRecord`, you'd see the same logic repeated here over
+and over again. Let's supply defaults to DRY up this code and end
+with:
+
+{% highlight ruby %}
+# Whitelist the filter
+allow_filter :title
+{% endhighlight %}
+
+...but allow developers to override those defaults whenever they'd like:
+
+{% highlight ruby %}
+allow_filter :title do |scope, value|
+ scope.where(["title LIKE ?", "#{value}%"])
+end
+
+sort do |attribute, direction|
+ # ... your custom sort logic ...
+end
+{% endhighlight %}
+
+The important thing is: **you still have full control of the query**.
+This is why JSONAPI Suite can easily work with any datastore, from SQL
+to MongoDB to HTTP requests. The "behind-the-scenes defaults" are stored
+in an [Adapter]({{ site.github.url }}/ruby/alternate-datastores/adapters).
+Supply blocks for one-off customizations, or package them up into an
+`Adapter` once those customizations become commonplace.
+
+By default, JSONAPI Suite comes with an `ActiveRecordAdapter`.
+
+#### Scopes - a Generic Query-Building Pattern
+
+If you look closely at the above examples, you can see our code breaks
+down into three key parts:
+
+ * **Step 1**: Start with a "base scope" - a default query object.
+ * **Step 2**: Modify that scope based on incoming parameters.
+ * **Step 3**: Actually fire the query.
+
+This pattern applies to any ORM or datastore. Let's try it with an HTTP
+client that accepts a hash of options. A generic Rails controller might
+look something like:
+
+{% highlight ruby %}
+def index
+ # Step 1: Our "base scope"
+ scope = {}
+
+ # Step 2: Modify that scope based on the request
+ if title = params[:filter].try(:[], :title)
+ scope[:title] = title
+ end
+
+ # Step 3: actually fire the request + build some models
+ # Post here is a PORO (plain old ruby object)
+ hashes = HTTP.get('/posts', scope)
+ posts = hashes.map { |attr| Post.new(attrs) }
+
+ # render
+ render json: posts
+end
+{% endhighlight %}
+
+So our JSONAPI Suite equivalent would be:
+
+{% highlight ruby %}
+# Step 1: Define the base scope in the controller
+def index
+ base_scope = {}
+ # Pass the base scope to the resource, which will
+ # build + fire the query.
+ #
+ # Then, render the results.
+ render_jsonapi(base_scope)
+end
+{% endhighlight %}
+
+{% highlight ruby %}
+# app/resources/post_resource.rb
+#
+# Step 2: Modify the scope in the Resource
+allow_filter :title do |scope, value|
+ scope[:title] = value
+end
+
+# Step 3: Actually fire the query
+# This method must return an array of Model instances
+def resolve(scope)
+ hashes = HTTP.get('/posts', scope)
+ hashes.map { |attr| Post.new(attrs) }
+end
+{% endhighlight %}
+
+Again, you can package this logic into an [Adapter]({{ site.github.url }}/ruby/alternate-datastores/adapters) if you found yourself repeating the same logic
+over and over. Adapters DRY-up Resources.
+
+This pattern applies to sorting, pagination, statistics and such as
+well - view the [Reads]({{ site.github.url }}/ruby/reads/basic-reads) documentation
+for more.
+
+#### Associations
+
+In the prior section, we noted the 3 key parts of query building. For
+associations, we need to answer 2 key questions:
+
+ * **Question 1**: Given an array of parents, what should the "base scope" be
+ in order to query only relevant children?
+ * **Question 2**: Once we've resolved both the parents and the children, how do
+ we associate these objects together?
+
+Let's switch back to vanilla `ActiveRecord` for a second. We've resolved
+the `Post`s and need to fetch the `Comment`s. Here's how we'd answer
+these questions:
+
+{% highlight ruby %}
+allow_sideload :comments, resource: CommentResource do
+ # Question 1: What's a "base scope" that will return only
+ # relevant comments?
+ scope do |posts|
+ Comment.where(post_id: posts.map(&:id))
+ end
+
+ # Question 2: How do we assign these objects together?
+ assign do |posts, comments|
+ posts.each do |post|
+ post.comments = comments.select { |c| c.post_id == post.id }
+ end
+ end
+end
+{% endhighlight %}
+
+Just like in our prior sections, we can see the same logic would repeat
+over and over again each time we added a new relationship...with some slight tweaks based on
+`has_many/belongs_to`, non-standard foreign keys and such. So our
+default `ActiveRecord` adapter comes with **macros** that generate this
+lower-level code for us:
+
+{% highlight ruby %}
+has_many :comments,
+ resource: CommentResource,
+ scope: -> { Comment.all },
+ foreign_key: :post_id
+{% endhighlight %}
+
+You can dig deeper into the various [ActiveRecord Association Macros here]({{ site.github.url }}/ruby/reads/activerecord-associations).
+
+Let's go back to HTTP calls. Imagine the `CommentResource` worked just
+like our HTTP-based `PostResource` from the prior section. Let's see how
+those same questions would be answered:
+
+{% highlight ruby %}
+# Step 1: What's a base scope that will return only
+# relevant comments?
+#
+# In the case of our HTTP client, the "base scope" is
+# nothing more than a ruby hash.
+#
+# Our final query would end up something like:
+#
+# HTTP.get('/comments', { post_id: [1,2,3] })
+scope do |posts|
+ { post_id: posts.map(&:id) }
+end
+
+# Step 2: How do we assign these objects together?
+# This code is unchanged from the prior example
+assign do |posts, comments|
+ posts.each do |post|
+ post.comments = comments.select { |c| c.post_id == post.id }
+ end
+end
+{% endhighlight %}
+
+The key lessons here:
+
+ * `scope` must return a "base scope" that can be further modified.
+ This way we can apply additional "deep query" logic - maybe we
+ want to sort these comments - and re-use the query-building code
+ defined in `CommentResource`. This allows the same logic at the
+ `/comments` endpoint to apply to the `/posts?include=comments`
+ endpoint.
+ * If you're not sure what the scope should be, look into the relevant
+ `Resource`, particularly the [#resolve](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Resource.html#resolve-instance_method) method,
+ to see how the query will actually be executed. If there is no
+ #resolve method, it's using the default of the relevant `Adapter`.
+ * Typically, you'd define an `ApplicationRecord` who specifies the
+ `Adapter`. You'll see this pattern if you use our generators.
+ * [Adapters]({{ site.github.url }}/ruby/alternate-datastores/adapters) can
+ DRY-up this logic with `has_many`-style macros.
+
+#### Writes
+
+In the prior sections, we removed boilerplate and dropped down to only
+the important code of scope modification. The same basic premise applies to write operations as well. Rather than
+dealing with parsing the incoming payload and associating the graph of
+objects, Suite supplies hooks for just the parts you care about:
+**actually persisting objects**.
+
+{% highlight ruby %}
+def create(attributes)
+ post = Post.new(attributes)
+ post.save
+ post
+end
+
+def update(attributes)
+ post = Post.find(attributes.delete(:id))
+ post.update_attributes(attributes)
+ post
+end
+
+# ... etc ...
+{% endhighlight %}
+
+Just like reads, this logic is usually extracted into an `Adapter`, but
+you can always use `super` to override, handle side effects, etc.
+
+{% highlight ruby %}
+def create(attributes)
+ model = super
+ Rails.logger.info "#{model.class} created with id #{model.id}!"
+ model
+end
+{% endhighlight %}
+
+See the [Writes]({{ site.github.url }}/ruby/writes/basic-writes) section for
+more.
+
+#### Generators
+
+If you're unsure of how a "default project" should look, use the
+`bin/rails g jsonapi:resource` generator:
+
+```ruby
+bin/rails g jsonapi:resource Post title:string
+```
+
+This generator can also limit the controller actions:
+
+```ruby
+bin/rails g jsonapi:resource Post title:string -a index
+```
+
+It's **highly encouraged** you run these generators at least once, as
+you'll get a bunch of helpful comments and understand baseline
+scenarios. You're getting:
+
+ * A controller (e.g. `PostsController`)
+ * A route (`//v1/posts`)
+ * A `Resource` (`PostResource`)
+ * Integration spec boilerplate
+ * including Factories
+ * ...and `Payload`s
+ * A whitelist of incoming parameters for writes
+ (`config/initializer/strong_resource.rb`)
+
+Type `bin/rails g jsonapi:resource --help for details`.
+
+#### Wrapping Up
+
+There's more to learn about various ways `Resource`s can be customized,
+but that's the basic premise: no magic, just removal of boilerplate.
+
+> Note: the same `Resource` logic can be re-used across endpoints, to
+> support logic like "fetch this `Post` and its `Comment`s that are
+> `active`". Whether you're sideloading comments from the `/posts`
+> endpoint or accessing the `/comments` endpoint directly, the same
+> `Resource` logic applies.
+
+> See the [Resource](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Resource.html) documentation for more.
diff --git a/ruby/scoping-to-current-user.md b/ruby/scoping-to-current-user.md
new file mode 100644
index 0000000..d0bfa95
--- /dev/null
+++ b/ruby/scoping-to-current-user.md
@@ -0,0 +1,104 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Scoping to Current User
+
+> Given a `Post` model with `hidden` attribute, only allow administrators
+to view hidden `Posts`.
+
+Let's start by adding a `visible` scope to `Post`, so we can easily
+retrive only `Post`s where `hidden` is `false`:
+
+{% highlight ruby %}
+# app/models/post.rb
+scope :visible, -> { where(hidden: false) }
+{% endhighlight %}
+
+As you know, we would typically use the base scope `Post.all` like so:
+
+{% highlight ruby %}
+def index
+ render_jsonapi(Post.all)
+end
+{% endhighlight %}
+
+Let's instead use the base scope `Post.visible` when the user is not an
+administrator:
+
+{% highlight ruby %}
+def index
+ scope = current_user.admin? ? Post.all : Post.visible
+ render_jsonapi(scope)
+end
+{% endhighlight %}
+
+That's it! Now only administrators can view hidden `Post`s.
+
+Of course, this logic would only apply to the `/posts` endpoint and
+would not apply when we are sideloading from `/blogs?include=posts`. To
+ensure this logic runs *all the time*, add a default filter:
+
+{% highlight ruby %}
+default_filter :visible do |scope, context|
+ context.current_user.admin? ? scope : scope.visible
+end
+{% endhighlight %}
+
+### Privileged Writes
+
+> Given `Post`s that have an `internal` attribute, only allow
+internal users to publish internal posts.
+
+Our controller context is available in our resource. Let's override
+`Resource#create` to ensure correct privileging:
+
+{% highlight ruby %}
+def create(attributes)
+ if !internal_user? && attributes[:internal] == true
+ raise "Hey you! YOU can't publish internal posts!"
+ else
+ super
+ end
+end
+
+private
+
+def internal_user?
+ context.current_user.internal?
+end
+{% endhighlight %}
+
+### Guarding Filters
+
+> Given `Employee`s with attribute `under_performance_review`, do not allow clients to find all employees under performance review.
+
+Occasionally you need to guard filters based on the current user. Use
+the `:if` option on `allow_filter`. This will execute in the context of
+your controller:
+
+{% highlight ruby %}
+# app/resources/employee_resource.rb
+allow_filter :under_performance_review, if: :admin?
+{% endhighlight %}
+
+{% highlight ruby %}
+# app/controllers/employees_controller.rb
+class EmployeesController < ApplicationController
+ jsonapi resource: EmployeeResource
+
+ def index
+ render_jsonapi(Employee.all)
+ end
+
+ private
+
+ def admin?
+ current_user.admin?
+ end
+end
+{% endhighlight %}
+
diff --git a/ruby/swagger.md b/ruby/swagger.md
new file mode 100644
index 0000000..de28bb4
--- /dev/null
+++ b/ruby/swagger.md
@@ -0,0 +1,148 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Autodocumenting with Swagger
+
+> [View the Sample App](https://github.com/jsonapi-suite/employee_directory/compare/step_23_disassociation...step_24_autodocumentation)
+
+This suite uses DSLs to specify inputs (`strong_resources`, filters, etc), and outputs (`jsonapi-rb` serializers).
+We can introspect that DSL to provide automatic documentation. Not only
+does this save a lot of time, it ensures your code and documentation are
+never out of sync.
+
+Here we'll be using [swagger](https://swagger.io), a popular open-source
+documentation framework.
+
+
+
+
+
+
+To get this UI, we need to install two things: a controller that
+generates a schema (`swagger.json`), and a static website
+in `public`. Our [generator]({{site.github.url}}/ruby/installation) installs these dependencies:
+
+
+
+{% highlight ruby %}
+# Gemfile
+# Below 'jsonapi_suite'
+gem 'jsonapi_spec_helpers'
+gem 'jsonapi_swagger_helpers'
+{% endhighlight %}
+
+
+ Note: here we're moving `jsonapi_spec_helpers` out of the
+ test-specific bundle group. Introspecting spec helpers is part of
+ autodocumenting, so we'll `require` them manually when our documentation
+ controller is loaded.
+
+
+The generator also installs a Swagger UI in your Rails app's `public`
+directory. If you haven't already done so:
+
+{% highlight bash %}
+$ mkdir -p public/api/docs && cd public/api/docs
+$ git clone https://github.com/jsonapi-suite/swagger-ui.git && cp swagger-ui/prod-dist/* . && rm -rf swagger-ui
+{% endhighlight %}
+
+Our documentation will be accessible at `/api/docs`, so we put the files
+in `public/api/docs`. You may want a different directory depending on
+your own routing rules. In either case, our next step is to edit
+`index.html`: make sure any javascript and css has the correct URL.
+There are also a few configuration options, such as providing a link to
+Github.
+
+{% highlight javascript %}
+window.CONFIG = {
+ githubURL: "http://github.com/user/repo",
+ basePath: "/api" // basePath/swagger.json, basePath/v1/employees, etc
+}
+{% endhighlight %}
+
+This static website will make a request to `/api/swagger.json`. Again,
+if not using the generator you'll have to add that endpoint:
+
+{% highlight bash %}
+$ touch app/controllers/docs_controller.rb
+{% endhighlight %}
+
+{% highlight ruby %}
+# config/routes.rb
+scope path: '/api' do
+ resources :docs, only: [:index], path: '/swagger'
+ # ... code ...
+end
+{% endhighlight %}
+
+Our `DocsController` uses [swagger-blocks](https://github.com/fotinakis/swagger-blocks) to generate
+the swagger schema. Here's the minimal setup needed to configure
+swagger:
+
+{% highlight ruby %}
+require 'jsonapi_swagger_helpers'
+
+class DocsController < ActionController::API
+ include JsonapiSwaggerHelpers::DocsControllerMixin
+
+ swagger_root do
+ key :swagger, '2.0'
+ info do
+ key :version, '1.0.0'
+ key :title, ''
+ key :description, ''
+ contact do
+ key :name, ''
+ end
+ end
+ key :basePath, '/api'
+ key :consumes, ['application/json']
+ key :produces, ['application/json']
+ end
+end
+{% endhighlight %}
+
+That's it. Now, every time we add an endpoint, we can autodocument with
+one line of code (below the `swagger_root` block):
+
+{% highlight ruby %}
+jsonapi_resource '/v1/employees'
+{% endhighlight %}
+
+This endpoint will be introspected for all RESTful actions, outputting
+the full configuration. There are a few customization options:
+
+{% highlight ruby %}
+jsonapi_resource '/v1/employees',
+ only: [:create, :index],
+ except: [:destroy],
+ descriptions: {
+ index: "Some additional documentation"
+ }
+{% endhighlight %}
+
+If you want additional attribute-level documentation, you can add this
+to your spec payloads:
+
+{% highlight ruby %}
+key(:name, String, description: 'The full name, e.g. "John Doe"')
+{% endhighlight %}
+
+
+Will give you an output similar to:
+
+
+
+
+
+
+
+### Authentication
+
+Your site may require authentication - for instance, sending a
+`Authorization` header in every request. We suggest using something like
+[Request.ly](https://chrome.google.com/webstore/detail/requestly-redirect-url-mo/mdnleldcmiljblolnjhpnblkcekpdkpa?hl=en) to modify headers for every request to a given URL.
diff --git a/ruby/testing.md b/ruby/testing.md
new file mode 100644
index 0000000..83ab504
--- /dev/null
+++ b/ruby/testing.md
@@ -0,0 +1,116 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Writing Integration Tests
+
+Validating verbose JSON API responses in tests can be a pain. We could
+use something like [json_matchers](https://github.com/thoughtbot/json_matchers) to validate a schema, but we hope to do one better - let's validate full payloads with a few simple helpers, using full-stack [rspec request specs](https://github.com/rspec/rspec-rails#request-specs).
+
+Let's say we're testing the `show` action of our employees controller, sideloading the employee's department. Follow the [Quickstart](/quickstart) to make sure your `rails_helper.rb` is setup correctly first.
+
+Let's say we're testing the `show` action of our employees controller, sideloading the employee's department.
+
+Let's begin with vanilla RSpec of what the test might look like:
+
+{% highlight ruby %}
+require 'rails_helper'
+
+RSpec.describe 'employees#show', type: :request do
+ let!(:homer) { Employee.create!(name: 'Homer Simpson') }
+ let!(:safety) { employee.create_department!(name: 'Safety') }
+
+ it 'renders an employee, sideloading department' do
+ get "/api/employees/#{homer.id}", params: {
+ include: 'department'
+ }
+ # ... code asserting json response ...
+ end
+end
+{% endhighlight %}
+
+To avoid painful json assertions, let's use [jsonapi_spec_helpers](https://github.com/jsonapi-suite/jsonapi_spec_helpers). Start by adding some setup code:
+
+{% highlight ruby %}
+# spec/rails_helper.rb
+require 'jsonapi_spec_helpers'
+
+RSpec.configure do |config|
+ config.include JsonapiSpecHelpers
+end
+{% endhighlight %}
+
+And now to validate the response, we'll call `assert_payload`:
+
+{% highlight ruby %}
+assert_payload(:employee, homer, json_item)
+assert_payload(:department, safety, json_include('departments'))
+{% endhighlight %}
+
+`assert_payload` takes three arguments:
+* The name of a payload we've defined (we haven't done this yet).
+* The record we want to compare against
+* The relevant slice of json. `json_item` and `json_includes` are
+ helpful methods to target the right slice. You can see all helpers in
+the documentation for `jsonapi_spec_helpers`.
+
+OK, so we want to take a record, response JSON, and compare them against
+something pre-defined. Let's write those definitions; they look very similar to
+something you'd write for [factory_girl](https://github.com/thoughtbot/factory_girl):
+
+{% highlight ruby %}
+# spec/payloads/employee.rb
+JsonapiSpecHelpers::Payload.register(:employee) do
+ key(:name)
+ key(:email)
+
+ timestamps!
+end
+
+# spec/payloads/department.rb
+JsonapiSpecHelpers::Payload.register(:department) do
+ key(:name)
+end
+{% endhighlight %}
+
+`assert_payload` will do four things:
+
+* Ensure keys that are not in the payload definition are **not** present.
+* Ensure all keys in the registered payload **are** present.
+* Ensures no value in a key/value pair is `nil` (this is overrideable).
+* Ensures each key matches the expected record value. In other words,
+ we're doing something like `expect(json['email']).to eq(homer.email)`.
+
+The comparison value can be customized. Let's say we serialize the
+`name` attribute as a combination of the employee's `first_name` and
+`last_name`:
+
+{% highlight ruby %}
+key(:name) { |record| "#{record.first_name} #{record.last_name}" }
+{% endhighlight %}
+
+Optionally, validate against a type as well. If both the expected and
+actual values match, but are the incorrect type, the test will fail:
+
+{% highlight ruby %}
+key(:salary, Integer)
+{% endhighlight %}
+
+You can also customize/override payloads at runtime in your test. Let's
+say we only serialize `salary` when the current user is an admin. Your
+test could look something like:
+
+{% highlight ruby %}
+sign_in(:user)
+assert_payload(:employee, homer, json_item)
+sign_in(:admin)
+assert_payload(:employee, homer, json_item) do
+ key(:salary)
+end
+{% endhighlight %}
+
+For documentation on all the spec helpers we provide, check out the
+[jsonapi_spec_helpers](https://github.com/jsonapi-suite/jsonapi_spec_helpers) gem.
diff --git a/ruby/writes/basic-writes.md b/ruby/writes/basic-writes.md
new file mode 100644
index 0000000..bb68de0
--- /dev/null
+++ b/ruby/writes/basic-writes.md
@@ -0,0 +1,143 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Basic Writes
+
+Here's the code for a JSONAPI endpoint that supports creating,
+updating, and deleting resources, complete with validation errors. Keep in mind a small modification will
+enable **nested** creates/updates/deletes/disassociations as well.
+
+We'll be adding on to the code from the [Basic
+Reads]({{site.github.url}}/ruby/reads/basic-reads) section.
+
+{% highlight ruby %}
+# config/routes.rb
+resources :posts, only: [:create, :update, :destroy]
+{% endhighlight %}
+
+{% highlight ruby %}
+# config/initializers/strong_resources.rb
+StrongResources.configure do
+ strong_resource :post do
+ attribute title: :string
+ attribute body: :string
+ attribute rating: :integer
+ end
+end
+{% endhighlight %}
+
+
+{% highlight ruby %}
+# app/controllers/posts_controller.rb
+
+strong_resource :employee
+
+before_action :apply_strong_params, only: [:create, :update]
+
+def create
+ post, success = jsonapi_create.to_a
+
+ if success
+ render_jsonapi(post, scope: false)
+ else
+ render_errors_for(post)
+ end
+end
+
+def update
+ post, success = jsonapi_update.to_a
+
+ if success
+ render_jsonapi(post, scope: false)
+ else
+ render_errors_for(post)
+ end
+end
+
+def destroy
+ post, success = jsonapi_destroy.to_a
+
+ if success
+ render json: { meta: {} }
+ else
+ render_errors_for(post)
+ end
+end
+{% endhighlight %}
+
+You'll see these controller methods all look very similar. Let's walk
+through what's going on.
+
+* `jsonapi_create/update/destroy`
+ * Parses the incoming request (including nested associations) and
+ delegates logic to the correct `Resource` classes.
+ * Wraps everything in a transaction.
+ * Handles validation errors.
+* `post, success`
+ * `post` is our model instance. Keep in mind, this may be an
+ unpersisted instance if our request had validation errors.
+ * `success` is a boolean indicating if the transaction was successful.
+ Mostly used to determine if we had validation errors.
+* `render_jsonapi` is explained in [Basic
+ Reads]({{site.github.url}}/ruby/reads/basic-reads)
+* `render_errors_for` collects any validation errors and formats them
+ into a [JSONAPI-compliant errors object](http://jsonapi.org/format/#errors). This includes nested validation errors.
+* `render json: { meta: {} }` (destroy only)
+ * Satisfied the [JSONAPI specification](http://jsonapi.org/format/#crud-deleting-responses-200) for deletes.
+
+We'll expand on these topics in the rest of the "Writes" section.
+
+#### Delegating Logic to Resources
+
+With read operations, we supply hooks, essentially asking the developer
+"*How do you want to modify the scope when a sort parameter comes in? How
+about when the `title` filter comes in?*".
+
+The same logic applies to write operations - but instead of "*how do you
+want to modify the scope?*" the question is "***how do you want to persist
+this data***"?
+
+{% highlight ruby %}
+def create(attributes)
+ puts attributes # { title: "Some Post" }
+end
+{% endhighlight %}
+
+In `ActiveRecord`'s case, you can imagine the defaults look something
+like this:
+
+{% highlight ruby %}
+def create(attributes)
+ post = Post.new(attributes)
+ post.save
+ post
+end
+
+def update(attributes)
+ post = Post.find(attributes.delete(:id))
+ post.update_attributes(attributes)
+ post
+end
+
+def destroy(id)
+ post = Post.find(id)
+ post.destroy
+ post
+end
+{% endhighlight %}
+
+> Note: create/update/destroy **must always** return the model instance
+
+Similar to read operations, we package this logic into an
+[Adapter]({{site.github.url}}/ruby/alternate-datastores/adapters) to
+DRY-up the boilerplate.
+
+For additional documentation:
+
+* [#create](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Resource.html#create-instance_method)
+* [#update](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Resource.html#update-instance_method)
+* [#destroy](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Resource.html#destroy-instance_method)
diff --git a/ruby/writes/nested-writes.md b/ruby/writes/nested-writes.md
new file mode 100644
index 0000000..d14af22
--- /dev/null
+++ b/ruby/writes/nested-writes.md
@@ -0,0 +1,102 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Nested Writes
+
+> View the Sample App: [Server](https://github.com/jsonapi-suite/employee_directory/compare/step_19_custom_persistence...step_23_disassociation) \| [Client](https://github.com/jsonapi-suite/employee-directory-vue/compare/step_9_dropdown...step_10_nested_create)
+
+Nested writes occur via "sideposting". Using the same example from the
+[strong resources section]({{site.github.url}}/ruby/writes/strong-resources), let's "sidepost" a `Person` at the `/accounts` endpoint:
+
+{% highlight ruby %}
+# PUT /accounts/123
+
+{
+ data: {
+ type: "accounts",
+ id: "123",
+ attributes: { name: "updated" }
+ relationships: {
+ people: {
+ data: [
+ { id: "1", type: "people", method: "update" }
+ ]
+ }
+ }
+ },
+ included: [
+ {
+ type: "people",
+ id: "1",
+ attributes: { name: "updated" }
+ }
+ ]
+}
+{% endhighlight %}
+
+Here we've update the `Account` and associated `Person` in a single
+request. You'll see this is nothing more than a mirror of a
+["sideloading" payload](http://jsonapi.org/format/#document-compound-documents), with one key addition - because HTTP verbs only apply to the top-level resource, we add a `method` key for all associated resources. Because we're sticking to the convention of pairing a Resource with a verb, we call this "REST with Relationships". Verbs can be one of `create`, `update`, `destroy` or `disassociate`.
+
+> Read more about the [Sideposting Concept]({{site.github.url}}/concepts#sideposting)
+
+There's not much code to satisfy this document. Make sure the
+relationship is defined in your `Resource`:
+
+{% highlight ruby %}
+# app/resources/account_resource.rb
+
+has_many :people,
+ resource: PersonResource,
+ foreign_key: :account_id,
+ scope: -> { Person.all }
+{% endhighlight %}
+
+And whitelisted in your controller:
+
+{% highlight ruby %}
+# app/controllers/accounts_controller.rb
+
+strong_resource :account do
+ has_many :people
+end
+{% endhighlight %}
+
+That's it!
+
+#### temp-id
+
+There's one final concept in sideposting, specific to `create`. We need
+to tell our clients how to update an in-memory object with the
+newly-minted `id` from the server. To do this, we pass `temp-id` (a random uuid) in the
+request instead of `id`:
+
+{% highlight ruby %}
+# create an Account with a Person in a single request
+{
+ data: {
+ type "accounts",
+ attributes: { name: "new account" },
+ relationships: {
+ people: {
+ data: [
+ { :'temp-id' => "abc123", type: "people" }
+ ]
+ }
+ },
+ included: [
+ {
+ type: "people",
+ :"temp-id" => "abc123",
+ attributes: { name: "John Doe" }
+ }
+ ]
+}
+{% endhighlight %}
+
+Clients like [JSORM]({{site.github.url}}/js/home) will handle this for you
+automatically.
diff --git a/ruby/writes/side-effects.md b/ruby/writes/side-effects.md
new file mode 100644
index 0000000..646a9f8
--- /dev/null
+++ b/ruby/writes/side-effects.md
@@ -0,0 +1,90 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Side Effects
+
+Side effects scenarios come up often. What if we want to send an email
+notification every time a `Comment` is created?
+
+It's important to note that there are three overall categories of side effects,
+and each requires a different solution:
+
+* Side effects internal to the `Model`: For example, setting a
+ `published_at` attribute.
+* Side effects that should only occur on a specific request: For
+ example, only send an email update if we're creating a `Post` for the
+ first time, at the `/posts` endpoint.
+* Side effects that should occur on every *type* of request: For
+example, send an email notification every time a `Comment` is created -
+but not updated - whether it was created at the `/comments` endpoint or sideposted at the
+`/posts` endpoint.
+
+#### Internal Side-Effects
+
+For the first scenario, it's OK to use `ActiveRecord` callbacks (or the
+equivalent functionality in a different ORM):
+
+{% highlight ruby %}
+# app/models/user.rb
+class Post < ApplicationRecord
+ before_save :set_published_at,
+ on: :update,
+ if: :publishing?
+
+ private
+
+ def set_published_at
+ self.published_at = Time.now
+ end
+
+ def publishing?
+ status_changed? && status == 'published'
+ end
+end
+{% endhighlight %}
+
+#### Side-Effects on Specific Action
+
+Just like you would with vanilla Rails, use the controller. Here we'll
+only send an email to our subscribers when the `Post` is first created.
+Keep in mind we could also "sidepost" `Post` objects at the `/blogs`
+endpoint, but this will only fire at the `/posts` endpoint.
+
+{% highlight ruby %}
+class PostsController < ApplicationController
+ def create
+ post, success = jsonapi_create.to_a
+
+ if success
+ PostMailer.published_email.deliver_later
+ render_jsonapi(post, scope: false)
+ else
+ render_errors_for(post)
+ end
+ end
+end
+{% endhighlight %}
+
+#### Side-Effects on Every Request of a Given Type
+
+Let's add some special logging every time we create a `Post`. Note this
+will fire *every* time we create a `Post` - whether we create it at the
+`/posts` endpoint or "sidepost" at the `/blogs` endpoint.
+
+We **do not** need to worry about this side-effect in our model specs,
+or rake tasks, as the functionality is only relevant to the API.
+
+Edit your `Resource`:
+
+{% highlight ruby %}
+# app/resources/post_resource.rb
+def create(attributes)
+ Rails.logger.info "Post begin created by #{context.current_user.email}..."
+ super
+ Rails.logger.info "Success!"
+end
+{% endhighlight %}
diff --git a/ruby/writes/strong-resources.md b/ruby/writes/strong-resources.md
new file mode 100644
index 0000000..861cf14
--- /dev/null
+++ b/ruby/writes/strong-resources.md
@@ -0,0 +1,94 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Strong Resources
+
+> View the Sample App: [Basic](https://github.com/jsonapi-suite/employee_directory/compare/step_15_validations...step_16_strong_resources) \| [Nested](https://github.com/jsonapi-suite/employee_directory/compare/step_19_custom_persistence...step_20_association_create)
+
+> [View the Strong Resources Github Documentation](https://jsonapi-suite.github.io/strong_resources)
+
+Rails 4 introduced the concept of [Strong Parameters](http://edgeguides.rubyonrails.org/action_controller_overview.html#strong-parameters), a way to whitelist incoming parameters for a given write operation. The folks at Zendesk took it a step further with [Stronger Parameters](https://github.com/zendesk/stronger_parameters), which added type-checking to the strong parameter checks.
+
+This works well for traditional REST endpoints that can put the logic in
+the controller. But JSONAPI Suite endpoints can "sidepost" objects at
+multiple endpoints - we might save a `Person` at the `/people` endpoint,
+but also sidepost from the `/accounts` endpoint. The strong parameters
+logic would need to be duplicated across controllers.
+
+Enter [Strong Resources](https://jsonapi-suite.github.io/strong_resources). Define whitelist templates in one place, and re-use them across your application:
+
+{% highlight ruby %}
+# config/initializers/strong_resources.rb
+StrongResources.configure do
+ strong_resource :account do
+ attribute :name, :string
+ attribute :active, :boolean
+ end
+end
+{% endhighlight %}
+
+{% highlight ruby %}
+# app/controllers/accounts_controller.rb
+
+before_action :apply_strong_params, only: [:create, :update]
+
+strong_resource :account
+{% endhighlight %}
+
+Now, whenever we `POST` or `PUT` to `/accounts`, the request
+`attributes` must come in this format. If an extra attribute is given -
+perhaps a read-only `rate_limit` attribute - the request will be
+rejected. If `active` comes in as a string instead of a boolean, the
+request will be rejected.
+
+Let's sidepost a `Person` record to the `/accounts` endpoint:
+
+{% highlight ruby %}
+# config/initializers/strong_resources.rb
+
+# ... code ...
+strong_resource :person do
+ attribute :name, :string
+ attribute :age, :integer
+end
+{% endhighlight %}
+
+{% highlight ruby %}
+# app/controllers/accounts_controller.rb
+
+strong_resource :account do
+ has_many :people
+end
+{% endhighlight %}
+
+We can now sidepost `Person` records - via the `people` relationship -
+to the `/accounts` endpoint. If the `Person` attributes don't match the
+`:person` strong resource template, the request will be rejected.
+
+By default, we only allow `create` and `update` of associations, but you
+can opt-in to `destroy` and `disassociate` as well:
+
+{% highlight ruby %}
+# app/controllers/accounts_controller.rb
+
+strong_resource :account do
+ has_many :people, destroy: true, disassociate: true
+end
+{% endhighlight %}
+
+There are a variety of ways to customize strong resource templates -
+like allowing certain parameters only on `update` but not `create`. Head
+over to the [strong_resources documentation](https://jsonapi-suite.github.io/strong_resources/) for a more in-depth
+overview.
+
+
+Note: a common issue is allowing an input to be null. You can define your own types, or
+ {% highlight ruby %}
+# config/initializers/strong_resources.rb
+ActionController::Parameters.allow_nil_for_everything = true
+ {% endhighlight %}
+
diff --git a/ruby/writes/validations.md b/ruby/writes/validations.md
new file mode 100644
index 0000000..a5f3ae9
--- /dev/null
+++ b/ruby/writes/validations.md
@@ -0,0 +1,75 @@
+---
+layout: page
+---
+
+{% include ruby-toc.html %}
+
+
+### Validations
+
+> [View the JSONAPI Errors Spec](http://jsonapi.org/format/#errors)
+
+> View the Sample App: [Server](https://github.com/jsonapi-suite/employee_directory/compare/step_14_create...step_15_validations) \| [Client](https://github.com/jsonapi-suite/employee-directory-vue/compare/step_10_nested_create...step_11_validations)
+
+> [View the JS Documentation]({{site.github.url}}/js/writes/validations)
+
+Validation errors are handled automatically for any models adhering to
+the [ActiveModel::Validations API](http://api.rubyonrails.org/classes/ActiveModel/Validations.html).
+
+After we've run the persistence logic - but before we close the
+transaction - we check `model.errors`. If errors are present anywhere in
+the graph, we rollback the transaction and return a JSONAPI-compliant [Error response](http://jsonapi.org/format/#errors):
+
+{% highlight ruby %}
+[
+ {
+ code: "unprocessable_entity",
+ detail: "Name can't be blank",
+ meta: {
+ attribute: "name",
+ message: "can't be blank"
+ },
+ source: {
+ pointer: "/data/attributes/name"
+ },
+ status: "422",
+ title: "Validation Error"
+ }
+]
+{% endhighlight %}
+
+This is true for nested write operations as well. Let's say we were
+saving an `Employee` and their `Position`s in a single request, but one
+of the positions had a validation error on a missing `title`:
+
+{% highlight ruby %}
+[
+ {
+ code: 'unprocessable_entity',
+ status: '422',
+ title: 'Validation Error',
+ detail: "Title can't be blank",
+ source: { pointer: '/data/attributes/title' },
+ meta: {
+ relationship: {
+ attribute: :title,
+ message: "can't be blank",
+ code: :blank,
+ name: :positions,
+ id: '123',
+ type: 'positions'
+ }
+ }
+ }
+]
+{% endhighlight %}
+
+This is enough information for a client to apply errors to the relevant
+objects. In JSORM's case, you'd see:
+
+{% highlight typescript %}
+let success = await employee.save({ with: "positions" })
+console.log(employee.errors) // # {}
+let position = employee.positions[0]
+console.log(position.errors.title.message) // # "Can't be blank"
+{% endhighlight %}
diff --git a/spec/jsonapi_suite_spec.rb b/spec/jsonapi_suite_spec.rb
deleted file mode 100644
index 374cd4d..0000000
--- a/spec/jsonapi_suite_spec.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-require 'spec_helper'
-
-describe JsonapiSuite do
- it 'has a version number' do
- expect(JsonapiSuite::VERSION).not_to be nil
- end
-
- it 'does something useful' do
- expect(false).to eq(true)
- end
-end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
deleted file mode 100644
index 0ce59c8..0000000
--- a/spec/spec_helper.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
-require 'jsonapi_suite'
diff --git a/tutorial.md b/tutorial.md
new file mode 100644
index 0000000..5822b0d
--- /dev/null
+++ b/tutorial.md
@@ -0,0 +1,630 @@
+---
+layout: page
+---
+
+Tutorial
+==========
+
+##### Walking Through Customization and Real-World Scenarios
+
+In this section, we'll build an employee directory application.
+If you're looking for a brief overview, head to the
+[Quickstart]({{site.github.url}}/quickstart)
+instead.
+
+
+
+If you get lost, you can view the code on Github:
+
+* [Server](https://github.com/jsonapi-suite/employee_directory)
+* [Client](https://github.com/jsonapi-suite/employee-directory)
+
+*Note: each of these repos has a separate branch for step one,
+step two, etc. View the latest branch for final code, or follow along
+step-by-step*
+
+The intent is to illustrate a variety of real-world use cases:
+
+* Turning 3 database tables into one cohesive search grid.
+* Customizing SQL queries.
+* Ability to filter, sort, and paginate data.
+* Total count
+* Custom Serialization
+* Nested CRUD of relationships, including validation errors.
+
+*Note: to better understand the underlying code, we'll be avoiding use
+of generators. Head to the [Quickstart]({{site.github.url}}/quickstart) for a guide on how
+to automate much of the legwork here.*
+
+# Reads
+## Setup
+
+We'll be creating an API for an Employee Directory. An `Employee` has many `Position`s (one of which is the *current* position), and a `Position` belongs to a `Department`.
+
+Let's start with a basic foundation: an index endpoint (list multiple
+entities) and a show (single entity) endpoint for an Employee model.
+
+Code:
+
+{% highlight ruby %}
+# app/controllers/employees_controller.rb
+class EmployeesController < ApplicationController
+ jsonapi resource: EmployeeResource
+
+ def index
+ render_jsonapi(Employee.all)
+ end
+
+ def show
+ scope = jsonapi_scope(Employee.where(id: params[:id]))
+ render_jsonapi(scope.resolve.first, scope: false)
+ end
+end
+{% endhighlight %}
+
+{% highlight ruby %}
+# app/resources/employee_resource.rb
+class EmployeeResource < ApplicationResource
+ type :employees
+end
+{% endhighlight %}
+
+{% highlight ruby %}
+# app/serializers/serializable_employee.rb
+class SerializableEmployee < JSONAPI::Serializable::Resource
+ type :employees
+
+ attribute :first_name
+ attribute :last_name
+ attribute :age
+end
+{% endhighlight %}
+
+Tests:
+
+{% highlight ruby %}
+# spec/api/v1/employees/index_spec.rb
+RSpec.describe 'v1/employees#index', type: :request do
+ let!(:employee1) { create(:employee) }
+ let!(:employee2) { create(:employee) }
+
+ it 'lists employees' do
+ get '/api/v1/employees'
+ expect(json_ids(true)).to eq([employee1.id, employee2.id])
+ assert_payload(:employee, employee1, json_items[0])
+ end
+end
+{% endhighlight %}
+
+{% highlight ruby %}
+# spec/api/v1/employees/show_spec.rb
+RSpec.describe 'v1/employees#show', type: :request do
+ let!(:employee) { create(:employee) }
+
+ it 'returns relevant employee' do
+ get "/api/v1/employees/#{employee.id}"
+ assert_payload(:employee, employee, json_item)
+ end
+end
+{% endhighlight %}
+
+A note on testing: these are full-stack [request specs](https://github.com/rspec/rspec-rails#request-specs). We seed the database using [factory_girl](https://github.com/thoughtbot/factory_girl), randomizing data with [faker](https://github.com/stympy/faker), then assert on the resulting JSON using [spec helpers](https://jsonapi-suite.github.io/jsonapi_spec_helpers).
+
+You won't have to write *all* the tests you see here, some are simply for demonstrating the functionality.
+
+## Filtering
+
+One line of code allows simple `WHERE` clauses. If the user tried to filter on something not whitelisted here, an error would be raised.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/master...step_1_add_filter)
+
+## Custom Filtering
+
+Sometimes `WHERE` clauses are more complex, such as prefix queries. Here we'll query all employees whose age is greater than or equal to a given number.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/step_1_add_filter...step_2_add_custom_filter)
+
+## Sorting
+
+Sorting comes for free, but here's a test for it. Decide as a team if we *actually* need to write a spec here, or if it's considered tested within the libraries.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/step_2_add_custom_filter...step_3_basic_sorting)
+
+## Custom Sorting
+
+Sometimes we need more than a simple `ORDER BY` clause, for example maybe we need to join on another table. In this example, we switch from Postgres's default case-sensitive query to a case in-sensitive one...but only for the `first_name` field.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/step_3_basic_sorting...step_4_custom_sorting)
+
+## Pagination
+
+Pagination also comes for free, so once again we'll have to decide if writing a spec like this is worth the bother.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/step_4_custom_sorting...step_5_pagination)
+
+## Custom Pagination
+
+By default we use the [Kaminari](https://github.com/kaminari/kaminari) library for pagination. This shows how we could instead sub-out Kaminari and replace it with [will_paginate](https://github.com/mislav/will_paginate)
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/step_5_pagination...step_6_custom_pagination)
+
+## Statistics
+
+For default statistics, (`count`, `sum`, `average`, `maximum` and `minimum`), simply specify the field and statistic.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/step_6_custom_pagination...step_7_stats)
+
+## Custom Statistics
+
+Here we add a `median` statistic to show non-standard custom statistic usage.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/step_7_stats...step_8_custom_stats)
+
+## Custom Serialization
+
+Let's say we wanted the employee's age to serialize `Thirty-Two` instead of `32` in JSON. Here we use a library to get the friendly-word doppleganger, and change the test to recognize this custom logic.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/master...custom-serialization)
+
+## Has-Many Association
+
+Get employees and their positions in one call.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/master...step_9_has_many)
+
+## Belongs-To Association
+
+Get employees, positions, and the department for those positions in one call:
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/step_9_has_many...step_10_belongs_to)
+
+## Many-to-Many
+
+In this example an `Employee` has many `Team`s and a `Team` has many `Employee`s.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/step_13_error_handling...many-to-many)
+
+## Resource Re-Use
+
+In prior steps we created `PositionResource` and `DepartmentResource`. These objects may have custom sort logic, filter whitelists, etc - this configuration can be re-used if we need to add `/api/v1/positions` and `/api/v1/departments` endpoints.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/step_10_belongs_to...step_11_resource_reuse)
+
+## Filter/Sort/Paginate Associations
+
+This comes for free. As long as the associated `Resource` knows how to do something, we can re-use that logic.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/step_11_resource_reuse...step_12_fsp_associations)
+
+## Error Handling
+
+In this example we add global error handling, so any random error will return a [JSONAPI-compatible error response](http://jsonapi.org/format/#errors). Then we customize that response for a specific scenario (the requested employee does not exist).
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/step_12_fsp_associations...step_13_error_handling)
+
+# Writes
+
+## Basic Create
+
+Basic example without validations or strong parameters.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/bump_gemfile_for_writes...step_14_create)
+
+## Validations
+
+Validations are basic, vanilla Rails code. When there is a validation error, we return a jsonapi-compatible error respone.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/step_14_create...step_15_validations)
+
+## Strong Resources
+
+The biggest problem with `strong_parameters` is that we might want to create an employee from the `/employees` endpoint, or we might want to create a position with an employee at the same time from `/positions`. Maintaining the same strong parameter hash across a number of places is difficult.
+
+Instead we use `strong_resources` to define the parameter template *once*, and re-use. This has the added benefit of being built on top of [stronger_parameters](https://github.com/zendesk/stronger_parameters), which gives us type checking and coercion.
+
+Note: `strong_resources` requires Rails.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/step_15_validations...step_16_strong_resources)
+
+## Basic Update
+
+Looks very similar to `create`.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/step_16_strong_resources...step_17_basic_update)
+
+## Basic Destroy
+
+More or less basic Rails.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/step_17_basic_update...step_18_basic_destroy)
+
+## Customizing Persistence
+
+So far we've shown `ActiveRecord`. What if we wanted to use a different ORM, or ElasticSearch? What if we wanted 'side effects' such as "send a confirmation email after creating the user"?
+
+This code shows how to customize `create/update/destroy`. In this example we're simply logging the action, but you could do whatever you want here as long as you return an instance of the object. Just like with reads, if any of this code becomes duplicative across `Resource` objects you could move it into a common `Adapter`.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/step_18_basic_destroy...step_19_custom_persistence)
+
+## Association Writes
+
+### Nested Creates
+
+Think Rails' `accepts_nested_attributes_for`, but not coupled to Rails or ActiveRecord. Here we create an `Employee`, a `Position` for the employee, and a `Department` for the position in one call. This is helpful when dealing with nested forms!
+
+Once again, note how our `strong_resources` can be shared across controllers.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/step_19_custom_persistence...step_20_association_create)
+
+### Nested Updates
+
+We got this for free, here's a spec!
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/step_20_association_create...step_21_association_update)
+
+### Nested Destroys
+
+We get this for free, though we have to explicitly tell `strong_resources` that destroys are allowed from this endpoint.
+
+Note destroy will do two things: delete the object, and make the foreign key on the corresponding child in the payload `null`.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/step_21_association_update...step_22_association_destroy)
+
+### Disassociations
+
+`destroy` actually deletes objects, what if we want to simply disassociate the objects by making the foreign key `null`? We get this for free, too.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee_directory/compare/step_22_association_destroy...step_23_disassociation)
+
+### Usage without ActiveRecord
+
+Let's say the departments come from a service call. Here's the change to
+the `/departments` endpoint.
+
+Make the model a PORO:
+
+{% highlight ruby %}
+# app/models/position.rb
+
+# belongs_to :department, optional: true
+attr_accessor :department
+{% endhighlight %}
+
+Use `{}` as our base scope instead of `ActiveRecord::Relation`:
+
+{% highlight ruby %}
+# app/controllers/departments_controller.rb
+def index
+ # render_jsonapi(Department.all)
+ render_jsonapi({})
+end
+{% endhighlight %}
+
+Customize `resolve` for the new hash-based scope:
+
+{% highlight ruby %}
+# app/resources/department_resource.rb
+use_adapter JsonapiCompliable::Adapters::Null
+
+def resolve(scope)
+ Department.where(scope)
+end
+{% endhighlight %}
+
+`Department.where` is our contract for resolving the scope. The underlying `Department` code could use an HTTP client, alternate datastore, what-have-you.
+
+Let's also change our code for sideloading departments at `/api/v1/employees?include=departments`:
+
+{% highlight ruby %}
+# app/resources/position_resource.rb
+
+# belongs_to :department,
+# scope: -> { Department.all },
+# foreign_key: :department_id,
+# resource: DepartmentResource
+
+allow_sideload :department, resource: DepartmentResource do
+ scope do |employees|
+ Department.where(employee_id: employees.map(&:id))
+ end
+
+ assign do |employees, departments|
+ employees.each do |e|
+ e.department = departments.find { |d| d.employee_id == e.id }
+ end
+ end
+end
+{% endhighlight %}
+
+As you can see, we're delving into a lower-level DSL to customize. You
+probably want to package up these changes into an [Adapter](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Adapters/Abstract.html). The `ActiveRecord` adapter is simple packaging up similar low-level defaults. Your app may require an `HTTPAdapter` or `ServiceAdapter`, or you can make one-off customizations as shown above.
+
+# ElasticSearch
+
+Similar to a service call, here's how we might incorporate the elasticsearch [trample](https://github.com/richmolj/trample) gem.
+
+Make our base scope an instance of our Trample client:
+
+{% highlight ruby %}
+# app/controllers/employees_controller.rb
+ def index
+ # render_jsonapi(Employee.all)
+ render_jsonapi(Search::Employee.new)
+ end
+end
+{% endhighlight %}
+
+Customize the resource using the Trample Client API:
+
+{% highlight ruby %}
+# app/resources/employee_resource.rb
+use_adapter JsonapiCompliable::Adapters::Null
+
+allow_filter :first_name do |scope, value|
+ scope.condition(:first_name).eq(value)
+end
+
+allow_filter :first_name_prefix do |scope, value|
+ scope.condition(:first_name).starts_with(value)
+end
+
+def resolve(scope)
+ scope.query!
+ scope.results
+end
+{% endhighlight %}
+
+Once again, you probably want to package these changes into an [Adapter](https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Adapters/Abstract.html).
+
+# Client-Side
+
+## JSORM
+
+There are number of [jsonapi clients](http://jsonapi.org/implementations/) in a variety of languages. Here we'll be using [JSORM](/js/home) - an ActiveRecord-style ORM that can be used from Node or the browser. It's been custom-built to work with JSONAPI Suite enhancements.
+
+This will fetch an employee with id 123, their last 3 positions where the title starts with 'dev', and the departments for those positions.
+
+We'll use typescript for this example, though we could use vanilla JS just as well. First define our models (additional client-side business logic can go in these classes):
+
+{% highlight typescript %}
+import { JSORMBase, Model, Attr, HasMany, BelongsTo } from "jsorm"
+
+@Model()
+class ApplicationRecord extends JSORMBase {
+ static baseUrl = "http://localhost:3000"
+ static apiNamespace = "/api/v1"
+}
+
+@Model()
+class Employee extends ApplicationRecord {
+ static jsonapiType = "people"
+
+ @Attr() firstName: string
+ @Attr() lastName: string
+ @Attr() age: number
+
+ @HasMany() positions: Position[]
+}
+
+@Model()
+class Position extends ApplicationRecord {
+ static jsonapiType = "positions"
+
+ @Attr() title: string
+
+ @BelongsTo() department: Department[]
+}
+
+@Model()
+class Department extends ApplicationRecord {
+ static jsonapiType = "departments"
+
+ @Attr() name: string
+}
+{% endhighlight %}
+
+Now fetch the data in one call:
+
+{% highlight javascript %}
+let positionScope = Position
+ .where({ title_prefix: "dev" })
+ .order({ created_at: "desc" })
+
+let scope = Employee
+ .includes({ positions: "department" })
+ .merge({ positions: positionScope })
+
+let employee = (await scope.find(123)).data
+// access data like so in HTML:
+// employee.positions[0].department.name
+}
+{% endhighlight %}
+
+[Read the JSORM documentation here](/js/home)
+
+## VueJS Sample Application
+
+
+
+**JSORM can be used with the client-side framework of your choice**. To give an example of real-world usage, we've created a demo application using
+[VueJS](https://vuejs.org/). Vue is lightweight and provides the
+bare-bones we need to illustrate JSONAPI and JSORM in action.
+
+This will point to a [slightly-tweaked branch](https://github.com/jsonapi-suite/employee_directory/tree/prepare_clientside) of the server-side API
+above.
+
+Let's create our app.
+
+### Step 0: Setup
+
+We've started with a basic Vue app configured with Webpack. None of this
+is JSONAPI-specific, just boilerplate to get started quickly.
+
+
+[View the Branch on Github](https://github.com/jsonapi-suite/employee-directory-vue/tree/step_0_setup)
+
+### Step 1: Models
+
+We'll start by defining our models, which should look very familiar if
+you've worked with `ActiveRecord`. Again, see the [JSORM
+documentation](/js/home) if any of this looks confusing to you.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee-directory-vue/compare/step_0_setup...step_1_models)
+
+### Step 2: Data Grid
+
+We'll add a simple data grid to our page listing all `Employee`s. This
+will turn into a search grid, but for now we're simply loading employees
+via `Employee.all()`
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee-directory-vue/compare/step_1_models...step_2_data_grid)
+
+### Step 3: Adding Relationships
+
+Here we've added a `currentPosition` relationship to avoid fetching
+excess data from the server. When the page loads we fetch the
+employees, current positions for those employees, and the departments for those positions.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee-directory-vue/compare/step_2_data_grid...step_3_includes)
+
+### Step 4: Filtering
+
+Here we've added a bit of state to the page, `query`, which will be
+bound to the form inputs. We pass `query` to `Employee.where` to
+successfully query employees by first and last name.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee-directory-vue/compare/step_3_includes...step_4_filtering)
+
+### Step 5: Sorting
+
+Just like with filters, we add a bit of state to our page to track
+sorting parameters. When the user clicks a table header, we'll update
+that state and pass it to our query.
+
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee-directory-vue/compare/step_4_filtering...step_5_sorting)
+
+### Step 6: Stats
+
+A JSORM promise returns a `response` object with a `data` key - the
+model instance(s) we've been referencing so far. It also returns a
+`meta` key that reflects the [meta](http://jsonapi.org/format/#document-meta) section of the JSONAPI response.
+
+Here we'll request the total count of employees in our query to the
+server, grab that total count from `meta`, and bind it to the page as
+`totalCount`
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee-directory-vue/compare/step_5_sorting...step_6_stats)
+
+### Step 7: Pagination
+
+Building on what we've already done, we can apply a similar pattern for
+pagination. We add the `currentPage` state and alter it when the user
+clicks pagination links. We use `currentPage` and `totalCount` to figure
+out if we should display previous/next page links.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee-directory-vue/compare/step_6_stats...step_7_pagination)
+
+### Step 8: Basic Form Setup
+
+We'll be adding a form to create and update employees. This step just
+adds the relevant HTML and Vue code to get set up, before involving
+JSORM.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee-directory-vue/compare/step_7_pagination...step_8_basic_form_setup)
+
+### Step 9: Dropdowns
+
+Our form will submit employees, positions and departments in a single
+request. We associate a position to an existing department through a
+`select` dropdown. To populate that dropdown, we fetch all departments
+from the server and bind it to the `select`.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee-directory-vue/compare/step_8_basic_form_setup...step_9_dropdown)
+
+### Step 10: Nested Create/Update
+
+This step simply binds our instantiated models to the form. When the
+form is submitted, we save everything in a single one-line request.
+
+After the form submission, we could edit the form and submit again -
+JSORM will know to `PATCH` an update to the appropriate URL
+automatically.
+
+Note we could also edit our search to immediately reflect the new
+data...but this is more Vue-specific than anything to do with JSORM, so
+we'll hold off until the last step.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee-directory-vue/compare/step_9_dropdown...step_10_nested_create)
+
+### Step 11: Validations
+
+Our server-side code will automatically handle validation errors and
+give us a well-formatted response. JSORM will read that response and
+automatically apply `error` objects to our model instances. Here we
+display simple error messages without involving any JS code.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee-directory-vue/compare/step_10_nested_create...step_11_validations)
+
+### Step 12: Nested Destroy
+
+This step adds buttons to our form that will add and remove positions
+for a given employee. To add, we simply `push` a new `Employee` onto the
+relationship array. To remove, we set `isMarkedForDestruction = true`,
+which allows for "unsaved deleted records". This follows a similar
+pattern to one introduced in Ember Data, [explained here](https://www.emberjs.com/blog/2015/09/02/ember-data-2-0-released.html#toc_unsaved-deleted-records).
+
+Note that if we wanted to **disassociate** the position rather than
+**destroying** the underlying record, we could use
+`position.isMarkedForDisassociation = true`.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee-directory-vue/compare/step_10_nested_create...step_11_validations)
+
+### Step 13: VueJS Wrap-up
+
+Our final step adds some Vue-specific functionality - we add an
+`EventBus` to allow selecting an employee from the grid and binding it
+to the form, and refresh the search grid after each form submission.
+
+
+[View the Diff on Github](https://github.com/jsonapi-suite/employee-directory-vue/compare/step_12_nested_destroy...step_13_vue)
+
+
+