restpack_serializer 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|