Skip to content

Instantly share code, notes, and snippets.

@beechnut
Last active January 2, 2016 23:59
Show Gist options
  • Save beechnut/8379657 to your computer and use it in GitHub Desktop.
Save beechnut/8379657 to your computer and use it in GitHub Desktop.
A "blow-by-blow" of my hypermedia-in-Rails work on beechnut/mfapi

I started with simple, concrete hypermedia for Objectives. For the collection I created a collection method, into which I passed the Objectives to create a collection+json object. For a single record, I loop through its associations with reflect_on_all_associations, and create links to all its associated objects.

I then moved this logic to ApplicationController and abstracted it for use with Indicators.

Namespaces in this hackery were a little tricky: self_link could theoretically mean either:

{"rel": "self", "href": "/path/to/self"}

or

Link: <http://localhost:3000/path/to/self?page=1>;rel=self

I don't know Rails well enough (yet!) to create a new renderer. The Rails 3 respond_to do |format| is messy, but for my small customizations it worked:

respond_to do |format|
  format.html # index.html.erb
    format.json { render json: collection(@indicators),
                         content_type: "collection+json"
                  response.headers['Link'] = link_header(current_page, last_page) }
end

I added pagination at the end. I needed a page_num util to do some calculations for the Link header, and my code for that is pretty wet (i.e. not DRY).

The billboard URL was also very un-Railsy. I explictly defined everything in root_controller.rb, as opposed to looping through the top-level models.

I'm sure I didn't form rel attributes well. I didn't have a clear idea of what a client might be expecting.

Next Steps

Before I go forward, I really want to extract this into a gem, and see if I can get more logic in the Model. From my experiment today it seems that a lot of this behavior is very generalizable, and could be customized easily (if it's a good gem, which I don't yet know how to write).

I imagine a 'hypermediafy' gem that:

One could implement it by doing:

# /app/models/indicator.rb

class Indicator < ActiveRecord::Base

    attr_accessible :attribute ...
    belongs_to      :objective
    has_many        :measurements, :datasets
    paginates_per   10
    
    hyperlinks_for  :objective, :measurements # whitelist relations to pass to `reflect_on_all_assocations`
    paginate_header :prev, :next # Adds rel=prev and rel=next links in the Link header
                                 # when used with a pagination gem like will_paginate or kaminari
    
end
# /app/controllers/indicators_controller.rb

class IndicatorsController < ApplicationController
  # GET /indicators
  # GET /indicators.json
  def index
    @indicators = Indicator.page(params[:page])

    respond_to do |format| # responds to 'Accept' headers
      format.html # index.html.erb
      format.json { render hypermedia: @indicators } # or render collection: @indicators
                                                     # sets Content-Type to 'collection+json'
    end
  end
  
# ...

end

I haven't yet done anything with authorization or PUT/POST actions. That's up next.

I also want to start defining ALPS.io semantics, and I could use help getting started there.

@mamund
Copy link

mamund commented Jan 12, 2014

@beechnut

looks like you're making good progress. i'll be interested in the "gem-ification" process; esp regarding generating links.

let me know what your plans are for ALPS semantics. i'd be happy to pitch in and/or kibitz along the way.

@beechnut
Copy link
Author

Revelations overnight: rel: "/indicators" is defining a rel as a path. The rel attribute is supposed to denote an action, so here it should be rel: "indicator" as in GET the indicator.

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