Skip to content

Commit

Permalink
Start adding support for object conversion / serialization in RP 2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Sutto committed Dec 15, 2013
1 parent 7f69ab0 commit 1c0c00c
Show file tree
Hide file tree
Showing 15 changed files with 519 additions and 0 deletions.
2 changes: 2 additions & 0 deletions lib/rocket_pants.rb
Expand Up @@ -14,8 +14,10 @@ module RocketPants

# Extra parts of RocketPants.
autoload :Base, 'rocket_pants/base'
autoload :API, 'rocket_pants/base'
autoload :Cacheable, 'rocket_pants/cacheable'
autoload :CacheMiddleware, 'rocket_pants/cache_middleware'
autoload :Converters, 'rocket_pants/converters'

# Helpers for various testing frameworks.
autoload :TestHelper, 'rocket_pants/test_helper'
Expand Down
30 changes: 30 additions & 0 deletions lib/rocket_pants/converters.rb
@@ -0,0 +1,30 @@
module RocketPants
module Converters

require 'rocket_pants/converters/base'
require 'rocket_pants/converters/serializable_object'
require 'rocket_pants/converters/collection'
require 'rocket_pants/converters/paginated'
require 'rocket_pants/converters/kaminari'
require 'rocket_pants/converters/will_paginate'

PAGINATED = [WillPaginate, Kaminari]
COLLECTION = [*PAGINATED, Collection]
INDIVIDUAL = [SerializableObject, Base]

ALL = [*COLLECTION, *INDIVIDUAL]

def self.fetch(object, options)
converter = ALL.detect { |k| k.converts? object, options }
raise "No Converter found for #{object.inspect}" unless converter
converter.new object, options
end

def self.serialize_single(object, options)
converter = INDIVIDUAL.detect { |k| k.converts? object, options }
raise "No Converter found for #{object.inspect}" unless converter
converter.new(object, options).convert
end

end
end
22 changes: 22 additions & 0 deletions lib/rocket_pants/converters/ams.rb
@@ -0,0 +1,22 @@
module RocketPants
module Converters
class AMS < Base

def self.support?(object, options)
options[:serializer].present? || object.respond_to?(:active_model_serializer)
end

def convert(object, options)
serializer = serializer_for object, options
serializer.new(object, options.merge(root: false)).serializable_hash
end

private

def serializer_for(object, options)
options[:serializer] || object.active_model_serializer
end

end
end
end
34 changes: 34 additions & 0 deletions lib/rocket_pants/converters/base.rb
@@ -0,0 +1,34 @@
module RocketPants
module Converters
class Base

def self.converts?(object, options)
true
end

attr_reader :object, :options

def initialize(object, options)
@object = object
@options = options
end

def convert
object
end

def metadata
{}
end

# We default the nested key to be just response for the moment.
# This will be changed to something else to make it compatible with Ember
# and the like before the release of 2.0
# TODO: Finish this off.
def response_key
options.fetch :root, 'response'
end

end
end
end
27 changes: 27 additions & 0 deletions lib/rocket_pants/converters/collection.rb
@@ -0,0 +1,27 @@
module RocketPants
module Converters
class Collection < Base

alias collection object

def self.converts?(object, options)
object.is_a?(Array) || object.respond_to?(:to_ary)
end

def convert
collection.map { |object| serialize_associated(object, options) }
end

def metadata
super.merge! count: collection.size
end

private

def serialize_associated(associated, options)
Converters.serialize_single associated, options
end

end
end
end
23 changes: 23 additions & 0 deletions lib/rocket_pants/converters/kaminari.rb
@@ -0,0 +1,23 @@
module RocketPants
module Converters
class Kaminari < Paginated

def self.converts?(object, options)
object.respond_to?(:num_pages) && object.respond_to?(:current_page)
end

def pagination
current, total, per_page = collection.current_page, collection.num_pages, collection.limit_value
{
current: current,
previous: (current > 1 ? (current - 1) : nil),
next: (current == total ? nil : (current + 1)),
per_page: per_page,
pages: total,
count: collection.total_count
}
end

