Skip to content

Commit 0fed486

Browse files
authored
Merge pull request #1754 from bf4/handle_missing_serialization_context_when_paginating
Fix #1759, Grape integration, adds serialization_context
2 parents b4615ea + 1a9c622 commit 0fed486

File tree

8 files changed

+172
-18
lines changed

8 files changed

+172
-18
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ Features:
99
- [#1426](https://github.com/rails-api/active_model_serializers/pull/1426) Add ActiveModelSerializers.config.default_includes (@empact)
1010

1111
Fixes:
12+
- [#1754](https://github.com/rails-api/active_model_serializers/pull/1754) Fixes #1759, Grape integration, improves serialization_context
13+
missing error message on pagination. Document overriding CollectionSerializer#paginated?. (@bf4)
14+
Moved serialization_context creation to Grape formatter, so resource serialization works without explicit calls to the `render` helper method.
15+
Added Grape collection tests. (@onomated)
1216
- [#1287](https://github.com/rails-api/active_model_serializers/pull/1287) Pass `fields` options from adapter to serializer. (@vasilakisfil)
1317
- [#1710](https://github.com/rails-api/active_model_serializers/pull/1710) Prevent association loading when `include_data` option
1418
is set to `false`. (@groyoh)

lib/active_model_serializers/adapter/json_api/pagination_links.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,21 @@ module ActiveModelSerializers
22
module Adapter
33
class JsonApi < Base
44
class PaginationLinks
5+
MissingSerializationContextError = Class.new(KeyError)
56
FIRST_PAGE = 1
67

78
attr_reader :collection, :context
89

910
def initialize(collection, adapter_options)
1011
@collection = collection
1112
@adapter_options = adapter_options
12-
@context = adapter_options.fetch(:serialization_context)
13+
@context = adapter_options.fetch(:serialization_context) do
14+
fail MissingSerializationContextError, <<-EOF.freeze
15+
JsonApi::PaginationLinks requires a ActiveModelSerializers::SerializationContext.
16+
Please pass a ':serialization_context' option or
17+
override CollectionSerializer#paginated? to return 'false'.
18+
EOF
19+
end
1320
end
1421

1522
def as_json

lib/active_model_serializers/serialization_context.rb

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
require 'active_support/core_ext/array/extract_options'
12
module ActiveModelSerializers
23
class SerializationContext
34
class << self
@@ -22,9 +23,15 @@ def default_url_options
2223

2324
attr_reader :request_url, :query_parameters, :key_transform
2425

25-
def initialize(request, options = {})
26-
@request_url = request.original_url[/\A[^?]+/]
27-
@query_parameters = request.query_parameters
26+
def initialize(*args)
27+
options = args.extract_options!
28+
if args.size == 1
29+
request = args.pop
30+
options[:request_url] = request.original_url[/\A[^?]+/]
31+
options[:query_parameters] = request.query_parameters
32+
end
33+
@request_url = options.delete(:request_url)
34+
@query_parameters = options.delete(:query_parameters)
2835
@url_helpers = options.delete(:url_helpers) || self.class.url_helpers
2936
@default_url_options = options.delete(:default_url_options) || self.class.default_url_options
3037
end

lib/grape/formatters/active_model_serializers.rb

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,31 @@
22
#
33
# Serializer options can be passed as a hash from your Grape endpoint using env[:active_model_serializer_options],
44
# or better yet user the render helper in Grape::Helpers::ActiveModelSerializers
5+
6+
require 'active_model_serializers/serialization_context'
7+
58
module Grape
69
module Formatters
710
module ActiveModelSerializers
811
def self.call(resource, env)
9-
serializer_options = {}
10-
serializer_options.merge!(env[:active_model_serializer_options]) if env[:active_model_serializer_options]
12+
serializer_options = build_serializer_options(env)
1113
::ActiveModelSerializers::SerializableResource.new(resource, serializer_options).to_json
1214
end
15+
16+
def self.build_serializer_options(env)
17+
ams_options = env[:active_model_serializer_options] || {}
18+
19+
# Add serialization context
20+
ams_options.fetch(:serialization_context) do
21+
request = env['grape.request']
22+
ams_options[:serialization_context] = ::ActiveModelSerializers::SerializationContext.new(
23+
request_url: request.url[/\A[^?]+/],
24+
query_parameters: request.params
25+
)
26+
end
27+
28+
ams_options
29+
end
1330
end
1431
end
1532
end

lib/grape/helpers/active_model_serializers.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Helpers can be included in your Grape endpoint as: helpers Grape::Helpers::ActiveModelSerializers
2+
23
module Grape
34
module Helpers
45
module ActiveModelSerializers

test/active_model_serializers/serialization_context_test_isolated.rb

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,19 @@
55
class SerializationContextTest < ActiveSupport::TestCase
66
include ActiveSupport::Testing::Isolation
77

8-
def create_request
9-
request = Minitest::Mock.new
10-
request.expect(:original_url, 'original_url')
11-
request.expect(:query_parameters, 'query_parameters')
12-
end
13-
148
class WithRails < SerializationContextTest
9+
def create_request
10+
request = ActionDispatch::Request.new({})
11+
def request.original_url
12+
'http://example.com/articles?page=2'
13+
end
14+
15+
def request.query_parameters
16+
{ 'page' => 2 }
17+
end
18+
request
19+
end
20+
1521
setup do
1622
require 'rails'
1723
require 'active_model_serializers'
@@ -20,8 +26,8 @@ class WithRails < SerializationContextTest
2026
end
2127

2228
test 'create context with request url and query parameters' do
23-
assert_equal @context.request_url, 'original_url'
24-
assert_equal @context.query_parameters, 'query_parameters'
29+
assert_equal @context.request_url, 'http://example.com/articles'
30+
assert_equal @context.query_parameters, 'page' => 2
2531
end
2632

2733
test 'url_helpers is set up for Rails url_helpers' do
@@ -36,14 +42,21 @@ class WithRails < SerializationContextTest
3642
end
3743

3844
class WithoutRails < SerializationContextTest
45+
def create_request
46+
{
47+
request_url: 'http://example.com/articles',
48+
query_parameters: { 'page' => 2 }
49+
}
50+
end
51+
3952
setup do
4053
require 'active_model_serializers/serialization_context'
4154
@context = ActiveModelSerializers::SerializationContext.new(create_request)
4255
end
4356

4457
test 'create context with request url and query parameters' do
45-
assert_equal @context.request_url, 'original_url'
46-
assert_equal @context.query_parameters, 'query_parameters'
58+
assert_equal @context.request_url, 'http://example.com/articles'
59+
assert_equal @context.query_parameters, 'page' => 2
4760
end
4861

4962
test 'url_helpers is a module when Rails is not present' do

test/adapter/json_api/pagination_links_test.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,17 @@ def test_not_showing_pagination_links
161161

162162
assert_equal expected_response_without_pagination_links, adapter.serializable_hash
163163
end
164+
165+
def test_raises_descriptive_error_when_serialization_context_unset
166+
render_options = { adapter: :json_api }
167+
adapter = serializable(using_kaminari, render_options)
168+
exception = assert_raises do
169+
adapter.as_json
170+
end
171+
exception_class = ActiveModelSerializers::Adapter::JsonApi::PaginationLinks::MissingSerializationContextError
172+
assert_equal exception_class, exception.class
173+
assert_match(/CollectionSerializer#paginated\?/, exception.message)
174+
end
164175
end
165176
end
166177
end

test/grape_test.rb

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
require 'test_helper'
22
require 'grape'
33
require 'grape/active_model_serializers'
4+
require 'kaminari'
5+
require 'kaminari/hooks'
6+
::Kaminari::Hooks.init
47

58
class ActiveModelSerializers::GrapeTest < ActiveSupport::TestCase
69
include Rack::Test::Methods
@@ -21,6 +24,30 @@ def self.all
2124
ARModels::Post.all
2225
end
2326
end
27+
28+
def self.reset_all
29+
ARModels::Post.delete_all
30+
@all = nil
31+
end
32+
33+
def self.collection_per
34+
2
35+
end
36+
37+
def self.collection
38+
@collection ||=
39+
begin
40+
Kaminari.paginate_array(
41+
[
42+
Profile.new(id: 1, name: 'Name 1', description: 'Description 1', comments: 'Comments 1'),
43+
Profile.new(id: 2, name: 'Name 2', description: 'Description 2', comments: 'Comments 2'),
44+
Profile.new(id: 3, name: 'Name 3', description: 'Description 3', comments: 'Comments 3'),
45+
Profile.new(id: 4, name: 'Name 4', description: 'Description 4', comments: 'Comments 4'),
46+
Profile.new(id: 5, name: 'Name 5', description: 'Description 5', comments: 'Comments 5')
47+
]
48+
).page(1).per(collection_per)
49+
end
50+
end
2451
end
2552

2653
class GrapeTest < Grape::API
@@ -41,11 +68,28 @@ class GrapeTest < Grape::API
4168
posts = Models.all
4269
render posts, adapter: :json_api
4370
end
71+
72+
get '/render_collection_with_json_api' do
73+
posts = Models.collection
74+
render posts, adapter: :json_api
75+
end
76+
77+
get '/render_with_implicit_formatter' do
78+
Models.model1
79+
end
80+
81+
get '/render_array_with_implicit_formatter' do
82+
Models.all
83+
end
84+
85+
get '/render_collection_with_implicit_formatter' do
86+
Models.collection
87+
end
4488
end
4589
end
4690

4791
def app
48-
GrapeTest.new
92+
Grape::Middleware::Globals.new(GrapeTest.new)
4993
end
5094

5195
def test_formatter_returns_json
@@ -77,6 +121,56 @@ def test_formatter_handles_arrays
77121
assert last_response.ok?
78122
assert_equal serializable_resource.to_json, last_response.body
79123
ensure
80-
ARModels::Post.delete_all
124+
Models.reset_all
125+
end
126+
127+
def test_formatter_handles_collections
128+
get '/grape/render_collection_with_json_api'
129+
assert last_response.ok?
130+
131+
representation = JSON.parse(last_response.body)
132+
assert representation.include?('data')
133+
assert representation['data'].count == Models.collection_per
134+
assert representation.include?('links')
135+
assert representation['links'].count > 0
136+
end
137+
138+
def test_implicit_formatter
139+
post = Models.model1
140+
serializable_resource = serializable(post, adapter: :json_api)
141+
142+
with_adapter :json_api do
143+
get '/grape/render_with_implicit_formatter'
144+
end
145+
146+
assert last_response.ok?
147+
assert_equal serializable_resource.to_json, last_response.body
148+
end
149+
150+
def test_implicit_formatter_handles_arrays
151+
posts = Models.all
152+
serializable_resource = serializable(posts, adapter: :json_api)
153+
154+
with_adapter :json_api do
155+
get '/grape/render_array_with_implicit_formatter'
156+
end
157+
158+
assert last_response.ok?
159+
assert_equal serializable_resource.to_json, last_response.body
160+
ensure
161+
Models.reset_all
162+
end
163+
164+
def test_implicit_formatter_handles_collections
165+
with_adapter :json_api do
166+
get '/grape/render_collection_with_implicit_formatter'
167+
end
168+
169+
representation = JSON.parse(last_response.body)
170+
assert last_response.ok?
171+
assert representation.include?('data')
172+
assert representation['data'].count == Models.collection_per
173+
assert representation.include?('links')
174+
assert representation['links'].count > 0
81175
end
82176
end

0 commit comments

Comments
 (0)