grape-active_model_serializers 1.4.0 → 1.5.0

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.
@@ -0,0 +1,99 @@
1
+ module Grape
2
+ module ActiveModelSerializers
3
+ class SerializerResolver
4
+ def initialize(resource, options)
5
+ self.resource = resource
6
+ self.options = options
7
+ end
8
+
9
+ def serializer
10
+ serializer_class.new(resource, serializer_options) if serializer_class
11
+ end
12
+
13
+ private
14
+
15
+ attr_accessor :resource, :options
16
+
17
+ def serializer_class
18
+ return @serializer_class if defined?(@serializer_class)
19
+ @serializer_class = resource_defined_class
20
+ @serializer_class ||= collection_class
21
+ @serializer_class ||= options[:serializer]
22
+ @serializer_class ||= namespace_inferred_class
23
+ @serializer_class ||= version_inferred_class
24
+ @serializer_class ||= resource_serializer_class
25
+ end
26
+
27
+ def serializer_options
28
+ if collection_serializer? && !options.key?(:serializer)
29
+ options.merge(each_serializer_option)
30
+ else
31
+ options
32
+ end
33
+ end
34
+
35
+ def collection_serializer?
36
+ serializer_class == ActiveModel::Serializer.config.collection_serializer
37
+ end
38
+
39
+ def each_serializer_option
40
+ serializer_class = options[:each_serializer]
41
+ serializer_class ||= namespace_inferred_class
42
+ serializer_class ||= version_inferred_class
43
+ serializer_class ? { serializer: serializer_class } : {}
44
+ end
45
+
46
+ def resource_defined_class
47
+ resource.serializer_class if resource.respond_to?(:serializer_class)
48
+ end
49
+
50
+ def collection_class
51
+ return nil unless resource.respond_to?(:to_ary)
52
+ ActiveModel::Serializer.config.collection_serializer
53
+ end
54
+
55
+ def namespace_inferred_class
56
+ return nil unless options.key?(:for)
57
+ namespace = options[:for].to_s.deconstantize
58
+ "#{namespace}::#{resource_serializer_klass}".safe_constantize
59
+ end
60
+
61
+ def version_inferred_class
62
+ return nil unless options.key?(:version)
63
+ "#{version}::#{resource_serializer_klass}".safe_constantize
64
+ end
65
+
66
+ def version
67
+ options[:version].try(:classify)
68
+ end
69
+
70
+ def resource_serializer_klass
71
+ @resource_serializer_klass ||= [
72
+ resource_namespace,
73
+ "#{resource_klass}Serializer"
74
+ ].compact.join('::')
75
+ end
76
+
77
+ def resource_klass
78
+ resource_class.name.demodulize
79
+ end
80
+
81
+ def resource_namespace
82
+ klass = resource_class.name.deconstantize
83
+ klass.empty? ? nil : klass
84
+ end
85
+
86
+ def resource_class
87
+ if resource.respond_to?(:to_ary)
88
+ resource.try(:klass) || resource.compact.first.class
89
+ else
90
+ resource.class
91
+ end
92
+ end
93
+
94
+ def resource_serializer_class
95
+ ActiveModel::Serializer.serializer_for(resource)
96
+ end
97
+ end
98
+ end
99
+ end
@@ -1,5 +1,5 @@
1
1
  module Grape
2
2
  module ActiveModelSerializers
3
- VERSION = '1.4.0'
3
+ VERSION = '1.5.0'.freeze
4
4
  end
5
5
  end
@@ -5,6 +5,7 @@ describe '#render' do
5
5
  let(:app) { Class.new(Grape::API) }
6
6
 
7
7
  before do
8
+ ActiveModelSerializers.config.adapter = :json
8
9
  app.format :json
9
10
  app.formatter :json, Grape::Formatter::ActiveModelSerializers
10
11
  end
@@ -2,9 +2,17 @@ require 'spec_helper'
2
2
 
3
3
  describe 'Grape::EndpointExtension' do
4
4
  if Grape::Util.const_defined?('InheritableSetting')
5
- subject { Grape::Endpoint.new(Grape::Util::InheritableSetting.new, path: '/', method: 'foo') }
5
+ subject do
6
+ Grape::Endpoint.new(
7
+ Grape::Util::InheritableSetting.new,
8
+ path: '/',
9
+ method: 'foo'
10
+ )
11
+ end
6
12
  else
7
- subject { Grape::Endpoint.new({}, path: '/', method: 'foo') }
13
+ subject do
14
+ Grape::Endpoint.new({}, path: '/', method: 'foo')
15
+ end
8
16
  end
9
17
 
10
18
  let(:serializer) { Grape::Formatter::ActiveModelSerializers }
@@ -23,19 +31,29 @@ describe 'Grape::EndpointExtension' do
23
31
  before do
24
32
  allow(subject).to receive(:env).and_return({})
25
33
  end
34
+
26
35
  it { should respond_to(:render) }
27
36
  let(:meta_content) { { total: 2 } }
28
37
  let(:meta_full) { { meta: meta_content } }
38
+
29
39
  context 'supplying meta' do
40
+ before do
41
+ allow(subject).to receive(:env) { { meta: meta_full } }
42
+ end
43
+
30
44
  it 'passes through the Resource and uses given meta settings' do
31
- allow(subject).to receive(:env).and_return(meta: meta_full)
32
45
  expect(subject.render(users, meta_full)).to eq(users)
33
46
  end
34
47
  end
48
+
35
49
  context 'supplying meta and key' do
36
50
  let(:meta_key) { { meta_key: :custom_key_name } }
51
+
52
+ before do
53
+ allow(subject).to receive(:env) { { meta: meta_full.merge(meta_key) } }
54
+ end
55
+
37
56
  it 'passes through the Resource and uses given meta settings' do
38
- allow(subject).to receive(:env).and_return(meta: meta_full.merge(meta_key))
39
57
  expect(subject.render(users, meta_full.merge(meta_key))).to eq(users)
40
58
  end
41
59
  end
@@ -18,19 +18,24 @@ describe Grape::Formatter::ActiveModelSerializers do
18
18
  end
19
19
  end
20
20
 
21
+ let(:env) { { 'api.endpoint' => app.endpoints.first } }
22
+ let(:options) { described_class.build_options(nil, env) }
23
+
21
24
  it 'should read serializer options like "root"' do
22
- expect(described_class.build_options_from_endpoint(app.endpoints.first)).to include :root
25
+ expect(options).to include(:root)
23
26
  end
24
27
  end
25
28
 
26
29
  describe '.fetch_serializer' do
27
30
  let(:user) { User.new(first_name: 'John') }
28
31
 
32
+ let(:params) { { path: '/', method: 'foo', root: false } }
29
33
  if Grape::Util.const_defined?('InheritableSetting')
30
- let(:endpoint) { Grape::Endpoint.new(Grape::Util::InheritableSetting.new, path: '/', method: 'foo', root: false) }
34
+ let(:setting) { Grape::Util::InheritableSetting.new }
31
35
  else
32
- let(:endpoint) { Grape::Endpoint.new({}, path: '/', method: 'foo', root: false) }
36
+ let(:setting) { {} }
33
37
  end
38
+ let(:endpoint) { Grape::Endpoint.new(setting, params) }
34
39
 
35
40
  let(:env) { { 'api.endpoint' => endpoint } }
36
41
 
@@ -44,7 +49,10 @@ describe Grape::Formatter::ActiveModelSerializers do
44
49
  end
45
50
  end
46
51
 
47
- subject { described_class.fetch_serializer(user, env) }
52
+ let(:options) { described_class.build_options(user, env) }
53
+ subject { described_class.fetch_serializer(user, options) }
54
+
55
+ let(:instance_options) { subject.instance_variable_get(:@instance_options) }
48
56
 
49
57
  it { should be_a UserSerializer }
50
58
 
@@ -53,12 +61,12 @@ describe Grape::Formatter::ActiveModelSerializers do
53
61
  end
54
62
 
55
63
  it 'should read default serializer options' do
56
- expect(subject.instance_variable_get('@only')).to eq([:only])
57
- expect(subject.instance_variable_get('@except')).to eq([:except])
64
+ expect(instance_options[:only]).to eq(:only)
65
+ expect(instance_options[:except]).to eq(:except)
58
66
  end
59
67
 
60
68
  it 'should read serializer options like "root"' do
61
- expect(described_class.build_options_from_endpoint(endpoint).keys).to include :root
69
+ expect(options).to include(:root)
62
70
  end
63
71
  end
64
72
  end
@@ -15,25 +15,35 @@ describe Grape::Formatter::ActiveModelSerializers do
15
15
 
16
16
  app.namespace('space') do |ns|
17
17
  ns.get('/', root: false, apiver: 'v1') do
18
- { user: { first_name: 'JR', last_name: 'HE', email: 'jrhe@github.com' } }
18
+ {
19
+ user: {
20
+ first_name: 'JR',
21
+ last_name: 'HE',
22
+ email: 'jrhe@github.com'
23
+ }
24
+ }
19
25
  end
20
26
  end
21
27
  end
22
28
 
29
+ let(:env) { { 'api.endpoint' => app.endpoints.first } }
30
+ let(:options) { described_class.build_options(nil, env) }
31
+
23
32
  it 'should read serializer options like "root"' do
24
- expect(described_class.build_options_from_endpoint(app.endpoints.first)).to include :root
33
+ expect(options).to include(:root)
25
34
  end
26
35
  end
27
36
 
28
37
  describe '.fetch_serializer' do
29
38
  let(:user) { User.new(first_name: 'John', email: 'j.doe@internet.com') }
30
39
 
40
+ let(:params) { { path: '/', method: 'foo', version: 'v1', root: false } }
31
41
  if Grape::Util.const_defined?('InheritableSetting')
32
- let(:endpoint) { Grape::Endpoint.new(Grape::Util::InheritableSetting.new, path: '/', method: 'foo', version: 'v1', root: false) }
42
+ let(:setting) { Grape::Util::InheritableSetting.new }
33
43
  else
34
- let(:endpoint) { Grape::Endpoint.new({}, path: '/', method: 'foo', version: 'v1', root: false) }
44
+ let(:setting) { {} }
35
45
  end
36
-
46
+ let(:endpoint) { Grape::Endpoint.new(setting, params) }
37
47
  let(:env) { { 'api.endpoint' => endpoint } }
38
48
 
39
49
  before do
@@ -46,7 +56,10 @@ describe Grape::Formatter::ActiveModelSerializers do
46
56
  end
47
57
  end
48
58
 
49
- subject { described_class.fetch_serializer(user, env) }
59
+ let(:options) { described_class.build_options(user, env) }
60
+ subject { described_class.fetch_serializer(user, options) }
61
+
62
+ let(:instance_options) { subject.send(:instance_options) }
50
63
 
51
64
  it { should be_a V1::UserSerializer }
52
65
 
@@ -55,12 +68,12 @@ describe Grape::Formatter::ActiveModelSerializers do
55
68
  end
56
69
 
57
70
  it 'should read default serializer options' do
58
- expect(subject.instance_variable_get('@only')).to eq([:only])
59
- expect(subject.instance_variable_get('@except')).to eq([:except])
71
+ expect(instance_options[:only]).to eq(:only)
72
+ expect(instance_options[:except]).to eq(:except)
60
73
  end
61
74
 
62
75
  it 'should read serializer options like "root"' do
63
- expect(described_class.build_options_from_endpoint(endpoint).keys).to include :root
76
+ expect(options).to include(:root)
64
77
  end
65
78
  end
66
79
  end
@@ -0,0 +1,146 @@
1
+ require 'pry'
2
+ require 'spec_helper'
3
+
4
+ # asserts serializer resolution order:
5
+ # 1. resource_defined_class # V1::UserSerializer
6
+ # 2. collection_class # CollectionSerializer, V2::UserSerializer
7
+ # 3. options[:serializer] # V3::UserSerializer
8
+ # 4. namespace_inferred_class # V4::UserSerializer
9
+ # 5. version_inferred_class # V5::UserSerializer
10
+ # 6. resource_serializer_class # UserSerializer
11
+ # 7. missing resource # nil
12
+
13
+ describe Grape::ActiveModelSerializers::SerializerResolver do
14
+ let(:resolver) { described_class.new(resource, options) }
15
+ let(:resource) { User.new }
16
+ let(:options) {
17
+ {
18
+ serializer: options_serializer_class, # options defined
19
+ for: V4::UsersApi, # namespace inference
20
+ version: 'v5' # version inference
21
+ }
22
+ }
23
+ # resource defined
24
+ let(:resource_defined?) { true }
25
+ let(:defined_serializer_class) { V1::UserSerializer }
26
+ # options defined
27
+ let(:options_serializer_class) { V3::UserSerializer }
28
+
29
+ let(:serializer) { resolver.serializer }
30
+
31
+ before do
32
+ if resource_defined?
33
+ allow(resource).to receive(:respond_to?).and_call_original
34
+ allow(resource).to receive(:respond_to?).with(:to_ary) { true }
35
+ allow(resource).to receive(:serializer_class) { defined_serializer_class }
36
+ end
37
+ end
38
+
39
+ context 'resource defined' do
40
+ it 'returns serializer' do
41
+ expect(serializer).to be_kind_of(defined_serializer_class)
42
+ end
43
+ end
44
+
45
+ context 'not resource defined' do
46
+ let(:resource_defined?) { false }
47
+
48
+ context 'resource collection' do
49
+ let(:resource) { [User.new] }
50
+ let(:serializer_class) { ActiveModel::Serializer::CollectionSerializer }
51
+
52
+ it 'returns serializer' do
53
+ expect(serializer).to be_kind_of(serializer_class)
54
+ end
55
+
56
+ context 'each serializer' do
57
+ let(:options) {
58
+ super().except(:serializer).merge(
59
+ each_serializer: V2::UserSerializer
60
+ )
61
+ }
62
+ let(:each_serializer) { serializer.send(:options)[:serializer] }
63
+
64
+ context 'each_serializer option' do
65
+ it 'returns expected serializer' do
66
+ expect(each_serializer).to eq(V2::UserSerializer)
67
+ end
68
+ end
69
+
70
+ context 'no each_serializer option' do
71
+ let(:options) { super().except(:each_serializer) }
72
+
73
+ context 'namespace inferred' do
74
+ it 'returns expected serializer' do
75
+ expect(each_serializer).to eq(V4::UserSerializer)
76
+ end
77
+ end
78
+
79
+ context 'not namespace inferred' do
80
+ let(:options) { super().except(:for) }
81
+
82
+ context 'version inferred' do
83
+ it 'returns expected serializer' do
84
+ expect(each_serializer).to eq(V5::UserSerializer)
85
+ end
86
+ end
87
+
88
+ context 'not version inferred' do
89
+ let(:options) { super().except(:version) }
90
+
91
+ it 'returns nil' do
92
+ expect(each_serializer).to eq(nil)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ context 'not resource collection' do
101
+ context 'specified by options' do
102
+ it 'returns specified serializer' do
103
+ expect(serializer).to be_kind_of(V3::UserSerializer)
104
+ end
105
+ end
106
+
107
+ context 'not specified by options' do
108
+ let(:options) { super().except(:serializer) }
109
+
110
+ context 'namespace inferred' do
111
+ it 'returns inferred serializer' do
112
+ expect(serializer).to be_kind_of(V4::UserSerializer)
113
+ end
114
+ end
115
+
116
+ context 'not namespace inferred' do
117
+ let(:options) { super().except(:for) }
118
+
119
+ context 'version inferred' do
120
+ it 'returns inferred serializer' do
121
+ expect(serializer).to be_kind_of(V5::UserSerializer)
122
+ end
123
+ end
124
+
125
+ context 'not version inferred' do
126
+ let(:options) { super().except(:version) }
127
+
128
+ context 'ASM resolved' do
129
+ it 'returns serializer' do
130
+ expect(serializer).to be_kind_of(UserSerializer)
131
+ end
132
+ end
133
+
134
+ context 'not ASM resolved' do
135
+ let(:resource) { nil }
136
+
137
+ it 'returns nil' do
138
+ expect(serializer).to eq(nil)
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -10,6 +10,7 @@ describe Grape::ActiveModelSerializers do
10
10
  subject { last_response.body }
11
11
 
12
12
  before do
13
+ ActiveModelSerializers.config.adapter = :json
13
14
  app.format :json
14
15
  app.formatter :json, Grape::Formatter::ActiveModelSerializers
15
16
  end
@@ -30,31 +31,48 @@ describe Grape::ActiveModelSerializers do
30
31
  end
31
32
  it 'uses the built in grape serializer' do
32
33
  get('/home')
33
- expect(subject).to eql "{\"user\":{\"first_name\":\"JR\",\"last_name\":\"HE\"}}"
34
+ expect(subject).to eq(
35
+ '{"user":{"first_name":"JR","last_name":"HE"}}'
36
+ )
34
37
  end
35
38
  end
36
39
 
37
40
  context "serializer isn't set" do
38
41
  before do
39
42
  app.get('/home') do
40
- User.new(first_name: 'JR', last_name: 'HE', email: 'contact@jrhe.co.uk')
43
+ User.new(
44
+ first_name: 'JR',
45
+ last_name: 'HE',
46
+ email: 'contact@jrhe.co.uk'
47
+ )
41
48
  end
42
49
  end
43
50
 
44
51
  it 'infers the serializer' do
45
52
  get '/home'
46
- expect(subject).to eql "{\"user\":{\"first_name\":\"JR\",\"last_name\":\"HE\"}}"
53
+ expect(subject).to eq(
54
+ '{"user":{"first_name":"JR","last_name":"HE"}}'
55
+ )
47
56
  end
48
57
  end
49
58
 
50
59
  it 'serializes arrays of objects' do
51
60
  app.get('/users') do
52
- user = User.new(first_name: 'JR', last_name: 'HE', email: 'contact@jrhe.co.uk')
61
+ user = User.new(
62
+ first_name: 'JR',
63
+ last_name: 'HE',
64
+ email: 'contact@jrhe.co.uk'
65
+ )
53
66
  [user, user]
54
67
  end
55
68
 
56
69
  get '/users'
57
- expect(subject).to eql "{\"users\":[{\"first_name\":\"JR\",\"last_name\":\"HE\"},{\"first_name\":\"JR\",\"last_name\":\"HE\"}]}"
70
+ expect(subject).to eq(
71
+ '{"users":['\
72
+ '{"first_name":"JR","last_name":"HE"},'\
73
+ '{"first_name":"JR","last_name":"HE"}'\
74
+ ']}'
75
+ )
58
76
  end
59
77
 
60
78
  context 'models with compound names' do
@@ -64,17 +82,29 @@ describe Grape::ActiveModelSerializers do
64
82
  end
65
83
 
66
84
  get '/home'
67
- expect(subject).to eql "{\"blog_post\":{\"title\":\"Grape AM::S Rocks!\",\"body\":\"Really, it does.\"}}"
85
+ expect(subject).to eq(
86
+ '{"blog_post":'\
87
+ '{"title":"Grape AM::S Rocks!","body":"Really, it does."}'\
88
+ '}'
89
+ )
68
90
  end
69
91
 
70
92
  it "generates the proper 'root' node for serialized arrays" do
71
93
  app.get('/blog_posts') do
72
- blog_post = BlogPost.new(title: 'Grape AM::S Rocks!', body: 'Really, it does.')
94
+ blog_post = BlogPost.new(
95
+ title: 'Grape AM::S Rocks!',
96
+ body: 'Really, it does.'
97
+ )
73
98
  [blog_post, blog_post]
74
99
  end
75
100
 
76
101
  get '/blog_posts'
77
- expect(subject).to eql "{\"blog_posts\":[{\"title\":\"Grape AM::S Rocks!\",\"body\":\"Really, it does.\"},{\"title\":\"Grape AM::S Rocks!\",\"body\":\"Really, it does.\"}]}"
102
+ expect(subject).to eq(
103
+ '{"blog_posts":['\
104
+ '{"title":"Grape AM::S Rocks!","body":"Really, it does."},'\
105
+ '{"title":"Grape AM::S Rocks!","body":"Really, it does."}'\
106
+ ']}'
107
+ )
78
108
  end
79
109
  end
80
110
 
@@ -86,7 +116,9 @@ describe Grape::ActiveModelSerializers do
86
116
  end
87
117
 
88
118
  get '/admin/jeff'
89
- expect(subject).to eql "{\"user\":{\"first_name\":\"Jeff\",\"last_name\":null}}"
119
+ expect(subject).to eq(
120
+ '{"user":{"first_name":"Jeff","last_name":null}}'
121
+ )
90
122
  end
91
123
 
92
124
  context 'route is in a namespace' do
@@ -99,7 +131,12 @@ describe Grape::ActiveModelSerializers do
99
131
  end
100
132
 
101
133
  get '/admin/jeff'
102
- expect(subject).to eql "{\"admin\":[{\"first_name\":\"Jeff\",\"last_name\":null},{\"first_name\":\"Jeff\",\"last_name\":null}]}"
134
+ expect(subject).to eq(
135
+ '{"admin":['\
136
+ '{"first_name":"Jeff","last_name":null},'\
137
+ '{"first_name":"Jeff","last_name":null}'\
138
+ ']}'
139
+ )
103
140
  end
104
141
  end
105
142
 
@@ -111,7 +148,12 @@ describe Grape::ActiveModelSerializers do
111
148
  end
112
149
 
113
150
  get '/people'
114
- expect(subject).to eql "{\"people\":[{\"first_name\":\"Jeff\",\"last_name\":null},{\"first_name\":\"Jeff\",\"last_name\":null}]}"
151
+ expect(subject).to eq(
152
+ '{"people":['\
153
+ '{"first_name":"Jeff","last_name":null},'\
154
+ '{"first_name":"Jeff","last_name":null}'\
155
+ ']}'
156
+ )
115
157
  end
116
158
  end
117
159
  end