end
end
end
21 changes: 21 additions & 0 deletions lib/rocket_pants/converters/paginated.rb
@@ -0,0 +1,21 @@
module RocketPants
module Converters
class Paginated < Collection

def self.converts?(object, options)
raise NotImplementedError.new("This must be implemented in your specified paginated converter")
end

def metadata
super.tap do |metadata|
metadata[:pagination] = pagination
end
end

def pagination
raise NotImplementedError.new("This must be implemented in your specified paginated converter.")
end

end
end
end
20 changes: 20 additions & 0 deletions lib/rocket_pants/converters/serializable_object.rb
@@ -0,0 +1,20 @@
module RocketPants
module Converters
class SerializableObject < Base

METHODS = [:serializable_hash, :serializable_object]

def self.converts?(object, options)
METHODS.any? { |m| object.respond_to?(m) }
end

attr_reader :object, :options

def convert
method = METHODS.detect { |m| object.respond_to?(m) }
object.send method, options
end

end
end
end
22 changes: 22 additions & 0 deletions lib/rocket_pants/converters/will_paginate.rb
@@ -0,0 +1,22 @@
module RocketPants
module Converters
class WillPaginate < Paginated

def self.converts?(object, options)
object.respond_to?(:total_entries)
end

def pagination
{
previous: collection.previous_page.try(:to_i),
next: collection.next_page.try(:to_i),
current: collection.current_page.try(:to_i),
per_page: collection.per_page.try(:to_i),
count: collection.total_entries.try(:to_i),
pages: collection.total_pages.try(:to_i)
}
end

end
end
end
Empty file.
26 changes: 26 additions & 0 deletions spec/rocket_pants/converters/base_spec.rb
@@ -0,0 +1,26 @@
require 'spec_helper'

describe RocketPants::Converters::Base do

it 'should always support items' do
described_class.should be_converts Object.new, {}
described_class.should be_converts 1, {}
described_class.should be_converts "Hello World", {}
end

it 'should pass through the item' do
object = Object.new
described_class.new(object, {}).convert.should == object
end

it 'should have no metadata' do
object = Object.new
described_class.new(object, {x: 1}).metadata.should == {}
end

it 'should have the default response key' do
object = Object.new
described_class.new(object, {x: 1}).response_key.should == 'response'
end

end
75 changes: 75 additions & 0 deletions spec/rocket_pants/converters/collection_spec.rb
@@ -0,0 +1,75 @@
require 'spec_helper'

describe RocketPants::Converters::Collection do

use_reversible_tables :users, scope: :all

let!(:user) { User.create! age: 23 }

it 'should have the correct hierarchy' do
described_class.should be < RocketPants::Converters::Base
end

context 'detection' do

subject { described_class }
let(:options) { {} }

it 'should detect arrays' do
subject.should be_converts User.all.to_a, options
end

it 'should detect activerecord relations' do
subject.should be_converts User.where('id IS NOT NULL'), options
end

it 'should detect items implementing .to_ary' do
item = Object.new
def item.to_ary; []; end
subject.should be_converts item, options
end

it 'should not detect hashes' do
subject.should_not be_converts({x: 1, y: 2}, options)
end

it 'should not detect individual records' do
subject.should_not be_converts User.first!, options
end

it 'should not detect other objects' do
subject.should_not be_converts 1, options
subject.should_not be_converts Object.new, options
subject.should_not be_converts true, options
subject.should_not be_converts false, options
end

end

it 'should serialize the individual options' do
result = Object.new
mock(RocketPants::Converters).serialize_single(user, anything) { result }
converted = described_class.new([user], {}).convert
converted.should be_a Array
converted.should == [result]
end

it 'should work with an empty array' do
described_class.new([], {}).convert.should == []
end

it 'should pass the options to the children' do
result = Object.new
mock(RocketPants::Converters).serialize_single(user, {compact: true}) { result }
converted = described_class.new([user], {compact: true}).convert
converted.should be_a Array
converted.should == [result]
end

it 'should include count in metadata' do
described_class.new([], {}).metadata.should include count: 0
described_class.new([1], {}).metadata.should include count: 1
described_class.new([10, 11, 12], {}).metadata.should include count: 3
end

end

0 comments on commit 1c0c00c

Please sign in to comment.