jsonapi-rails 0.4.0 → 0.4.1

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
  SHA256:
3
- metadata.gz: d405b930bc2fa8afb813fc3ab4632b3b3badad3650dc0ef812dbb5696a5e1998
4
- data.tar.gz: ac9a0458233bd902d06996b1576d4e5bf4ee649914a363f55da6c22c206de198
3
+ metadata.gz: 0c800b64bcd00613a30227d1ce18ad5a26e1cd28979fde5a64c055aa471864f9
4
+ data.tar.gz: 99fecc3f76f087018335941f017c7e04b5e95a9de4da4b21c5e7fd03302355cb
5
5
  SHA512:
6
- metadata.gz: 5481077f3a4c79df726f33a338c5961b746ad5764a3644ded49d9f85b4b4c09340f1bc43300b433aa61828973a71e736e81dad3d9ca354cb1986721462d87a7f
7
- data.tar.gz: '054339d8e7b084229d01da3d70a19e68f904b38e88c32d23c13337950e4c30cd77ae86a744b4636c483bdd5d5458666d381952e284e465eb58417cbc91bb8b0e'
6
+ metadata.gz: 23a5535c16e86c274045d93dcdccfe86731247b96afb432c1945a0bd3043e497ac8df0e8bd69d2436d19e5a54d3b7867586827bf0ab804a4f4c27dbfb4e287e0
7
+ data.tar.gz: 1cc3c9b6305fe6829fb44caa19716a429ea51a0210a080c64c1668d7e5e1aa9d29ae645ac2adc69a4b92329b6d0bd7d875ea4bf2563941b8f6561ba8458fd920
data/README.md CHANGED
@@ -4,8 +4,8 @@ Rails integration for [jsonapi-rb](http://jsonapi-rb.org).
4
4
  ## Status
5
5
 
6
6
  [![Gem Version](https://badge.fury.io/rb/jsonapi-rails.svg)](https://badge.fury.io/rb/jsonapi-rails)
7
- [![Build Status](https://secure.travis-ci.org/jsonapi-rb/jsonapi-rails.svg?branch=master)](http://travis-ci.org/jsonapi-rb/jsonapi-rails?branch=master)
8
- [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/jsonapi-rb/Lobby)
7
+ [![Build Status](https://github.com/jsonapi-rb/jsonapi-rails/actions/workflows/ci.yml/badge.svg)](https://github.com/jsonapi-rb/jsonapi-rails/actions/workflows/ci.yml/badge.svg)
8
+ [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/jsonapi-rb/Lobby)
9
9
 
10
10
  ## Resources
11
11
 
@@ -6,6 +6,8 @@ module Jsonapi
6
6
  # TODO(beauby): Implement versioning.
7
7
 
8
8
  def copy_serializable_file
9
+ fail "#{class_name} model not found." unless model_exists?
10
+
9
11
  template 'serializable.rb.erb',
10
12
  File.join('app/serializable', class_path,
11
13
  "#{serializable_file_name}.rb")
@@ -13,6 +15,12 @@ module Jsonapi
13
15
 
14
16
  private
15
17
 
18
+ def model_exists?
19
+ Rails.application.eager_load!
20
+ models = ApplicationRecord.descendants.map(&:name)
21
+ !!models.find { |model_name| model_name == class_name }
22
+ end
23
+
16
24
  def serializable_file_name
17
25
  "serializable_#{file_name}"
18
26
  end
@@ -22,7 +30,6 @@ module Jsonapi
22
30
  end
23
31
 
24
32
  def model_klass
25
- # TODO(beauby): Ensure the model class exists.
26
33
  class_name.safe_constantize
27
34
  end
28
35
 
@@ -49,7 +49,8 @@ module JSONAPI
49
49
  Class.new(JSONAPI::Rails::DeserializableResource, &block)
50
50
 
51
51
  before_action(options) do |controller|
52
- hash = controller.params.to_unsafe_hash[:_jsonapi]
52
+ hash = controller.params.to_unsafe_hash
53
+ .with_indifferent_access[:_jsonapi]
53
54
  if hash.nil?
54
55
  JSONAPI::Rails.logger.warn do
55
56
  "Unable to deserialize #{key} because no JSON API payload was" \
@@ -0,0 +1,39 @@
1
+ require 'rack/media_type'
2
+
3
+ module JSONAPI
4
+ module Rails
5
+ class FilterMediaType
6
+ JSONAPI_MEDIA_TYPE = 'application/vnd.api+json'.freeze
7
+
8
+ def initialize(app)
9
+ @app = app
10
+ end
11
+
12
+ def call(env)
13
+ return [415, {}, []] unless valid_content_type?(env['CONTENT_TYPE'])
14
+ return [406, {}, []] unless valid_accept?(env['HTTP_ACCEPT'])
15
+
16
+ @app.call(env)
17
+ end
18
+
19
+ private
20
+
21
+ def valid_content_type?(content_type)
22
+ Rack::MediaType.type(content_type) != JSONAPI_MEDIA_TYPE ||
23
+ content_type == JSONAPI_MEDIA_TYPE
24
+ end
25
+
26
+ def valid_accept?(accept)
27
+ return true if accept.nil?
28
+
29
+ jsonapi_media_types =
30
+ accept.split(',')
31
+ .map(&:strip)
32
+ .select { |m| Rack::MediaType.type(m) == JSONAPI_MEDIA_TYPE }
33
+
34
+ jsonapi_media_types.empty? ||
35
+ jsonapi_media_types.any? { |m| Rack::MediaType.params(m) == {} }
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,5 +1,6 @@
1
1
  require 'rails/railtie'
2
2
 
3
+ require 'jsonapi/rails/filter_media_type'
3
4
  require 'jsonapi/rails/log_subscriber'
4
5
  require 'jsonapi/rails/renderer'
5
6
 
@@ -19,7 +20,7 @@ module JSONAPI
19
20
  jsonapi_errors: ErrorsRenderer.new
20
21
  }.freeze
21
22
 
22
- initializer 'jsonapi-rails.init' do
23
+ initializer 'jsonapi-rails.init' do |app|
23
24
  register_mime_type
24
25
  register_parameter_parser
25
26
  register_renderers
@@ -27,6 +28,8 @@ module JSONAPI
27
28
  require 'jsonapi/rails/controller'
28
29
  include ::JSONAPI::Rails::Controller
29
30
  end
31
+
32
+ app.middleware.use FilterMediaType
30
33
  end
31
34
 
32
35
  private
@@ -49,12 +52,78 @@ module JSONAPI
49
52
  RENDERERS.each do |name, renderer|
50
53
  ::ActionController::Renderers.add(name) do |resources, options|
51
54
  # Renderer proc is evaluated in the controller context.
52
- self.content_type ||= Mime[:jsonapi]
55
+ headers['Content-Type'] = Mime[:jsonapi].to_s
56
+
57
+ ActiveSupport::Notifications.instrument(
58
+ 'render.jsonapi-rails',
59
+ resources: resources,
60
+ options: options
61
+ ) do
62
+ # Depending on whether or not a valid cache object is present
63
+ # in the options, the #render call below will return two
64
+ # slightly different kinds of hash.
65
+ #
66
+ # Both hashes have broadly the following structure, where r is
67
+ # some representation of a JSON::API resource:
68
+ #
69
+ # {
70
+ # data: [ r1, r2, r3 ],
71
+ # meta: { count: 12345 },
72
+ # jsonapi: { version: "1.0" }
73
+ # }
74
+ #
75
+ # For non-cached calls to this method, the `data` field in the
76
+ # return value will contain an array of Ruby hashes.
77
+ #
78
+ # For cached calls, the `data` field will contain an array of
79
+ # JSON strings corresponding to the same data. This happens
80
+ # because jsonapi-renderer caches both the JSON serialization
81
+ # step as well as the assembly of the relevant attributes into
82
+ # a JSON::API-compliant structure. Those JSON strings are
83
+ # created via calls to `to_json`. They are then wrapped in
84
+ # CachedResourcesProcessor::JSONString. This defines a
85
+ # `to_json` method which simply returns self, ie - it attempts
86
+ # to ensure that any further `to_json` calls result in no
87
+ # changes.
88
+ #
89
+ # That isn't what happens in a Rails context, however. Below,
90
+ # the last step is to convert the entire output hash of the
91
+ # renderer into a JSON string to send to the client. If we
92
+ # call `to_json` on the cached output, the already-made JSON
93
+ # strings in the `data` field will be converted again,
94
+ # resulting in malformed data reaching the client. This happens
95
+ # because the ActiveSupport `to_json` takes precedent, meaning
96
+ # the "no-op" `to_json` definition on JSONString never gets
97
+ # executed.
98
+ #
99
+ # We can get around this by using JSON.generate instead, which
100
+ # will use the `to_json` defined on JSONString rather than the
101
+ # ActiveSupport one.
102
+ #
103
+ # However, we can't use JSON.generate on the non-cached output.
104
+ # Doing so means that its `data` field contents are converted
105
+ # with a non-ActiveSupport `to_json`. This means cached and
106
+ # non-cached responses have subtle differences in how their
107
+ # resources are serialized. For example:
108
+ #
109
+ # x = Time.new(2021,1,1)
110
+ #
111
+ # x.to_json
112
+ # => "\"2021-01-01T00:00:00.000+00:00\""
113
+ #
114
+ # JSON.generate x
115
+ # => "\"2021-01-01 00:00:00 +0000\""
116
+ #
117
+ # The different outputs mean we need to take different
118
+ # approaches when converting the entire payload into JSON,
119
+ # hence the check below.
120
+ jsonapi_hash = renderer.render(resources, options, self)
53
121
 
54
- ActiveSupport::Notifications.instrument('render.jsonapi-rails',
55
- resources: resources,
56
- options: options) do
57
- renderer.render(resources, options, self).to_json
122
+ if jsonapi_hash[:data]&.first&.class == JSONAPI::Renderer::CachedResourcesProcessor::JSONString
123
+ JSON.generate jsonapi_hash
124
+ else
125
+ jsonapi_hash.to_json
126
+ end
58
127
  end
59
128
  end
60
129
  end
@@ -25,7 +25,7 @@ module JSONAPI
25
25
  end
26
26
 
27
27
  def as_jsonapi
28
- @errors.keys.flat_map do |key|
28
+ error_keys.flat_map do |key|
29
29
  @errors.full_messages_for(key).map do |message|
30
30
  SerializableActiveModelError.new(field: key, message: message,
31
31
  pointer: @reverse_mapping[key])
@@ -33,6 +33,12 @@ module JSONAPI
33
33
  end
34
34
  end
35
35
  end
36
+
37
+ private
38
+
39
+ def error_keys
40
+ @errors.respond_to?(:attribute_names) ? @errors.attribute_names : @errors.keys
41
+ end
36
42
  end
37
43
  end
38
44
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsonapi-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lucas Hosseini
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-03-07 00:00:00.000000000 Z
11
+ date: 2022-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jsonapi-rb
@@ -42,14 +42,14 @@ dependencies:
42
42
  name: rails
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '5.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '5.0'
55
55
  - !ruby/object:Gem::Dependency
@@ -58,26 +58,26 @@ dependencies:
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: '1.3'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: '1.3'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rake
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - "~>"
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
75
  version: '11.3'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - "~>"
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '11.3'
83
83
  - !ruby/object:Gem::Dependency
@@ -142,6 +142,7 @@ files:
142
142
  - lib/jsonapi/rails/controller/deserialization.rb
143
143
  - lib/jsonapi/rails/controller/hooks.rb
144
144
  - lib/jsonapi/rails/deserializable_resource.rb
145
+ - lib/jsonapi/rails/filter_media_type.rb
145
146
  - lib/jsonapi/rails/log_subscriber.rb
146
147
  - lib/jsonapi/rails/logging.rb
147
148
  - lib/jsonapi/rails/railtie.rb
@@ -152,7 +153,7 @@ homepage: https://github.com/jsonapi-rb/jsonapi-rails
152
153
  licenses:
153
154
  - MIT
154
155
  metadata: {}
155
- post_install_message:
156
+ post_install_message:
156
157
  rdoc_options: []
157
158
  require_paths:
158
159
  - lib
@@ -167,8 +168,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
167
168
  - !ruby/object:Gem::Version
168
169
  version: '0'
169
170
  requirements: []
170
- rubygems_version: 3.0.2
171
- signing_key:
171
+ rubygems_version: 3.3.7
172
+ signing_key:
172
173
  specification_version: 4
173
174
  summary: jsonapi-rb integrations for Rails.
174
175
  test_files: []