Skip to content

Commit

Permalink
update app to use curies and embeds
Browse files Browse the repository at this point in the history
  • Loading branch information
kookster committed Mar 5, 2014
1 parent a146071 commit 56e8c66
Show file tree
Hide file tree
Showing 15 changed files with 181 additions and 46 deletions.
15 changes: 14 additions & 1 deletion app/controllers/api/base_controller.rb
@@ -1,6 +1,12 @@
class Api::BaseController < ApplicationController
protect_from_forgery with: :null_session

rescue_from ActiveRecord::RecordNotFound, with: :record_not_found

def record_not_found
render text: '{"error": "404 Not Found"}', status: 404
end

class << self

attr_accessor :understood_api_versions, :resource_class, :resources_params, :resource_representer
Expand Down Expand Up @@ -55,15 +61,22 @@ def resource_name
def show_options
options = {}
options[:represent_with] = self.class.resource_representer if self.class.resource_representer
options[:zoom_param] = zoom_param
options
end

def zoom_param
zp = (params[:z] || params[:zoom])
return nil if zp.blank?
zp.split(',').map(&:strip).compact
end

def index
respond_with collection
end

def collection
PagedCollection.new(with_params(self.class.resources_params, resources), request, item_class: self.class.resource_class, item_decorator: self.class.resource_representer, is_root_resource: true )
PagedCollection.new(with_params(self.class.resources_params, resources), request, item_class: self.class.resource_class, item_decorator: self.class.resource_representer)
end

def resources
Expand Down
6 changes: 0 additions & 6 deletions app/controllers/application_controller.rb
Expand Up @@ -3,10 +3,4 @@ class ApplicationController < ActionController::Base
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception

rescue_from ActiveRecord::RecordNotFound, with: :record_not_found

def record_not_found
render text: "404 Not Found\n", status: 404
end

end
4 changes: 4 additions & 0 deletions app/models/api.rb
Expand Up @@ -10,6 +10,10 @@ def self.version(version)

attr_accessor :version

def show_curies
true
end

def initialize(version)
@version = version
end
Expand Down
4 changes: 4 additions & 0 deletions app/models/base_model.rb
Expand Up @@ -11,6 +11,10 @@ def is_root_resource
!!@is_root_resource
end

def show_curies
is_root_resource
end

def update_file!(name)
filename_will_change!
raw_write_attribute(:filename, name)
Expand Down
25 changes: 20 additions & 5 deletions app/models/paged_collection.rb
Expand Up @@ -13,16 +13,25 @@ class PagedCollection

def_delegators :request, :params

def initialize(items, request, options={})
def initialize(items, request=nil, options=nil)
self.items = items
self.request = request
self.options = options
self.request = request || request_stub
self.options = options || {}
self.options[:is_root_resource] = true unless (self.options[:is_root_resource] == false)
end

def request_stub
OpenStruct.new(params: {})
end

def is_root_resource
!!self.options[:is_root_resource]
end

def show_curies
is_root_resource && !options[:no_curies]
end

def item_class
options[:item_class] || self.items.first.try(:item_class) || self.items.first.class
end
Expand All @@ -31,8 +40,14 @@ def item_decorator
options[:item_decorator] || "Api::#{item_class.name}Representer".constantize
end

def url_helper
options[:url_helper] #|| "api_#{item_class.name.underscore.pluralize}_path"
# url to use for the self:href, can be a string or proc
def url
options[:url]
end

# If this is an embedded collection, the parent will be set here for use in urls
def parent
options[:parent]
end

end
1 change: 1 addition & 0 deletions app/models/story.rb
Expand Up @@ -12,6 +12,7 @@ class Story < BaseModel
has_many :audio_versions, -> { where(promos: false).includes(:audio_files) }, foreign_key: :piece_id
has_many :audio_files, through: :audio_versions
has_many :producers
has_many :musical_works, -> { order(:position) }, foreign_key: :piece_id

