restpack_serializer 0.5.3 → 0.5.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/restpack_serializer/serializable/side_load_data_builder.rb +56 -0
- data/lib/restpack_serializer/serializable/side_loading.rb +40 -73
- data/lib/restpack_serializer/serializable.rb +10 -11
- data/lib/restpack_serializer/version.rb +1 -1
- data/spec/fixtures/db.rb +17 -0
- data/spec/fixtures/serializers.rb +6 -1
- data/spec/serializable/serializer_spec.rb +18 -5
- data/spec/serializable/side_loading/has_and_belongs_many_spec.rb +44 -0
- data/spec/support/factory.rb +13 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 59940462e061cdebfa6cdcee2e8614a0c4907fd7
|
4
|
+
data.tar.gz: 6d9baf69729c965e1a45e1a99e6592d30e73552b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e22ce4ca562d1a086d63268a735a9b37ce77e80995cee9fdfd61b220fa3810011f8ab89a202e6b1e1603afe051dba6380cd2ee75eb22b2ff947816eb7d69105d
|
7
|
+
data.tar.gz: 4a14b79434d7ae0c01e9796183f62547ac90424b4b781d2149d26d623f36e5f3dbfca5baa2586bc69cbbfb5bfd114427cab002a86c1e22604a4b9315b03de563
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module RestPack
|
2
|
+
module Serializer
|
3
|
+
class SideLoadDataBuilder
|
4
|
+
|
5
|
+
def initialize(association, models, serializer)
|
6
|
+
@association = association
|
7
|
+
@models = models
|
8
|
+
@serializer = serializer
|
9
|
+
end
|
10
|
+
|
11
|
+
def side_load_belongs_to
|
12
|
+
foreign_keys = @models.map { |model| model.send(@association.foreign_key) }.uniq
|
13
|
+
side_load = @association.klass.find(foreign_keys)
|
14
|
+
json_model_data = side_load.map { |model| @serializer.as_json(model) }
|
15
|
+
{ @association.plural_name.to_sym => json_model_data, meta: { } }
|
16
|
+
end
|
17
|
+
|
18
|
+
def side_load_has_many
|
19
|
+
has_association_relation do |options|
|
20
|
+
if join_table = @association.options[:through]
|
21
|
+
options.scope = options.scope.joins(join_table)
|
22
|
+
association_fk = @association.through_reflection.foreign_key.to_sym
|
23
|
+
options.filters = { join_table => { association_fk => model_ids } }
|
24
|
+
else
|
25
|
+
options.filters = { @association.foreign_key.to_sym => model_ids }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def side_load_has_and_belongs_to_many
|
31
|
+
has_association_relation do |options|
|
32
|
+
join_table_name = @association.join_table
|
33
|
+
join_clause = "join #{join_table_name} on #{@association.plural_name}.id = #{join_table_name}.#{@association.class_name.foreign_key}"
|
34
|
+
options.scope = options.scope.joins(join_clause)
|
35
|
+
association_fk = @association.foreign_key.to_sym
|
36
|
+
options.filters = { join_table_name.to_sym => { association_fk => model_ids } }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def model_ids
|
43
|
+
@models.map(&:id)
|
44
|
+
end
|
45
|
+
|
46
|
+
def has_association_relation
|
47
|
+
return {} if @models.empty?
|
48
|
+
serializer_class = @serializer.class
|
49
|
+
options = RestPack::Serializer::Options.new(serializer_class)
|
50
|
+
yield options
|
51
|
+
options.include_links = false
|
52
|
+
serializer_class.page_with_options(options)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -3,17 +3,15 @@ module RestPack::Serializer::SideLoading
|
|
3
3
|
|
4
4
|
module ClassMethods
|
5
5
|
def side_loads(models, options)
|
6
|
-
|
7
|
-
|
8
|
-
}
|
9
|
-
return side_loads if models.empty? || options.include.nil?
|
6
|
+
{ meta: { } }.tap do |side_loads|
|
7
|
+
return side_loads if models.empty? || options.include.nil?
|
10
8
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
options.include.each do |include|
|
10
|
+
side_load_data = side_load(include, models, options)
|
11
|
+
side_loads[:meta].merge!(side_load_data[:meta] || {})
|
12
|
+
side_loads.merge! side_load_data.except(:meta)
|
13
|
+
end
|
15
14
|
end
|
16
|
-
side_loads
|
17
15
|
end
|
18
16
|
|
19
17
|
def can_includes
|
@@ -26,91 +24,60 @@ module RestPack::Serializer::SideLoading
|
|
26
24
|
end
|
27
25
|
|
28
26
|
def links
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
27
|
+
{}.tap do |links|
|
28
|
+
associations.each do |association|
|
29
|
+
if association.macro == :belongs_to
|
30
|
+
link_key = "#{self.key}.#{association.name}"
|
31
|
+
href = "/#{association.plural_name}/{#{link_key}}"
|
32
|
+
elsif association.macro.to_s.match(/has_/)
|
33
|
+
singular_key = self.key.to_s.singularize
|
34
|
+
link_key = "#{self.key}.#{association.plural_name}"
|
35
|
+
href = "/#{association.plural_name}?#{singular_key}_id={#{key}.id}"
|
36
|
+
end
|
37
|
+
links.merge!(link_key => {
|
38
|
+
:href => RestPack::Serializer.config.href_prefix + href,
|
39
|
+
:type => association.plural_name.to_sym
|
40
|
+
}
|
41
|
+
)
|
39
42
|
end
|
40
|
-
|
41
|
-
links[link_key] = {
|
42
|
-
:href => RestPack::Serializer.config.href_prefix + href,
|
43
|
-
:type => association.plural_name.to_sym
|
44
|
-
}
|
45
43
|
end
|
46
|
-
|
47
|
-
links
|
48
44
|
end
|
49
45
|
|
50
46
|
def associations
|
51
|
-
|
52
|
-
can_includes.each do |include|
|
47
|
+
can_includes.map do |include|
|
53
48
|
association = association_from_include(include)
|
54
|
-
|
55
|
-
end
|
56
|
-
associations
|
49
|
+
association if supported_association?(association.macro)
|
50
|
+
end.compact
|
57
51
|
end
|
58
52
|
|
59
53
|
private
|
60
54
|
|
61
55
|
def side_load(include, models, options)
|
62
56
|
association = association_from_include(include)
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def supported_association?(association)
|
73
|
-
[:belongs_to, :has_many].include?(association.macro)
|
74
|
-
end
|
75
|
-
|
76
|
-
def side_load_belongs_to(association, models, serializer)
|
77
|
-
foreign_keys = models.map { |model| model.send(association.foreign_key) }.uniq
|
78
|
-
side_load = association.klass.find(foreign_keys)
|
79
|
-
|
80
|
-
return {
|
81
|
-
association.plural_name.to_sym => side_load.map { |model| serializer.as_json(model) },
|
82
|
-
:meta => { }
|
83
|
-
}
|
57
|
+
return {} unless supported_association?(association.macro)
|
58
|
+
serializer = RestPack::Serializer::Factory.create(association.class_name)
|
59
|
+
builder = RestPack::Serializer::SideLoadDataBuilder.new(association,
|
60
|
+
models,
|
61
|
+
serializer)
|
62
|
+
builder.send("side_load_#{association.macro}")
|
84
63
|
end
|
85
64
|
|
86
|
-
def
|
87
|
-
|
88
|
-
|
89
|
-
join_table = association.options[:through]
|
90
|
-
|
91
|
-
filters = if join_table
|
92
|
-
{ join_table => { association.through_reflection.foreign_key.to_sym => models.map(&:id) } }
|
93
|
-
else
|
94
|
-
{ association.foreign_key.to_sym => models.map(&:id) }
|
95
|
-
end
|
96
|
-
|
97
|
-
options = RestPack::Serializer::Options.new(serializer.class)
|
98
|
-
options.scope = options.scope.joins(join_table) if join_table
|
99
|
-
options.filters = filters
|
100
|
-
options.include_links = false
|
101
|
-
|
102
|
-
serializer.class.page_with_options(options)
|
65
|
+
def supported_association?(association_macro)
|
66
|
+
[:belongs_to, :has_many, :has_and_belongs_to_many].include?(association_macro)
|
103
67
|
end
|
104
68
|
|
105
69
|
def association_from_include(include)
|
106
70
|
raise_invalid_include(include) unless self.can_includes.include?(include)
|
107
|
-
|
108
71
|
possible_relations = [include.to_s.singularize.to_sym, include]
|
72
|
+
select_association_from_possibles(possible_relations)
|
73
|
+
end
|
74
|
+
|
75
|
+
def select_association_from_possibles(possible_relations)
|
109
76
|
possible_relations.each do |relation|
|
110
|
-
association = self.model_class.reflect_on_association(relation)
|
111
|
-
|
77
|
+
if association = self.model_class.reflect_on_association(relation)
|
78
|
+
return association
|
79
|
+
end
|
112
80
|
end
|
113
|
-
|
114
81
|
raise_invalid_include(include)
|
115
82
|
end
|
116
83
|
|
@@ -6,6 +6,7 @@ require_relative "serializable/paging"
|
|
6
6
|
require_relative "serializable/resource"
|
7
7
|
require_relative "serializable/single"
|
8
8
|
require_relative "serializable/side_loading"
|
9
|
+
require_relative "serializable/side_load_data_builder"
|
9
10
|
require_relative "serializable/symbolizer"
|
10
11
|
require_relative "serializable/sortable"
|
11
12
|
|
@@ -65,17 +66,15 @@ module RestPack
|
|
65
66
|
|
66
67
|
def add_links(model, data)
|
67
68
|
self.class.associations.each do |association|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
data[:links] ||= {}
|
78
|
-
data[:links][association.name.to_sym] = ids
|
69
|
+
data[:links] ||= {}
|
70
|
+
links_value = case
|
71
|
+
when association.macro == :belongs_to
|
72
|
+
model.send(association.foreign_key).try(:to_s)
|
73
|
+
when association.macro.to_s.match(/has_/)
|
74
|
+
model.send(association.name).pluck(:id).map(&:to_s)
|
75
|
+
end
|
76
|
+
unless links_value.blank?
|
77
|
+
data[:links][association.name.to_sym] = links_value
|
79
78
|
end
|
80
79
|
end
|
81
80
|
data
|
data/spec/fixtures/db.rb
CHANGED
@@ -51,6 +51,17 @@ ActiveRecord::Schema.define(:version => 1) do
|
|
51
51
|
t.datetime "created_at"
|
52
52
|
t.datetime "updated_at"
|
53
53
|
end
|
54
|
+
|
55
|
+
create_table "stalkers", :force => true do |t|
|
56
|
+
t.string "name"
|
57
|
+
t.datetime "created_at"
|
58
|
+
t.datetime "updated_at"
|
59
|
+
end
|
60
|
+
|
61
|
+
create_table "artists_stalkers", force: true, id: false do |t|
|
62
|
+
t.integer :artist_id
|
63
|
+
t.integer :stalker_id
|
64
|
+
end
|
54
65
|
end
|
55
66
|
|
56
67
|
module MyApp
|
@@ -61,6 +72,7 @@ module MyApp
|
|
61
72
|
has_many :songs
|
62
73
|
has_many :payments
|
63
74
|
has_many :fans, :through => :payments
|
75
|
+
has_and_belongs_to_many :stalkers
|
64
76
|
end
|
65
77
|
|
66
78
|
class Album < ActiveRecord::Base
|
@@ -98,4 +110,9 @@ module MyApp
|
|
98
110
|
has_many :payments
|
99
111
|
has_many :artists, :through => :albums
|
100
112
|
end
|
113
|
+
|
114
|
+
class Stalker < ActiveRecord::Base
|
115
|
+
attr_accessible :name
|
116
|
+
has_and_belongs_to_many :artists
|
117
|
+
end
|
101
118
|
end
|
@@ -27,11 +27,16 @@ module MyApp
|
|
27
27
|
class ArtistSerializer
|
28
28
|
include RestPack::Serializer
|
29
29
|
attributes :id, :name, :website
|
30
|
-
can_include :albums, :songs, :fans
|
30
|
+
can_include :albums, :songs, :fans, :stalkers
|
31
31
|
end
|
32
32
|
|
33
33
|
class FanSerializer
|
34
34
|
include RestPack::Serializer
|
35
35
|
attributes :id, :name
|
36
36
|
end
|
37
|
+
|
38
|
+
class StalkerSerializer
|
39
|
+
include RestPack::Serializer
|
40
|
+
attributes :id, :name
|
41
|
+
end
|
37
42
|
end
|
@@ -152,14 +152,27 @@ describe RestPack::Serializer do
|
|
152
152
|
end
|
153
153
|
end
|
154
154
|
|
155
|
-
context "
|
155
|
+
context "with a serializer with has_* associations" do
|
156
156
|
let(:artist_serializer) { MyApp::ArtistSerializer.new }
|
157
|
+
let(:json) { artist_serializer.as_json(artist_factory) }
|
158
|
+
let(:side_load_ids) { artist_has_association.map {|obj| obj.id.to_s } }
|
157
159
|
|
158
|
-
|
159
|
-
|
160
|
+
describe "'has_many, through' associations" do
|
161
|
+
let(:artist_factory) { FactoryGirl.create :artist_with_fans }
|
162
|
+
let(:artist_has_association) { artist_factory.fans }
|
160
163
|
|
161
|
-
|
162
|
-
|
164
|
+
it "includes 'links' data when there are associated records" do
|
165
|
+
expect(json[:links][:fans]).to match_array(side_load_ids)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe "'has_and_belongs_to_many' associations" do
|
170
|
+
let(:artist_factory) { FactoryGirl.create :artist_with_stalkers }
|
171
|
+
let(:artist_has_association) { artist_factory.stalkers }
|
172
|
+
|
173
|
+
it "includes 'links' data when there are associated records" do
|
174
|
+
expect(json[:links][:stalkers]).to match_array(side_load_ids)
|
175
|
+
end
|
163
176
|
end
|
164
177
|
end
|
165
178
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RestPack::Serializer::SideLoading do
|
4
|
+
context "when side-loading" do
|
5
|
+
let(:side_loads) { MyApp::ArtistSerializer.side_loads(models, options) }
|
6
|
+
|
7
|
+
describe ".has_and_belongs_to_many" do
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
@artist1 = FactoryGirl.create(:artist_with_stalkers, stalker_count: 2)
|
11
|
+
@artist2 = FactoryGirl.create(:artist_with_stalkers, stalker_count: 3)
|
12
|
+
end
|
13
|
+
|
14
|
+
context "with a single model" do
|
15
|
+
let(:models) { [@artist1] }
|
16
|
+
|
17
|
+
context "when including :albums" do
|
18
|
+
let(:options) { RestPack::Serializer::Options.new(MyApp::ArtistSerializer, { "include" => "stalkers" }) }
|
19
|
+
let(:stalker_count) { @artist1.stalkers.count }
|
20
|
+
|
21
|
+
it "returns side-loaded albums" do
|
22
|
+
side_loads[:stalkers].count.should == stalker_count
|
23
|
+
side_loads[:meta][:stalkers][:page].should == 1
|
24
|
+
side_loads[:meta][:stalkers][:count].should == stalker_count
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "with two models" do
|
30
|
+
let(:models) { [@artist1, @artist2] }
|
31
|
+
|
32
|
+
context "when including :albums" do
|
33
|
+
let(:options) { RestPack::Serializer::Options.new(MyApp::ArtistSerializer, { "include" => "stalkers" }) }
|
34
|
+
let(:stalker_count) { @artist1.stalkers.count + @artist2.stalkers.count }
|
35
|
+
|
36
|
+
it "returns side-loaded albums" do
|
37
|
+
side_loads[:stalkers].count.should == stalker_count
|
38
|
+
side_loads[:meta][:stalkers][:count].should == stalker_count
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/spec/support/factory.rb
CHANGED
@@ -23,6 +23,15 @@ FactoryGirl.define do
|
|
23
23
|
create_list(:payment, evaluator.fans_count, artist: artist)
|
24
24
|
end
|
25
25
|
end
|
26
|
+
|
27
|
+
factory :artist_with_stalkers do
|
28
|
+
ignore do
|
29
|
+
stalker_count 2
|
30
|
+
end
|
31
|
+
after(:create) do |artist, evaluator|
|
32
|
+
create_list(:stalker, evaluator.stalker_count, artists: [ artist ])
|
33
|
+
end
|
34
|
+
end
|
26
35
|
end
|
27
36
|
|
28
37
|
factory :album, :class => MyApp::Album do
|
@@ -56,4 +65,8 @@ FactoryGirl.define do
|
|
56
65
|
factory :fan, :class => MyApp::Fan do
|
57
66
|
sequence(:name) {|n| "Fan ##{n}"}
|
58
67
|
end
|
68
|
+
|
69
|
+
factory :stalker, :class => MyApp::Stalker do
|
70
|
+
sequence(:name) {|n| "Stalker ##{n}"}
|
71
|
+
end
|
59
72
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: restpack_serializer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gavin Joyce
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-09-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -229,6 +229,7 @@ files:
|
|
229
229
|
- lib/restpack_serializer/serializable/filterable.rb
|
230
230
|
- lib/restpack_serializer/serializable/paging.rb
|
231
231
|
- lib/restpack_serializer/serializable/resource.rb
|
232
|
+
- lib/restpack_serializer/serializable/side_load_data_builder.rb
|
232
233
|
- lib/restpack_serializer/serializable/side_loading.rb
|
233
234
|
- lib/restpack_serializer/serializable/single.rb
|
234
235
|
- lib/restpack_serializer/serializable/sortable.rb
|
@@ -247,6 +248,7 @@ files:
|
|
247
248
|
- spec/serializable/resource_spec.rb
|
248
249
|
- spec/serializable/serializer_spec.rb
|
249
250
|
- spec/serializable/side_loading/belongs_to_spec.rb
|
251
|
+
- spec/serializable/side_loading/has_and_belongs_many_spec.rb
|
250
252
|
- spec/serializable/side_loading/has_many_spec.rb
|
251
253
|
- spec/serializable/side_loading/side_loading_spec.rb
|
252
254
|
- spec/serializable/single_spec.rb
|
@@ -289,6 +291,7 @@ test_files:
|
|
289
291
|
- spec/serializable/resource_spec.rb
|
290
292
|
- spec/serializable/serializer_spec.rb
|
291
293
|
- spec/serializable/side_loading/belongs_to_spec.rb
|
294
|
+
- spec/serializable/side_loading/has_and_belongs_many_spec.rb
|
292
295
|
- spec/serializable/side_loading/has_many_spec.rb
|
293
296
|
- spec/serializable/side_loading/side_loading_spec.rb
|
294
297
|
- spec/serializable/single_spec.rb
|