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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dfbd8796c92fa7467d0ffac9b47477668189212b
4
- data.tar.gz: 41d7e43eb3cd37b6bebdd491e3c278d084effc1d
3
+ metadata.gz: 59940462e061cdebfa6cdcee2e8614a0c4907fd7
4
+ data.tar.gz: 6d9baf69729c965e1a45e1a99e6592d30e73552b
5
5
  SHA512:
6
- metadata.gz: a0b65750fcc4de192ff9841a9c7f406a78339116fe6daa0c8ab94cf37f2d655140c87b06a23e923d4c493579e15e2a206e3f50982291783fde1e3e316ca9b533
7
- data.tar.gz: 998f3f1cdd6cdcdfbfb7cf3678a37c677e468ea1163e632acdd27368b1925718d35d029dbc8f8290db9fc9ffb21f123c07d756003ba687475a2ac1d74b33f3d7
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
- side_loads = {
7
- :meta => { }
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
- options.include.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)
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
- links = {}
30
-
31
- associations.each do |association|
32
- if association.macro == :belongs_to
33
- link_key = "#{self.key}.#{association.name}"
34
- href = "/#{association.plural_name}/{#{link_key}}"
35
- elsif association.macro == :has_many
36
- singular_key = self.key.to_s.singularize
37
- link_key = "#{self.key}.#{association.plural_name}"
38
- href = "/#{association.plural_name}?#{singular_key}_id={#{key}.id}"
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
- associations = []
52
- can_includes.each do |include|
47
+ can_includes.map do |include|
53
48
  association = association_from_include(include)
54
- associations << association if supported_association?(association)
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
- if supported_association?(association)
65
- serializer = RestPack::Serializer::Factory.create(association.class_name)
66
- return send("side_load_#{association.macro}", association, models, serializer)
67
- else
68
- return {}
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 side_load_has_many(association, models, serializer)
87
- return {} if models.empty?
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
- return association unless association.nil?
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
- if association.macro == :belongs_to
69
- data[:links] ||= {}
70
- foreign_key_value = model.send(association.foreign_key)
71
- if foreign_key_value
72
- data[:links][association.name.to_sym] = foreign_key_value.to_s
73
- end
74
- elsif association.macro == :has_many
75
- ids = model.send(association.name).pluck(:id).map { |id| id.to_s }
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
@@ -1,5 +1,5 @@
1
1
  module RestPack
2
2
  module Serializer
3
- VERSION = '0.5.3'
3
+ VERSION = '0.5.4'
4
4
  end
5
5
  end
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 "'has_many, through' associations" do
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
- it "includes 'links' data when there are associated records" do
159
- artist_with_fans = FactoryGirl.create :artist_with_fans
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
- json = artist_serializer.as_json(artist_with_fans)
162
- json[:links][:fans].should == artist_with_fans.fans.collect {|obj| obj.id.to_s }
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
@@ -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.3
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-08-16 00:00:00.000000000 Z
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