Skip to content

Instantly share code, notes, and snippets.

@eterps
Created Jan 30, 2015
Embed
What would you like to do?
REST/hypermedia webservice in Ruby using Webmachine, Webmachine linking and Roar for HAL/JSON support
require 'webmachine/adapters/rack'
require_relative 'webservice'
run Webservice.adapter
Feature: Artists
Scenario: Get information about the artist Radiohead
Given these albums:
| artist | album |
| Radiohead | Pablo Honey |
| Radiohead | OK Computer |
When the client provides the header "Accept: application/hal+json"
And the client does a GET request to "/artist/Radiohead"
Then the response should be HAL JSON:
"""json
{
"_links": {
"self": {
"href": "/artist/Radiohead"
},
"albums": [
{
"href": "/album/Pablo Honey"
},
{
"href": "/album/OK Computer"
}
]
},
"name": "Radiohead"
}
"""
Scenario: Add a new artist
Given the client provides the header "Content-Type: application/json"
When the client does a POST request to "/artists" with the following data:
"""json
{
"name": "Led Zeppelin"
}
"""
Then the status code should be "201" (Created)
And the Location header should be "http://example.org/artist/Led Zeppelin"
And this artist should exist:
| name |
| Led Zeppelin |
Scenario: List all artists
Given the client provides the header "Accept: application/hal+json"
When the client does a GET request to "/artists"
Then the response should be HAL JSON:
"""json
{
"_links": {
"self": {
"href": "/artists"
},
"artists": [
{
"href": "/artist/Radiohead"
},
{
"href": "/artist/Led Zeppelin"
}
]
}
}
"""
class Artist
attr_accessor :name, :albums
def initialize(name: name, albums: albums)
@name, @albums = name, albums
end
end
class Album
attr_accessor :name
def initialize(name: name)
@name = name
end
end
module ArtistRepresenter
include Roar::JSON::HAL
include BaseRepresenter
links(:albums) {|n| rel n, :albums }
property :name
end
module ArtistsRepresenter
include Roar::JSON::HAL
include BaseRepresenter
links(:artists) {|n| rel n, :artists }
end
class ArtistResource < BaseResource
def relations
{
self: {href: url_for(ArtistResource, name: @artist.name)},
albums: @artist.albums.map{|n| {href: url_for(AlbumResource, name: n.name)}}
}
end
def resource_exists?
@artist = DB.artists.find{|n| n.name == request.path_info[:name]}
end
private
def to_json
@artist.extend(ArtistRepresenter).to_json(rel: relations)
end
end
class ArtistsResource < BaseResource
def relations
{
self: {href: url_for(ArtistsResource)},
artists: @artists.map{|n| {href: url_for(ArtistResource, name: n.name)}}
}
end
def resource_exists?
@artists = DB.artists
end
def finish_request
return unless request.method == 'POST'
response.headers['Location'] += url_for(ArtistResource, name: @artist.name)[1..-1]
end
def allowed_methods; %w(GET POST) end
def post_is_create?; true end
def create_path; '' end
private
def from_json
@artist = Artist.new
@artist.extend(ArtistRepresenter).from_json(request.body.to_s)
@artists << @artist
end
def to_json
extend(ArtistsRepresenter).to_json
end
end
class AlbumResource < BaseResource
end
require 'roar/json/hal'
require 'webmachine'
require 'webmachine-linking'
module BaseRepresenter
include Roar::JSON::HAL
def rel(rels, id)
return relations[id] unless rels.include?(:rel)
rels[:rel][id]
end
link(:self) {|n| rel n, :self }
end
class BaseResource < Webmachine::Resource
include Webmachine::Linking::Resource::LinkHelpers
def content_types_accepted; [['application/json', :from_json]] end
def content_types_provided; [['application/hal+json', :to_json]] end
end
module DB
class << self
attr_accessor :artists
def artists
@artists ||= []
end
end
end
require_relative 'resources'
Webmachine.application.routes do
add ['artist', :name], ArtistResource
add ['album', :name], AlbumResource
add ['artists'], ArtistsResource
end
Webmachine.application.inject_resource_url_provider
Webservice = Webmachine.application
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment