fast_jsonapi 1.0.17 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +106 -38
- data/lib/extensions/has_one.rb +19 -13
- data/lib/fast_jsonapi.rb +2 -0
- data/lib/fast_jsonapi/instrumentation.rb +2 -0
- data/lib/fast_jsonapi/instrumentation/serializable_hash.rb +15 -0
- data/lib/fast_jsonapi/instrumentation/serialized_json.rb +15 -0
- data/lib/fast_jsonapi/instrumentation/skylight.rb +2 -0
- data/lib/fast_jsonapi/instrumentation/skylight/normalizers/serializable_hash.rb +22 -0
- data/lib/fast_jsonapi/instrumentation/skylight/normalizers/serialized_json.rb +22 -0
- data/lib/fast_jsonapi/multi_to_json.rb +92 -0
- data/lib/fast_jsonapi/object_serializer.rb +120 -91
- data/lib/fast_jsonapi/serialization_core.rb +44 -32
- data/lib/generators/serializer/USAGE +8 -0
- data/lib/generators/serializer/serializer_generator.rb +19 -0
- data/lib/generators/serializer/templates/serializer.rb.tt +6 -0
- metadata +48 -88
- data/.document +0 -5
- data/.rspec +0 -1
- data/Gemfile +0 -4
- data/Gemfile.lock +0 -158
- data/README.rdoc +0 -231
- data/Rakefile +0 -55
- data/VERSION +0 -1
- data/docs/collection_serializer_output.json +0 -35
- data/docs/object_serializer.json +0 -30
- data/fast_jsonapi.gemspec +0 -108
- data/spec/lib/extensions/active_record_spec.rb +0 -67
- data/spec/lib/object_serializer_caching_spec.rb +0 -68
- data/spec/lib/object_serializer_class_methods_spec.rb +0 -69
- data/spec/lib/object_serializer_hyphen_spec.rb +0 -40
- data/spec/lib/object_serializer_performance_spec.rb +0 -87
- data/spec/lib/object_serializer_spec.rb +0 -126
- data/spec/lib/object_serializer_struct_spec.rb +0 -31
- data/spec/lib/serialization_core_spec.rb +0 -84
- data/spec/shared/contexts/ams_context.rb +0 -83
- data/spec/shared/contexts/movie_context.rb +0 -192
- data/spec/spec_helper.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: fed8f385f05f5ea345918e1e5310e3b0a36b1dfe0c7a5cf001d7eddba8374e38
|
4
|
+
data.tar.gz: fb2c1aac0e54a6dae6a6c1838005995531f25b1689168caed6ecf6f6602ddc0d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f935259ae0fcaa99dfb30d048d1fde82c99aba7d9abd21a4162cfad35b7448614cf44086414b9e0980bc9737794db31350124f7b9533968f97563c2f7fafbe76
|
7
|
+
data.tar.gz: e20e432a1a4e03f08d055cb134a894afe14e12a8c714be5330c1cc8259e6a592ff9ab4869156dc85af67ee21005bf6769660d872e8be5a9c2461b0530876d8bd
|
data/README.md
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# Fast JSON API
|
2
2
|
|
3
|
-
[![
|
3
|
+
[![Build Status](https://travis-ci.org/Netflix/fast_jsonapi.svg?branch=master)](https://travis-ci.org/Netflix/fast_jsonapi)
|
4
4
|
|
5
5
|
A lightning fast [JSON:API](http://jsonapi.org/) serializer for Ruby Objects.
|
6
6
|
|
7
7
|
# Performance Comparison
|
8
8
|
|
9
|
-
We compare serialization times with Active Model Serializer as part of RSpec performance tests included on this library. We want to ensure that with every change on this library, serialization time is at least `25 times` faster than Active Model Serializers on up to current benchmark of 1000 records.
|
9
|
+
We compare serialization times with Active Model Serializer as part of RSpec performance tests included on this library. We want to ensure that with every change on this library, serialization time is at least `25 times` faster than Active Model Serializers on up to current benchmark of 1000 records. Please read the [performance document](https://github.com/Netflix/fast_jsonapi/blob/master/performance_methodology.md) for any questions related to methodology.
|
10
10
|
|
11
11
|
## Benchmark times for 250 records
|
12
12
|
|
@@ -21,10 +21,12 @@ Fast JSON API serialized 250 records in 3.01 ms
|
|
21
21
|
* [Features](#features)
|
22
22
|
* [Installation](#installation)
|
23
23
|
* [Usage](#usage)
|
24
|
+
* [Rails Generator](#rails-generator)
|
24
25
|
* [Model Definition](#model-definition)
|
25
26
|
* [Serializer Definition](#serializer-definition)
|
26
27
|
* [Object Serialization](#object-serialization)
|
27
28
|
* [Compound Document](#compound-document)
|
29
|
+
* [Key Transforms](#key-transforms)
|
28
30
|
* [Collection Serialization](#collection-serialization)
|
29
31
|
* [Caching](#caching)
|
30
32
|
* [Contributing](#contributing)
|
@@ -54,11 +56,19 @@ $ bundle install
|
|
54
56
|
|
55
57
|
## Usage
|
56
58
|
|
59
|
+
### Rails Generator
|
60
|
+
You can use the bundled generator if you are using the library inside of
|
61
|
+
a Rails project:
|
62
|
+
|
63
|
+
rails g Serializer Movie name year
|
64
|
+
|
65
|
+
This will create a new serializer in `app/serializers/movie_serializer.rb`
|
66
|
+
|
57
67
|
### Model Definition
|
58
68
|
|
59
69
|
```ruby
|
60
70
|
class Movie
|
61
|
-
attr_accessor :id, :name, :year, :actor_ids, :owner_id
|
71
|
+
attr_accessor :id, :name, :year, :actor_ids, :owner_id, :movie_type_id
|
62
72
|
end
|
63
73
|
```
|
64
74
|
|
@@ -68,6 +78,7 @@ end
|
|
68
78
|
class MovieSerializer
|
69
79
|
include FastJsonapi::ObjectSerializer
|
70
80
|
set_type :movie # optional
|
81
|
+
set_id :owner_id # optional
|
71
82
|
attributes :name, :year
|
72
83
|
has_many :actors
|
73
84
|
belongs_to :owner, record_type: :user
|
@@ -104,7 +115,7 @@ json_string = MovieSerializer.new(movie).serialized_json
|
|
104
115
|
```json
|
105
116
|
{
|
106
117
|
"data": {
|
107
|
-
"id": "
|
118
|
+
"id": "3",
|
108
119
|
"type": "movie",
|
109
120
|
"attributes": {
|
110
121
|
"name": "test movie",
|
@@ -134,6 +145,65 @@ json_string = MovieSerializer.new(movie).serialized_json
|
|
134
145
|
}
|
135
146
|
|
136
147
|
```
|
148
|
+
|
149
|
+
### Key Transforms
|
150
|
+
By default fast_jsonapi underscores the key names. It supports the same key transforms that are supported by AMS. Here is the syntax of specifying a key transform
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
class MovieSerializer
|
154
|
+
include FastJsonapi::ObjectSerializer
|
155
|
+
# Available options :camel, :camel_lower, :dash, :underscore(default)
|
156
|
+
set_key_transform :camel
|
157
|
+
end
|
158
|
+
```
|
159
|
+
Here are examples of how these options transform the keys
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
set_key_transform :camel # "some_key" => "SomeKey"
|
163
|
+
set_key_transform :camel_lower # "some_key" => "someKey"
|
164
|
+
set_key_transform :dash # "some_key" => "some-key"
|
165
|
+
set_key_transform :underscore # "some_key" => "some_key"
|
166
|
+
```
|
167
|
+
|
168
|
+
### Attributes
|
169
|
+
Attributes are defined in FastJsonapi using the `attributes` method. This method is also aliased as `attribute`, which is useful when defining a single attribute.
|
170
|
+
|
171
|
+
By default, attributes are read directly from the model property of the same name. In this example, `name` is expected to be a property of the object being serialized:
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
class MovieSerializer
|
175
|
+
include FastJsonapi::ObjectSerializer
|
176
|
+
|
177
|
+
attribute :name
|
178
|
+
end
|
179
|
+
```
|
180
|
+
|
181
|
+
Custom attributes that must be serialized but do not exist on the model can be declared using Ruby block syntax:
|
182
|
+
|
183
|
+
```ruby
|
184
|
+
class MovieSerializer
|
185
|
+
include FastJsonapi::ObjectSerializer
|
186
|
+
|
187
|
+
attributes :name, :year
|
188
|
+
|
189
|
+
attribute :name_with_year do |object|
|
190
|
+
"#{object.name} (#{object.year})"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
```
|
194
|
+
|
195
|
+
The block syntax can also be used to override the property on the object:
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
class MovieSerializer
|
199
|
+
include FastJsonapi::ObjectSerializer
|
200
|
+
|
201
|
+
attribute :name do |object|
|
202
|
+
"#{object.name} Part 2"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
```
|
206
|
+
|
137
207
|
### Compound Document
|
138
208
|
|
139
209
|
Support for top-level included member through ` options[:include] `.
|
@@ -169,68 +239,66 @@ end
|
|
169
239
|
Option | Purpose | Example
|
170
240
|
------------ | ------------- | -------------
|
171
241
|
set_type | Type name of Object | ```set_type :movie ```
|
242
|
+
set_id | ID of Object | ```set_id :owner_id ```
|
172
243
|
cache_options | Hash to enable caching and set cache length | ```cache_options enabled: true, cache_length: 12.hours```
|
173
244
|
id_method_name | Set custom method name to get ID of an object | ```has_many :locations, id_method_name: :place_ids ```
|
174
245
|
object_method_name | Set custom method name to get related objects | ```has_many :locations, object_method_name: :places ```
|
175
246
|
record_type | Set custom Object Type for a relationship | ```belongs_to :owner, record_type: :user```
|
176
247
|
serializer | Set custom Serializer for a relationship | ```has_many :actors, serializer: :custom_actor```
|
177
248
|
|
249
|
+
### Instrumentation
|
178
250
|
|
179
|
-
|
251
|
+
`fast_jsonapi` also has builtin [Skylight](https://www.skylight.io/) integration. To enable, add the following to an initializer:
|
180
252
|
|
181
|
-
|
253
|
+
```ruby
|
254
|
+
require 'fast_jsonapi/instrumentation/skylight'
|
255
|
+
```
|
182
256
|
|
183
|
-
|
257
|
+
Skylight relies on `ActiveSupport::Notifications` to track these two core methods. If you would like to use these notifications without using Skylight, simply require the instrumentation integration:
|
184
258
|
|
185
|
-
```
|
186
|
-
|
259
|
+
```ruby
|
260
|
+
require 'fast_jsonapi/instrumentation'
|
187
261
|
```
|
188
262
|
|
189
|
-
|
190
|
-
|
263
|
+
The two instrumented notifcations are supplied by these two constants:
|
264
|
+
* `FastJsonapi::ObjectSerializer::SERIALIZABLE_HASH_NOTIFICATION`
|
265
|
+
* `FastJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION`
|
191
266
|
|
192
|
-
|
193
|
-
|
267
|
+
It is also possible to instrument one method without the other by using one of the following require statements:
|
268
|
+
|
269
|
+
```ruby
|
270
|
+
require 'fast_jsonapi/instrumentation/serializable_hash'
|
271
|
+
require 'fast_jsonapi/instrumentation/serialized_json'
|
194
272
|
```
|
195
273
|
|
274
|
+
Same goes for the Skylight integration:
|
275
|
+
```ruby
|
276
|
+
require 'fast_jsonapi/instrumentation/skylight/normalizers/serializable_hash'
|
277
|
+
require 'fast_jsonapi/instrumentation/skylight/normalizers/serialized_json'
|
278
|
+
```
|
279
|
+
|
280
|
+
## Contributing
|
281
|
+
Please see [contribution check](https://github.com/Netflix/fast_jsonapi/blob/master/CONTRIBUTING.md) for more details on contributing
|
282
|
+
|
196
283
|
### Running Tests
|
197
|
-
We use [RSpec](http://rspec.info/) for testing. We have unit tests, functional tests and performance tests. To run tests use the following
|
284
|
+
We use [RSpec](http://rspec.info/) for testing. We have unit tests, functional tests and performance tests. To run tests use the following command:
|
198
285
|
|
199
286
|
```bash
|
200
|
-
|
287
|
+
rspec
|
201
288
|
```
|
202
289
|
|
203
|
-
|
290
|
+
To run tests without the performance tests (for quicker test runs):
|
204
291
|
|
205
292
|
```bash
|
206
|
-
|
293
|
+
rspec spec --tag ~performance:true
|
207
294
|
```
|
208
295
|
|
209
|
-
|
210
|
-
set if you're using [RVM](http://rvm.beginrescueend.com/), but you may
|
211
|
-
need to run it with sudo if you have a system-installed Ruby:
|
212
|
-
|
213
|
-
### Bumping Version
|
214
|
-
|
215
|
-
It feels good to release code. Do it, do it often. But before that, bump
|
216
|
-
the version. Then release it. There's a few ways to update the version:
|
296
|
+
To run tests only performance tests:
|
217
297
|
|
218
298
|
```bash
|
219
|
-
|
220
|
-
$ rake version:write MAJOR=0 MINOR=3 PATCH=0
|
221
|
-
|
222
|
-
# bump just major, ie 0.1.0 -> 1.0.0
|
223
|
-
$ rake version:bump:major
|
224
|
-
|
225
|
-
# bump just minor, ie 0.1.0 -> 0.2.0
|
226
|
-
$ rake version:bump:minor
|
227
|
-
|
228
|
-
# bump just patch, ie 0.1.0 -> 0.1.1
|
229
|
-
$ rake version:bump:patch
|
299
|
+
rspec spec --tag performance:true
|
230
300
|
```
|
231
301
|
|
232
|
-
---
|
233
|
-
|
234
302
|
### We're Hiring!
|
235
303
|
|
236
304
|
Join the Netflix Studio Engineering team and help us build gems like this!
|
data/lib/extensions/has_one.rb
CHANGED
@@ -1,16 +1,22 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
mixin
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
3
|
+
begin
|
4
|
+
require 'active_record'
|
5
|
+
|
6
|
+
::ActiveRecord::Associations::Builder::HasOne.class_eval do
|
7
|
+
# Based on
|
8
|
+
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/builder/collection_association.rb#L50
|
9
|
+
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/builder/singular_association.rb#L11
|
10
|
+
def self.define_accessors(mixin, reflection)
|
11
|
+
super
|
12
|
+
name = reflection.name
|
13
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
14
|
+
def #{name}_id
|
15
|
+
association(:#{name}).reader.try(:id)
|
16
|
+
end
|
17
|
+
CODE
|
18
|
+
end
|
15
19
|
end
|
20
|
+
rescue LoadError
|
21
|
+
# active_record can't be loaded so we shouldn't try to monkey-patch it.
|
16
22
|
end
|
data/lib/fast_jsonapi.rb
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'active_support/notifications'
|
2
|
+
|
3
|
+
module FastJsonapi
|
4
|
+
module ObjectSerializer
|
5
|
+
|
6
|
+
alias_method :serializable_hash_without_instrumentation, :serializable_hash
|
7
|
+
|
8
|
+
def serializable_hash
|
9
|
+
ActiveSupport::Notifications.instrument(SERIALIZABLE_HASH_NOTIFICATION, { name: self.class.name }) do
|
10
|
+
serializable_hash_without_instrumentation
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'active_support/notifications'
|
2
|
+
|
3
|
+
module FastJsonapi
|
4
|
+
module ObjectSerializer
|
5
|
+
|
6
|
+
alias_method :serialized_json_without_instrumentation, :serialized_json
|
7
|
+
|
8
|
+
def serialized_json
|
9
|
+
ActiveSupport::Notifications.instrument(SERIALIZED_JSON_NOTIFICATION, { name: self.class.name }) do
|
10
|
+
serialized_json_without_instrumentation
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'skylight'
|
2
|
+
require 'fast_jsonapi/instrumentation/serializable_hash'
|
3
|
+
|
4
|
+
module FastJsonapi
|
5
|
+
module Instrumentation
|
6
|
+
module Skylight
|
7
|
+
module Normalizers
|
8
|
+
class SerializableHash < Skylight::Normalizers::Normalizer
|
9
|
+
|
10
|
+
register FastJsonapi::ObjectSerializer::SERIALIZABLE_HASH_NOTIFICATION
|
11
|
+
|
12
|
+
CAT = "view.#{FastJsonapi::ObjectSerializer::SERIALIZABLE_HASH_NOTIFICATION}".freeze
|
13
|
+
|
14
|
+
def normalize(trace, name, payload)
|
15
|
+
[ CAT, payload[:name], nil ]
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'skylight'
|
2
|
+
require 'fast_jsonapi/instrumentation/serializable_hash'
|
3
|
+
|
4
|
+
module FastJsonapi
|
5
|
+
module Instrumentation
|
6
|
+
module Skylight
|
7
|
+
module Normalizers
|
8
|
+
class SerializedJson < Skylight::Normalizers::Normalizer
|
9
|
+
|
10
|
+
register FastJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION
|
11
|
+
|
12
|
+
CAT = "view.#{FastJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION}".freeze
|
13
|
+
|
14
|
+
def normalize(trace, name, payload)
|
15
|
+
[ CAT, payload[:name], nil ]
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Usage:
|
4
|
+
# class Movie
|
5
|
+
# def to_json(payload)
|
6
|
+
# FastJsonapi::MultiToJson.to_json(payload)
|
7
|
+
# end
|
8
|
+
# end
|
9
|
+
module FastJsonapi
|
10
|
+
module MultiToJson
|
11
|
+
# Result object pattern is from https://johnnunemaker.com/resilience-in-ruby/
|
12
|
+
# e.g. https://github.com/github/github-ds/blob/fbda5389711edfb4c10b6c6bad19311dfcb1bac1/lib/github/result.rb
|
13
|
+
class Result
|
14
|
+
def initialize(*rescued_exceptions)
|
15
|
+
rescued_exceptions = [StandardError] if rescued_exceptions.empty?
|
16
|
+
@value = yield
|
17
|
+
@error = nil
|
18
|
+
rescue *rescued_exceptions => e
|
19
|
+
@error = e
|
20
|
+
end
|
21
|
+
|
22
|
+
def ok?
|
23
|
+
@error.nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
def value!
|
27
|
+
if ok?
|
28
|
+
@value
|
29
|
+
else
|
30
|
+
raise @error
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def rescue
|
35
|
+
return self if ok?
|
36
|
+
Result.new { yield(@error) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.logger(device=nil)
|
41
|
+
return @logger = Logger.new(device) if device
|
42
|
+
@logger ||= Logger.new(IO::NULL)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Encoder-compatible with default MultiJSON adapters and defaults
|
46
|
+
def self.to_json_method
|
47
|
+
encode_method = String.new(%(def _fast_to_json(object)\n ))
|
48
|
+
encode_method << Result.new(LoadError) {
|
49
|
+
require 'oj'
|
50
|
+
%(::Oj.dump(object, mode: :compat, time_format: :ruby, use_to_json: true))
|
51
|
+
}.rescue {
|
52
|
+
require 'yajl'
|
53
|
+
%(::Yajl::Encoder.encode(object))
|
54
|
+
}.rescue {
|
55
|
+
require 'jrjackson' unless defined?(::JrJackson)
|
56
|
+
%(::JrJackson::Json.dump(object))
|
57
|
+
}.rescue {
|
58
|
+
require 'json'
|
59
|
+
%(JSON.fast_generate(object, create_additions: false, quirks_mode: true))
|
60
|
+
}.rescue {
|
61
|
+
require 'gson'
|
62
|
+
%(::Gson::Encoder.new({}).encode(object))
|
63
|
+
}.rescue {
|
64
|
+
require 'active_support/json/encoding'
|
65
|
+
%(::ActiveSupport::JSON.encode(object))
|
66
|
+
}.rescue {
|
67
|
+
warn "No JSON encoder found. Falling back to `object.to_json`"
|
68
|
+
%(object.to_json)
|
69
|
+
}.value!
|
70
|
+
encode_method << "\nend"
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.to_json(object)
|
74
|
+
_fast_to_json(object)
|
75
|
+
rescue NameError
|
76
|
+
define_to_json(FastJsonapi::MultiToJson)
|
77
|
+
_fast_to_json(object)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.define_to_json(receiver)
|
81
|
+
cl = caller_locations[0]
|
82
|
+
method_body = to_json_method
|
83
|
+
logger.debug { "Defining #{receiver}._fast_to_json as #{method_body.inspect}" }
|
84
|
+
receiver.instance_eval method_body, cl.absolute_path, cl.lineno
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.reset_to_json!
|
88
|
+
undef :_fast_to_json if method_defined?(:_fast_to_json)
|
89
|
+
logger.debug { "Undefining #{receiver}._fast_to_json" }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|