This is an application that provides a simple example of a JSON:API compliant server written in Ruby on Rails. It's based on the cerebris/peeps application that is the demo of cerebris/jsonapi-resources.
Follow these instructions to create the application.
Create the rails app.
rails new json_api_server_example --skip-javascript --skip-test-unit
Create the database.
bin/rake db:create
Edit the Gemfile (the application's dependencies).
- Remove unnecessary gems.
- Add the
jsonapi-resourcesto easily define an interface for our resources that complies with JSON:API specification. - Add
rspec-rails,spring-commands-rspec, andshoulda-matchersfor the test infrastructure. - Add
factory_girl_railsto generate factories to make testing easier.
# Gemfilesource"https://rubygems.org"gem"rails","4.2.0"gem"pg"gem"jsonapi-resources"group:development,:testdogem"byebug"gem"spring"gem"rspec-rails","~> 3.1.0"gem"spring-commands-rspec"gem"factory_girl_rails","~> 4.5.0"endgroup:testdogem"shoulda-matchers",require: falseend
bundle install bin/rails generate rspec:install bundle exec spring binstub rspecConfigure the environment.
# config/environments/development.rb# avoids autoloading strangeness and thread safety issuesconfig.eager_load=true# don't generate helpers because we're not going to use themconfig.generators.helper=false
Subclass
ApplicationControllerfromJSONAPI::ResourceController.
This will give controllers that inherit fromApplicationControllerthe ability to respond to JSON:API formatted requests.# app/controllers/application_controller.rbclassApplicationController < JSONAPI::ResourceControllerend
Create the resources directory where we'll add our resources classes.
mkdir app/resources
Create a model, test that it worked, and add a factory.
bin/rails g model Sport name:string bin/rake db:migrate Sports --skip-assets
# spec/models/sport_spec.rbRSpec.describeSport,type: :modeldoit{is_expected.tohave_attribute:name}end
# spec/factories/sports.rbFactoryGirl.definedofactory:sportdoname"Basketball"endend
Create a resource.
# app/resources/sport_resource.rbclassSportResource < JSONAPI::Resourceattributes:nameend
Setup the routes.
# config/routes.rbRails.application.routes.drawdojsonapi_resources:sportsend
Test that the sports resource is accessible via the API. This is just a simple set of tests to show that everything works and will be updated later as we add additional functionality.
# spec/controllers/sports_controller_spec.rbrequire'rails_helper'RSpec.describeSportsController,:type=>:controllerdodescribe"POST create"doit"responds with a 201 status"dopost:create,sports: FactoryGirl.attributes_for(:sport)expect(response.status).toeq201endenddescribe"GET show"doit"responds with a 200 status"doget:show,id: FactoryGirl.create(:sport)expect(response.status).toeq200endenddescribe"PUT update"dolet!:sportdoFactoryGirl.create(:sport)endit"updates the resource and responds with a 200 status"doexpect(sport.name).toeq"Basketball"put:update,id: sport,sports: {name: "basketball"}expect(sport.reload.name).toeq"basketball"expect(response.status).toeq200endenddescribe"GET index"doit"responds with a 200 status"doget:indexexpect(response.status).toeq200endenddescribe"DELETE destroy"doit"responds with a 200 status"dosport=FactoryGirl.create(:sport)delete:destroy,id: sportexpect(Sport.find_by(id: sport)).tobe_nilexpect(response.status).toeq204endendend
Test that the sports resource is serialized into JSON with the correct format.
First, to DRY up the code, create an example group that will be added to all resource specs.
# spec/rails_helper.rb# This will require all files in the support directory.# ...Dir[Rails.root.join("spec/support/**/*.rb")].each{ |f| requiref}
# spec/support/example_groups/resource_example_group.rbmoduleResourceExampleGroupextendActiveSupport::Concernincludeddorequire"jsonapi/resource_serializer"let:serializerdoJSONAPI::ResourceSerializer.new(described_class)endlet:modeldoFactoryGirl.build_stubbed(described_class._model_class.model_name.element,id: 1001)endlet:resourcedodescribed_class.new(model)endlet:serialized_hashdoserializer.serialize_to_hash(resource)endendRSpec.configuredo |config| config.include(self,type: :resource,file_path: %r(spec/resources))endend
Then, test that the serialized sport resource matches our expectation.
# spec/resources/sport_resource_spec.rbrequire"rails_helper"describeSportResourcedolet:expected_serialized_hashdo{"sports"=>{"id"=>1001,"name"=>"Basketball"}}endit"serializes the sport into the correct JSON format"doexpect(serialized_hash).toeq(expected_serialized_hash)endend
Start a development server and try the API out via curl.
bin/rails server
curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d '{"sports": {"name": "Basketball"}}' http://localhost:3000/sports
HTTP/1.1 201 Created X-Frame-Options: SAMEORIGIN X-Xss-Protection: 1; mode=block X-Content-Type-Options: nosniff Content-Type: application/json; charset=utf-8 Etag: W/"51eb95f4b3dc2f26a423d3072646f26e" Cache-Control: max-age=0, private, must-revalidate X-Request-Id: 4dd8b179-366e-408f-a692-c87479979894 X-Runtime: 0.028043 Server: WEBrick/1.3.1 (Ruby/2.2.0/2014-12-25) Date: Fri, 09 Jan 2015 14:46:21 GMT Content-Length: 39 Connection: Keep-Alive {"sports":{"id":1,"name":"Basketball"}}
curl http://localhost:3000/sports/1
{"sports":{"id":1,"name":"Basketball"}}curl -H "Accept: application/json" -H "Content-Type: application/json" -X PUT -d '{"sports":{"name": "basketball"}}' http://localhost:3000/sports/1
{"sports":{"id":1,"name":"basketball"}}curl http://localhost:3000/sports
{"sports":[{"id":1,"name":"basketball"}]}curl -i -X DELETE http://localhost:3000/sports/1
HTTP/1.1 204 No Content