has_one :promos, -> { where(promos: true) }, class_name: 'AudioVersion', foreign_key: :piece_id
has_one :license, foreign_key: :piece_id
Expand Down
4 changes: 2 additions & 2 deletions app/representers/api/account_representer.rb
Expand Up @@ -18,11 +18,11 @@ class Api::AccountRepresenter < Api::BaseRepresenter
link :image do
{
href: polymorphic_path([:api, represented.image]),
title: represented.image.filename,
title: represented.image.filename,
profile: prx_model_uri(represented.image)
} if represented.image
end
embed :image, class: Image, decorator: Api::ImageRepresenter
embed :image, class: Image, decorator: Api::ImageRepresenter, zoom: :always

link :address do
api_address_path(represented.address) if represented.address
Expand Down
39 changes: 29 additions & 10 deletions app/representers/api/paged_collection_representer.rb
Expand Up @@ -7,39 +7,58 @@ class Api::PagedCollectionRepresenter < Api::BaseRepresenter

link :self do
{
href: helper(params),
href: href_url_helper(params),
profile: prx_model_uri(:collection, represented.item_class)
}
end

link :prev do
helper(params.merge(page: represented.prev_page)) unless represented.first_page?
href_url_helper(params.merge(page: represented.prev_page)) unless represented.first_page?
end

link :next do
helper(params.merge(page: represented.next_page)) unless represented.last_page?
href_url_helper(params.merge(page: represented.next_page)) unless represented.last_page?
end

link :first do
helper(params.merge(page: nil))
href_url_helper(params.merge(page: nil))
end

link :last do
helper(params.merge(page: represented.total_pages))
href_url_helper(params.merge(page: represented.total_pages))
end

embeds :items, decorator: lambda{|*| item_decorator }, class: lambda{|*| item_class }
embeds :items, decorator: lambda{|*| item_decorator }, class: lambda{|*| item_class }, zoom: :always

def params
represented.params
end

def helper(options={})
url_helper ? self.send(url_helper, options) : url_for(options.merge(only_path: true))
# refactor to use single property, :url, that can be a method name, a string, or a lambda
# if it is a method name, execute against self - the representer - which has local url helpers methods
# if it is a sym/string, but self does not respond to it, then just use that string
# if it is a lambda, execute in the context against the represented.parent (if there is one) or represented
def href_url_helper(options={})

if represented_url.nil?
# Rails.logger.debug("href_url_helper: #{options.inspect}, ")
result = url_for(options.merge(only_path: true)) rescue nil
result ||= polymorphic_path([:api, represented.parent, represented.item_class], options) if represented.parent
result ||= polymorphic_path([:api, represented.item_class], options)
return result
end

if represented_url.respond_to?(:call)
self.instance_exec(options, &represented_url)
elsif self.respond_to?(represented_url)
self.send(represented_url, options)
else
represented_url.to_s
end
end

def url_helper
represented.try(:url_helper)
def represented_url
@represented_url ||= represented.try(:url)
end

end
30 changes: 18 additions & 12 deletions app/representers/api/story_representer.rb
Expand Up @@ -18,45 +18,51 @@ def timing_and_cues
represented.default_audio_version.try(:timing_and_cues)
end

# default zoom
link :account do
{
href: api_account_path(represented.account),
title: represented.account.name,
profile: prx_model_uri(represented.account)
}
end
embed :account, class: Account, decorator: Api::AccountRepresenter
embed :account, class: Account, decorator: Api::AccountRepresenter, zoom: true

link :image do
api_story_image_path(represented.default_image.id) if represented.default_image
end
embed :default_image, as: :image, class: StoryImage, decorator: Api::ImageRepresenter
embed :default_image, as: :image, class: StoryImage, decorator: Api::ImageRepresenter, zoom: :always

link :series do
{ href: api_series_path(represented.series), title: represented.series.title } if represented.series_id
end
embed :series, class: Series, decorator: Api::SeriesRepresenter

link :'prx:license' do
api_license_path(represented.license.id) if represented.license
end

link :musical_works do
api_story_musical_works_path(represented)
end
embed :series, class: Series, decorator: Api::SeriesRepresenter, zoom: true

links :audio do
represented.default_audio.collect{ |a| { href: api_audio_file_path(a), title: a.label } }
end
embeds :default_audio, as: :audio, class: AudioFile, decorator: Api::AudioFileRepresenter
embeds :default_audio, as: :audio, class: AudioFile, decorator: Api::AudioFileRepresenter, zoom: :always

# default links
link :'prx:license' do
api_license_path(represented.license.id) if represented.license
end
embed :license, as: :'prx:license', class: License, decorator: Api::LicenseRepresenter

links :audio_versions do
represented.audio_versions.collect{ |a| { href: api_audio_version_path(a), title: a.label } }
end
embeds :audio_versions, class: AudioVersion, decorator: Api::AudioVersionRepresenter

links :images do
represented.images.collect{ |a| { href: api_story_image_path(a) } } unless represented.image_ids.size > 0
end
embeds :images, class: StoryImage, decorator: Api::ImageRepresenter

link :musical_works do
api_story_musical_works_path(represented)
end
embed :musical_works, paged: true, item_class: MusicalWork

end

Expand Down
2 changes: 1 addition & 1 deletion app/representers/api/user_representer.rb
Expand Up @@ -14,6 +14,6 @@ class Api::UserRepresenter < Api::BaseRepresenter
link :image do
api_user_image_path(represented.image) if represented.image
end
embed :image, class: Image, decorator: Api::ImageRepresenter
embed :image, class: Image, decorator: Api::ImageRepresenter, zoom: :always

end
25 changes: 25 additions & 0 deletions app/representers/concerns/caches.rb
@@ -0,0 +1,25 @@
# encoding: utf-8

require 'active_support/concern'

# expects underlying model to have filename, class, and id attributes
module Caches
extend ActiveSupport::Concern

included do
include Resources
# TODO - check to make sure this is not included mutliple times.
Representable::Mapper.send(:include, Caches::Resources)
end

module Resources

def serialize(doc, options)
# Rails.logger.debug("serialize:\n - doc: #{doc.inspect}\n - options: #{options.inspect}\n - self:#{self.inspect}\n\n")
# Rails.logger.debug("serialize:\n - represented: #{represented.cache_key}\n - options: #{options.inspect}\n\n")
super(doc, options)
end

end

end
18 changes: 18 additions & 0 deletions test/controllers/api/base_controller_test.rb
Expand Up @@ -7,4 +7,22 @@
assert_response :success
end

it "should return missing for record not found" do
raises_exception = ->(version) { raise(ActiveRecord::RecordNotFound.new(version)) }
Api.stub :version, raises_exception do
get(:entrypoint, { api_version: 'v1', format: 'json' } )
assert_response :missing
end
end

it "determines show action options for roar" do
@controller.class.resource_representer = "rr"
@controller.show_options.must_equal({zoom_param: nil, represent_with: "rr"})
end

it "can parse a zoom parameter" do
@controller.params[:zoom] = "a,test"
@controller.zoom_param.must_equal ['a', 'test']
end

end
26 changes: 22 additions & 4 deletions test/models/paged_collection_test.rb
Expand Up @@ -4,7 +4,7 @@

describe PagedCollection do

let(:items) { (0..25).collect{|t| TestObject.new("test #{t}") } }
let(:items) { (0..25).collect{|t| TestObject.new("test #{t}", true) } }
let(:paged_items) { Kaminari.paginate_array(items).page(1).per(10) }
let(:paged_collection) { PagedCollection.new(paged_items, OpenStruct.new(params: {})) }

Expand All @@ -24,10 +24,18 @@
paged_collection.total.must_equal 26
end

it 'can be a root resource from options' do
it 'has a stubbed request by default' do
paged_collection = PagedCollection.new([])
paged_collection.params.must_equal Hash.new
end

it 'will be a root resource be default' do
paged_collection.is_root_resource.must_equal true
end

it 'will be a root resource based on options' do
paged_collection.options[:is_root_resource] = false
paged_collection.is_root_resource.must_equal false
paged_collection.options[:is_root_resource] = true
paged_collection.is_root_resource.must_equal true
end

it 'has an item_class' do
Expand All @@ -38,4 +46,14 @@
paged_collection.item_decorator.must_equal(Api::TestObjectRepresenter)
end

it 'has an url' do
paged_collection.options[:url] = "test"
paged_collection.url.must_equal "test"
end

it 'has a parent' do
paged_collection.options[:parent] = "test"
paged_collection.parent.must_equal "test"
end

end

0 comments on commit 56e8c66

Please sign in to comment.