restpack_serializer 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +87 -0
- data/Guardfile +5 -0
- data/LICENSE +20 -0
- data/README.md +300 -0
- data/Rakefile +50 -0
- data/lib/restpack_serializer/factory.rb +16 -0
- data/lib/restpack_serializer/options.rb +48 -0
- data/lib/restpack_serializer/serializable/attributes.rb +43 -0
- data/lib/restpack_serializer/serializable/paging.rb +54 -0
- data/lib/restpack_serializer/serializable/resource.rb +9 -0
- data/lib/restpack_serializer/serializable/side_loading.rb +115 -0
- data/lib/restpack_serializer/serializable.rb +69 -0
- data/lib/restpack_serializer/version.rb +5 -0
- data/lib/restpack_serializer.rb +6 -0
- data/restpack_serializer.gemspec +32 -0
- data/spec/factory/factory_spec.rb +28 -0
- data/spec/fixtures/db.rb +68 -0
- data/spec/fixtures/serializers.rb +17 -0
- data/spec/serializable/attributes_spec.rb +27 -0
- data/spec/serializable/options_spec.rb +65 -0
- data/spec/serializable/paging_spec.rb +184 -0
- data/spec/serializable/resource_spec.rb +40 -0
- data/spec/serializable/serializer_spec.rb +102 -0
- data/spec/serializable/side_loading/belongs_to_spec.rb +72 -0
- data/spec/serializable/side_loading/has_many_spec.rb +43 -0
- data/spec/serializable/side_loading/side_loading_spec.rb +82 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/factory.rb +45 -0
- metadata +266 -0
@@ -0,0 +1,115 @@
|
|
1
|
+
module RestPack::Serializer::SideLoading
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
def side_loads(models, options)
|
6
|
+
side_loads = {
|
7
|
+
:meta => { }
|
8
|
+
}
|
9
|
+
return side_loads if models.empty? || options.includes.nil?
|
10
|
+
|
11
|
+
options.includes.each do |include|
|
12
|
+
side_load_data = side_load(include, models, options)
|
13
|
+
side_loads[:meta].merge!(side_load_data[:meta] || {})
|
14
|
+
side_loads.merge! side_load_data.except(:meta)
|
15
|
+
end
|
16
|
+
side_loads
|
17
|
+
end
|
18
|
+
|
19
|
+
def filterable_by
|
20
|
+
filters = [self.model_class.primary_key.to_sym]
|
21
|
+
filters += self.model_class.reflect_on_all_associations(:belongs_to).map(&:foreign_key).map(&:to_sym)
|
22
|
+
filters.uniq
|
23
|
+
end
|
24
|
+
|
25
|
+
def can_includes
|
26
|
+
@can_includes || []
|
27
|
+
end
|
28
|
+
|
29
|
+
def can_include(*includes)
|
30
|
+
@can_includes ||= []
|
31
|
+
@can_includes += includes
|
32
|
+
end
|
33
|
+
|
34
|
+
def links
|
35
|
+
links = {}
|
36
|
+
|
37
|
+
associations.each do |association|
|
38
|
+
if association.macro == :belongs_to
|
39
|
+
href = "/#{association.plural_name}/{#{self.key}.#{association.name}}.json"
|
40
|
+
elsif association.macro == :has_many
|
41
|
+
singular_key = self.key.to_s.singularize
|
42
|
+
href = "/#{association.plural_name}.json?#{singular_key}_id={#{key}.id}"
|
43
|
+
end
|
44
|
+
|
45
|
+
links["#{self.key}.#{association.plural_name}"] = {
|
46
|
+
:href => href,
|
47
|
+
:type => association.plural_name.to_sym
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
links
|
52
|
+
end
|
53
|
+
|
54
|
+
def associations
|
55
|
+
associations = []
|
56
|
+
can_includes.each do |include|
|
57
|
+
association = association_from_include(include)
|
58
|
+
associations << association if supported_association?(association)
|
59
|
+
end
|
60
|
+
associations
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def side_load(include, models, options)
|
66
|
+
association = association_from_include(include)
|
67
|
+
|
68
|
+
if supported_association?(association)
|
69
|
+
serializer = RestPack::Serializer::Factory.create(association.class_name)
|
70
|
+
return send("side_load_#{association.macro}", association, models, serializer)
|
71
|
+
else
|
72
|
+
return {}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def supported_association?(association)
|
77
|
+
[:belongs_to, :has_many].include?(association.macro)
|
78
|
+
end
|
79
|
+
|
80
|
+
def side_load_belongs_to(association, models, serializer)
|
81
|
+
foreign_keys = models.map { |model| model.send(association.foreign_key) }.uniq
|
82
|
+
side_load = association.klass.find(foreign_keys)
|
83
|
+
|
84
|
+
return {
|
85
|
+
association.plural_name.to_sym => side_load.map { |model| serializer.as_json(model) },
|
86
|
+
:meta => { }
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
def side_load_has_many(association, models, serializer)
|
91
|
+
return {} if models.empty?
|
92
|
+
options = RestPack::Serializer::Options.new(serializer.class.model_class)
|
93
|
+
options.filters = { association.foreign_key.to_sym => models.map(&:id) }
|
94
|
+
options.include_links = false
|
95
|
+
return serializer.class.page_with_options(options)
|
96
|
+
end
|
97
|
+
|
98
|
+
def association_from_include(include)
|
99
|
+
raise_invalid_include(include) unless self.can_includes.include?(include)
|
100
|
+
|
101
|
+
possible_relations = [include.to_s.singularize.to_sym, include]
|
102
|
+
possible_relations.each do |relation|
|
103
|
+
association = self.model_class.reflect_on_association(relation)
|
104
|
+
return association unless association.nil?
|
105
|
+
end
|
106
|
+
|
107
|
+
raise_invalid_include(include)
|
108
|
+
end
|
109
|
+
|
110
|
+
def raise_invalid_include(include)
|
111
|
+
raise RestPack::Serializer::InvalidInclude.new,
|
112
|
+
":#{include} is not a valid include for #{self.model_class}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require_relative "options"
|
3
|
+
require_relative "serializable/attributes"
|
4
|
+
require_relative "serializable/paging"
|
5
|
+
require_relative "serializable/resource"
|
6
|
+
require_relative "serializable/side_loading"
|
7
|
+
|
8
|
+
module RestPack
|
9
|
+
module Serializer
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
include RestPack::Serializer::Paging
|
13
|
+
include RestPack::Serializer::Resource
|
14
|
+
include RestPack::Serializer::Attributes
|
15
|
+
include RestPack::Serializer::SideLoading
|
16
|
+
|
17
|
+
class InvalidInclude < Exception; end
|
18
|
+
|
19
|
+
def as_json(model, options = {})
|
20
|
+
@model, @options = model, options
|
21
|
+
|
22
|
+
data = {}
|
23
|
+
if self.class.serializable_attributes.present?
|
24
|
+
self.class.serializable_attributes.each do |key, name|
|
25
|
+
data[key] = self.send(name) if include_attribute?(name)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
add_links(model, data)
|
30
|
+
|
31
|
+
data
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def add_links(model, data)
|
37
|
+
self.class.associations.each do |association|
|
38
|
+
if association.macro == :belongs_to
|
39
|
+
data[:links] ||= {}
|
40
|
+
data[:links][association.name.to_sym] = model.send(association.foreign_key).to_s
|
41
|
+
end
|
42
|
+
end
|
43
|
+
data
|
44
|
+
end
|
45
|
+
|
46
|
+
def include_attribute?(name)
|
47
|
+
self.send("include_#{name}?".to_sym)
|
48
|
+
end
|
49
|
+
|
50
|
+
module ClassMethods
|
51
|
+
def as_json(model, options = {})
|
52
|
+
new.as_json(model, options)
|
53
|
+
end
|
54
|
+
|
55
|
+
def model_name
|
56
|
+
self.name.chomp('Serializer')
|
57
|
+
end
|
58
|
+
|
59
|
+
def model_class
|
60
|
+
model_name.constantize
|
61
|
+
end
|
62
|
+
|
63
|
+
def key
|
64
|
+
self.model_class.send(:table_name).to_sym
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'restpack_serializer/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "restpack_serializer"
|
8
|
+
gem.version = RestPack::Serializer::VERSION
|
9
|
+
gem.authors = ["Gavin Joyce"]
|
10
|
+
gem.email = ["gavinjoyce@gmail.com"]
|
11
|
+
gem.description = %q{Model serialization, paging, side-loading and filtering}
|
12
|
+
gem.summary = %q{Model serialization, paging, side-loading and filtering}
|
13
|
+
gem.homepage = "https://github.com/RestPack"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency 'activerecord', '>= 3.0'
|
21
|
+
gem.add_dependency 'activesupport', '>= 3.0'
|
22
|
+
gem.add_dependency 'will_paginate', '~> 3.0'
|
23
|
+
|
24
|
+
gem.add_development_dependency 'rake', '~> 10.0.3'
|
25
|
+
gem.add_development_dependency 'rspec', '~> 2.12'
|
26
|
+
gem.add_development_dependency 'guard-rspec', '~> 2.5.4'
|
27
|
+
gem.add_development_dependency 'growl', '~> 1.0.3'
|
28
|
+
gem.add_development_dependency 'factory_girl', '~> 4.2.0'
|
29
|
+
gem.add_development_dependency 'sqlite3', '~> 1.3.7'
|
30
|
+
gem.add_development_dependency 'database_cleaner', '~> 0.9.1'
|
31
|
+
gem.add_development_dependency 'bump'
|
32
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require './spec/spec_helper'
|
2
|
+
|
3
|
+
describe RestPack::Serializer::Factory do
|
4
|
+
let(:factory) { RestPack::Serializer::Factory }
|
5
|
+
|
6
|
+
it "creates by string" do
|
7
|
+
factory.create("Song").should be_an_instance_of(SongSerializer)
|
8
|
+
end
|
9
|
+
it "creates by lowercase string" do
|
10
|
+
factory.create("song").should be_an_instance_of(SongSerializer)
|
11
|
+
end
|
12
|
+
it "creates by lowercase plural string" do
|
13
|
+
factory.create("songs").should be_an_instance_of(SongSerializer)
|
14
|
+
end
|
15
|
+
it "creates by symbol" do
|
16
|
+
factory.create(:song).should be_an_instance_of(SongSerializer)
|
17
|
+
end
|
18
|
+
it "creates by class" do
|
19
|
+
factory.create(Song).should be_an_instance_of(SongSerializer)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "creates multiple with Array" do
|
23
|
+
serializers = factory.create("Song", "artists", :album)
|
24
|
+
serializers[0].should be_an_instance_of(SongSerializer)
|
25
|
+
serializers[1].should be_an_instance_of(ArtistSerializer)
|
26
|
+
serializers[2].should be_an_instance_of(AlbumSerializer)
|
27
|
+
end
|
28
|
+
end
|
data/spec/fixtures/db.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'sqlite3'
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
ActiveRecord::Base.establish_connection(
|
5
|
+
:adapter => 'sqlite3',
|
6
|
+
:database => 'test.db'
|
7
|
+
)
|
8
|
+
|
9
|
+
ActiveRecord::Schema.define(:version => 1) do
|
10
|
+
create_table "artists", :force => true do |t|
|
11
|
+
t.string "name"
|
12
|
+
t.string "website"
|
13
|
+
t.datetime "created_at"
|
14
|
+
t.datetime "updated_at"
|
15
|
+
end
|
16
|
+
|
17
|
+
create_table "albums", :force => true do |t|
|
18
|
+
t.string "title"
|
19
|
+
t.integer "year"
|
20
|
+
t.integer "artist_id"
|
21
|
+
t.datetime "created_at"
|
22
|
+
t.datetime "updated_at"
|
23
|
+
end
|
24
|
+
|
25
|
+
create_table "songs", :force => true do |t|
|
26
|
+
t.string "title"
|
27
|
+
t.integer "album_id"
|
28
|
+
t.integer "artist_id"
|
29
|
+
t.datetime "created_at"
|
30
|
+
t.datetime "updated_at"
|
31
|
+
end
|
32
|
+
|
33
|
+
create_table "payments", :force => true do |t|
|
34
|
+
t.integer "amount"
|
35
|
+
t.integer "artist_id"
|
36
|
+
t.datetime "created_at"
|
37
|
+
t.datetime "updated_at"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Artist < ActiveRecord::Base
|
42
|
+
attr_accessible :name, :website
|
43
|
+
|
44
|
+
has_many :albums
|
45
|
+
has_many :songs
|
46
|
+
has_many :payments
|
47
|
+
end
|
48
|
+
|
49
|
+
class Album < ActiveRecord::Base
|
50
|
+
attr_accessible :title, :year, :artist
|
51
|
+
scope :classic, where("year < 1950")
|
52
|
+
|
53
|
+
belongs_to :artist
|
54
|
+
has_many :songs
|
55
|
+
end
|
56
|
+
|
57
|
+
class Song < ActiveRecord::Base
|
58
|
+
attr_accessible :title, :artist, :album
|
59
|
+
|
60
|
+
belongs_to :artist
|
61
|
+
belongs_to :album
|
62
|
+
end
|
63
|
+
|
64
|
+
class Payment < ActiveRecord::Base
|
65
|
+
attr_accessible :amount, :artist
|
66
|
+
|
67
|
+
belongs_to :artist
|
68
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class SongSerializer
|
2
|
+
include RestPack::Serializer
|
3
|
+
attributes :id, :title, :album_id
|
4
|
+
can_include :albums, :artists
|
5
|
+
end
|
6
|
+
|
7
|
+
class AlbumSerializer
|
8
|
+
include RestPack::Serializer
|
9
|
+
attributes :id, :title, :year, :artist_id
|
10
|
+
can_include :artists, :songs
|
11
|
+
end
|
12
|
+
|
13
|
+
class ArtistSerializer
|
14
|
+
include RestPack::Serializer
|
15
|
+
attributes :id, :name, :website
|
16
|
+
can_include :albums, :songs
|
17
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require './spec/spec_helper'
|
2
|
+
|
3
|
+
describe RestPack::Serializer::Attributes do
|
4
|
+
class CustomSerializer
|
5
|
+
include RestPack::Serializer
|
6
|
+
attributes :a, :b, :c
|
7
|
+
attribute :old_attribute, :key => :new_key
|
8
|
+
end
|
9
|
+
|
10
|
+
before do
|
11
|
+
@attributes = CustomSerializer.serializable_attributes
|
12
|
+
end
|
13
|
+
|
14
|
+
it "correctly models specified attributes" do
|
15
|
+
@attributes.length.should == 4
|
16
|
+
end
|
17
|
+
|
18
|
+
it "correctly maps normal attributes" do
|
19
|
+
[:a, :b, :c].each do |attr|
|
20
|
+
@attributes[attr].should == attr
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it "correctly maps attribute with :key options" do
|
25
|
+
@attributes[:new_key].should == :old_attribute
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require './spec/spec_helper'
|
2
|
+
|
3
|
+
describe RestPack::Serializer::Options do
|
4
|
+
let(:subject) { RestPack::Serializer::Options.new(Song, params, scope) }
|
5
|
+
let(:params) { {} }
|
6
|
+
let(:scope) { nil }
|
7
|
+
|
8
|
+
describe 'default values' do
|
9
|
+
it { subject.model_class.should == Song }
|
10
|
+
it { subject.includes.should == [] }
|
11
|
+
it { subject.page.should == 1 }
|
12
|
+
it { subject.page_size.should == 10 }
|
13
|
+
it { subject.filters.should == {} }
|
14
|
+
it { subject.scope.should == Song.scoped }
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'with paging params' do
|
18
|
+
let(:params) { { 'page' => '2', 'page_size' => '8' } }
|
19
|
+
it { subject.page.should == 2 }
|
20
|
+
it { subject.page_size.should == 8 }
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'with includes' do
|
24
|
+
let(:params) { { 'includes' => 'model1,model2' } }
|
25
|
+
it { subject.includes.should == [:model1, :model2] }
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'with filters' do
|
29
|
+
describe 'with no filter params' do
|
30
|
+
let(:params) { { } }
|
31
|
+
it { subject.filters.should == {} }
|
32
|
+
end
|
33
|
+
describe 'with a primary key with a single value' do
|
34
|
+
let(:params) { { 'id' => '142857' } }
|
35
|
+
it { subject.filters.should == { id: ['142857'] } }
|
36
|
+
end
|
37
|
+
describe 'with a primary key with multiple values' do
|
38
|
+
let(:params) { { 'ids' => '42,142857' } }
|
39
|
+
it { subject.filters.should == { id: ['42', '142857'] } }
|
40
|
+
end
|
41
|
+
describe 'with a foreign key with a single value' do
|
42
|
+
let(:params) { { 'album_id' => '789' } }
|
43
|
+
it { subject.filters.should == { album_id: ['789'] } }
|
44
|
+
end
|
45
|
+
describe 'with a foreign key with multiple values' do
|
46
|
+
let(:params) { { 'album_id' => '789,678,567' } }
|
47
|
+
it { subject.filters.should == { album_id: ['789', '678', '567'] } }
|
48
|
+
end
|
49
|
+
describe 'with multiple foreign keys' do
|
50
|
+
let(:params) { { 'album_id' => '111,222', 'artist_id' => '888,999' } }
|
51
|
+
it { subject.filters.should == { album_id: ['111', '222'], artist_id: ['888', '999'] } }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'scopes' do
|
56
|
+
describe 'with default scope' do
|
57
|
+
it { subject.scope.should == Song.scoped }
|
58
|
+
end
|
59
|
+
|
60
|
+
describe 'with custom scope' do
|
61
|
+
let(:scope) { Song.where("id >= 100") }
|
62
|
+
it { subject.scope.should == scope }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require './spec/spec_helper'
|
2
|
+
|
3
|
+
describe RestPack::Serializer::Paging do
|
4
|
+
before(:each) do
|
5
|
+
@album1 = FactoryGirl.create(:album_with_songs, song_count: 11)
|
6
|
+
@album2 = FactoryGirl.create(:album_with_songs, song_count: 7)
|
7
|
+
end
|
8
|
+
|
9
|
+
context "#page" do
|
10
|
+
let(:page) { SongSerializer.page(params) }
|
11
|
+
let(:params) { { } }
|
12
|
+
|
13
|
+
context "with defaults" do
|
14
|
+
it "page defaults to 1" do
|
15
|
+
page[:meta][:songs][:page].should == 1
|
16
|
+
end
|
17
|
+
it "page_size defaults to 10" do
|
18
|
+
page[:meta][:songs][:page_size].should == 10
|
19
|
+
end
|
20
|
+
it "includes valid paging meta data" do
|
21
|
+
page[:meta][:songs][:count].should == 18
|
22
|
+
page[:meta][:songs][:page_count].should == 2
|
23
|
+
page[:meta][:songs][:previous_page].should == nil
|
24
|
+
page[:meta][:songs][:next_page].should == 2
|
25
|
+
end
|
26
|
+
it "includes links" do
|
27
|
+
page[:links].should == {
|
28
|
+
'songs.albums' => { :href => "/albums/{songs.album}.json", :type => :albums },
|
29
|
+
'songs.artists' => { :href => "/artists/{songs.artist}.json", :type => :artists }
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "with custom page size" do
|
35
|
+
let(:params) { { page_size: '3' } }
|
36
|
+
it "returns custom page sizes" do
|
37
|
+
page[:meta][:songs][:page_size].should == 3
|
38
|
+
page[:meta][:songs][:page_count].should == 6
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it "serializes results" do
|
43
|
+
first = Song.first
|
44
|
+
page[:songs].first.should == {
|
45
|
+
id: first.id.to_s,
|
46
|
+
title: first.title,
|
47
|
+
album_id: first.album_id,
|
48
|
+
links: {
|
49
|
+
album: first.album_id.to_s,
|
50
|
+
artist: first.artist_id.to_s
|
51
|
+
}
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
context "first page" do
|
56
|
+
let(:params) { { page: '1' } }
|
57
|
+
|
58
|
+
it "returns first page" do
|
59
|
+
page[:meta][:songs][:page].should == 1
|
60
|
+
page[:meta][:songs][:page_size].should == 10
|
61
|
+
page[:meta][:songs][:previous_page].should == nil
|
62
|
+
page[:meta][:songs][:next_page].should == 2
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "second page" do
|
67
|
+
let(:params) { { page: '2' } }
|
68
|
+
|
69
|
+
it "returns second page" do
|
70
|
+
page[:songs].length.should == 8
|
71
|
+
page[:meta][:songs][:page].should == 2
|
72
|
+
page[:meta][:songs][:previous_page].should == 1
|
73
|
+
page[:meta][:songs][:next_page].should == nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "when sideloading" do
|
78
|
+
let(:params) { { includes: 'albums' } }
|
79
|
+
|
80
|
+
it "includes side-loaded models" do
|
81
|
+
page[:albums].should_not == nil
|
82
|
+
end
|
83
|
+
|
84
|
+
it "includes the side-loads in the main meta data" do
|
85
|
+
page[:meta][:songs][:includes].should == [:albums]
|
86
|
+
end
|
87
|
+
|
88
|
+
context "with includes as comma delimited string" do
|
89
|
+
let(:params) { { includes: "albums,artists" } }
|
90
|
+
it "includes side-loaded models" do
|
91
|
+
page[:albums].should_not == nil
|
92
|
+
page[:artists].should_not == nil
|
93
|
+
end
|
94
|
+
|
95
|
+
it "includes links" do
|
96
|
+
page[:links]['songs.albums'].should_not == nil
|
97
|
+
page[:links]['songs.artists'].should_not == nil
|
98
|
+
page[:links]['albums.songs'].should_not == nil
|
99
|
+
page[:links]['albums.artists'].should_not == nil
|
100
|
+
page[:links]['artists.songs'].should_not == nil
|
101
|
+
page[:links]['artists.albums'].should_not == nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context "when filtering" do
|
107
|
+
context "with no filters" do
|
108
|
+
let(:params) { {} }
|
109
|
+
|
110
|
+
it "returns a page of all data" do
|
111
|
+
page[:meta][:songs][:count].should == 18
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context "with :album_id filter" do
|
116
|
+
let(:params) { { album_id: @album1.id.to_s } }
|
117
|
+
|
118
|
+
it "returns a page with songs from album1" do
|
119
|
+
page[:meta][:songs][:count].should == @album1.songs.length
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context "with custom scope" do
|
125
|
+
before do
|
126
|
+
FactoryGirl.create(:album, year: 1930)
|
127
|
+
FactoryGirl.create(:album, year: 1948)
|
128
|
+
end
|
129
|
+
let(:page) { AlbumSerializer.page(params, scope) }
|
130
|
+
let(:scope) { Album.classic }
|
131
|
+
|
132
|
+
it "returns a page of scoped data" do
|
133
|
+
page[:meta][:albums][:count].should == 2
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context "#page_with_options" do
|
139
|
+
let(:page) { SongSerializer.page_with_options(options) }
|
140
|
+
let(:params) { {} }
|
141
|
+
let(:options) { RestPack::Serializer::Options.new(Song, params) }
|
142
|
+
|
143
|
+
context "with defaults" do
|
144
|
+
it "includes valid paging meta data" do
|
145
|
+
page[:meta][:songs][:count].should == 18
|
146
|
+
page[:meta][:songs][:page_count].should == 2
|
147
|
+
page[:meta][:songs][:previous_page].should == nil
|
148
|
+
page[:meta][:songs][:next_page].should == 2
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context "with custom page size" do
|
153
|
+
let(:params) { { page_size: '3' } }
|
154
|
+
it "returns custom page sizes" do
|
155
|
+
page[:meta][:songs][:page_size].should == 3
|
156
|
+
page[:meta][:songs][:page_count].should == 6
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
context "paging with paged side-load" do
|
162
|
+
let(:page) { AlbumSerializer.page_with_options(options) }
|
163
|
+
let(:options) { RestPack::Serializer::Options.new(Album, { includes: 'songs' }) }
|
164
|
+
|
165
|
+
it "includes side-loaded paging data in meta data" do
|
166
|
+
page[:meta][:albums].should_not == nil
|
167
|
+
page[:meta][:albums][:page].should == 1
|
168
|
+
page[:meta][:songs].should_not == nil
|
169
|
+
page[:meta][:songs][:page].should == 1
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context "paging with two paged side-loads" do
|
174
|
+
let(:page) { ArtistSerializer.page_with_options(options) }
|
175
|
+
let(:options) { RestPack::Serializer::Options.new(Artist, { includes: 'albums,songs' }) }
|
176
|
+
|
177
|
+
it "includes side-loaded paging data in meta data" do
|
178
|
+
page[:meta][:albums].should_not == nil
|
179
|
+
page[:meta][:albums][:page].should == 1
|
180
|
+
page[:meta][:songs].should_not == nil
|
181
|
+
page[:meta][:songs][:page].should == 1
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require './spec/spec_helper'
|
2
|
+
|
3
|
+
describe RestPack::Serializer::Resource do
|
4
|
+
before(:each) do
|
5
|
+
@album = FactoryGirl.create(:album_with_songs, song_count: 11)
|
6
|
+
@song = @album.songs.first
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:resource) { SongSerializer.resource(params) }
|
10
|
+
let(:params) { { id: @song.id.to_s } }
|
11
|
+
|
12
|
+
it "returns a resource by id" do
|
13
|
+
resource[:songs].count.should == 1
|
14
|
+
resource[:songs][0][:id].should == @song.id.to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "side-loading" do
|
18
|
+
let(:params) { { id: @song.id.to_s, includes: 'albums' } }
|
19
|
+
|
20
|
+
it "includes side-loaded models" do
|
21
|
+
resource[:albums].count.should == 1
|
22
|
+
resource[:albums].first[:id].should == @song.album.id.to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
it "includes the side-loads in the main meta data" do
|
26
|
+
resource[:meta][:songs][:includes].should == [:albums]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "missing resource" do
|
31
|
+
let(:params) { { id: "-99" } }
|
32
|
+
it "returns no resource" do
|
33
|
+
resource[:songs].length.should == 0
|
34
|
+
end
|
35
|
+
|
36
|
+
#TODO: add specs for jsonapi error format when it has been standardised
|
37
|
+
# https://github.com/RestPack/restpack_serializer/issues/27
|
38
|
+
# https://github.com/json-api/json-api/issues/7
|
39
|
+
end
|
40
|
+
end
|