Skip to content

Instantly share code, notes, and snippets.

@adstage-david
Last active February 7, 2018 16:20
Show Gist options
  • Save adstage-david/5500352 to your computer and use it in GitHub Desktop.
Save adstage-david/5500352 to your computer and use it in GitHub Desktop.
Building an RFC compliant JSON-Patch with Rails 3 and Backbone

I recently got PATCH mostly working with Backbone and Rails 3, here's what I needed to do.

Sticking Points:

  1. [Rails] JSON Patch gem needs updated.
  2. [Rails] Routing verb for patch missing - I believe this is fixed in Rails 4, haven't investigated.
  3. [Rails] Need to parse the 'application/json-patch+json' content type in sort of a crazy way
  4. [Rails] Building a patch and applying it to an activerecord model is kind of a pain, ideally we'd have a ActiveModel#apply_patch(json_patch)
  5. [Backbone] Building a patch from a set of attributes is tricky - I've done the bare minimum using a loop over a hash to build add operations, which should probably cover a good chunk of cases for backbone. (I imagine most people aren't going to need the json-patch support for moving, copying or deleting keys generally.)

Relevant discussion in backbone project:

# Gemfile
# Fork because tenderlove hasn't released the latest RFC-compliant gem yet,
# and doesn't have the gemspec in the repo for direct linking.
gem 'hana', git: "git://github.com/adstage-david/hana.git"
# config/routes.rb
# ...
resources :goals do
member do
# Rails 3 doesn't appear to have the patch verb for routes, believe this is fixed in Rails 4.
match :update, via: :patch
end
end
#...
# config/initializers/mime_types.rb
# Add new mime types for use in respond_to blocks:
Mime::Type.register "application/json-patch+json", :json_patch
ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime::Type.lookup('application/json-patch+json')]=lambda do |body|
{patch: JSON.parse(body)} # A JSON Patch is an array of operations,
# params parser has to be a hash, so we just set "patch" variable
end
# app/controllers/goals_controller.rb
class GoalsController < ApplicationController
def update
patch = Hana::Patch.new params[:patch]
# Dumb update for now
data = patch.apply({})
update_goal(params[:id], data)
end
def update_goal(id, params)
goal = Goal.find_by_id(params[:id])
goal.update_attributes(data)
end
end
# app/assets/javascripts/adstage_model.js.coffee
class adstage.Model extends Backbone.Model
patch: (attrs = {})->
patch = JSON.stringify(@buildPatch(attrs))
# patch makes sure only attrs is sent, rather than full json
# contentType sets json-patch.
@save(attrs, {data: patch, patch: true, contentType: 'application/json-patch+json'})
buildPatch: (attrs = {}, main_path = "")->
(_.flatten(_.map(attrs, (val, key)=>
path = "#{main_path}/#{key}"
if _.isArray(val)
# Technically this is just a blind replace of an old array where we could
# be appending each item in the array to the existing array.
# Would probably want to fix this.
{path: path, value: val, op: 'add'}
else if _.isObject(val)
@buildPatch(val, path)
else
{path: path, value: val, op: 'add'}
)))
@adstage-david
Copy link
Author

Need to add in check in the controller to return :unsupported_media_type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment