grape-active_model_serializers 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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