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.
Files changed (38) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +106 -38
  3. data/lib/extensions/has_one.rb +19 -13
  4. data/lib/fast_jsonapi.rb +2 -0
  5. data/lib/fast_jsonapi/instrumentation.rb +2 -0
  6. data/lib/fast_jsonapi/instrumentation/serializable_hash.rb +15 -0
  7. data/lib/fast_jsonapi/instrumentation/serialized_json.rb +15 -0
  8. data/lib/fast_jsonapi/instrumentation/skylight.rb +2 -0
  9. data/lib/fast_jsonapi/instrumentation/skylight/normalizers/serializable_hash.rb +22 -0
  10. data/lib/fast_jsonapi/instrumentation/skylight/normalizers/serialized_json.rb +22 -0
  11. data/lib/fast_jsonapi/multi_to_json.rb +92 -0
  12. data/lib/fast_jsonapi/object_serializer.rb +120 -91
  13. data/lib/fast_jsonapi/serialization_core.rb +44 -32
  14. data/lib/generators/serializer/USAGE +8 -0
  15. data/lib/generators/serializer/serializer_generator.rb +19 -0
  16. data/lib/generators/serializer/templates/serializer.rb.tt +6 -0
  17. metadata +48 -88
  18. data/.document +0 -5
  19. data/.rspec +0 -1
  20. data/Gemfile +0 -4
  21. data/Gemfile.lock +0 -158
  22. data/README.rdoc +0 -231
  23. data/Rakefile +0 -55
  24. data/VERSION +0 -1
  25. data/docs/collection_serializer_output.json +0 -35
  26. data/docs/object_serializer.json +0 -30
  27. data/fast_jsonapi.gemspec +0 -108
  28. data/spec/lib/extensions/active_record_spec.rb +0 -67
  29. data/spec/lib/object_serializer_caching_spec.rb +0 -68
  30. data/spec/lib/object_serializer_class_methods_spec.rb +0 -69
  31. data/spec/lib/object_serializer_hyphen_spec.rb +0 -40
  32. data/spec/lib/object_serializer_performance_spec.rb +0 -87
  33. data/spec/lib/object_serializer_spec.rb +0 -126
  34. data/spec/lib/object_serializer_struct_spec.rb +0 -31
  35. data/spec/lib/serialization_core_spec.rb +0 -84
  36. data/spec/shared/contexts/ams_context.rb +0 -83
  37. data/spec/shared/contexts/movie_context.rb +0 -192
  38. data/spec/spec_helper.rb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c5fa6a70751601bec882ccec2e0e70b3b76fd084
4
- data.tar.gz: 307516586bee4df357afb385ee89776bc118cd13
2
+ SHA256:
3
+ metadata.gz: fed8f385f05f5ea345918e1e5310e3b0a36b1dfe0c7a5cf001d7eddba8374e38
4
+ data.tar.gz: fb2c1aac0e54a6dae6a6c1838005995531f25b1689168caed6ecf6f6602ddc0d
5
5
  SHA512:
6
- metadata.gz: 982f868f471f0ad64e8d424ba3fe1313f0c14ee841b87ce8920d68816ef8d306f18b5b39be13eaffdf7c00ed888be3059195c7008f8541dfeefe2036ebd4c1ad
7
- data.tar.gz: 27aef6f9b9e7fb5b94f881c190814dcc7d44d6919118cf732f42a77fe5bf941644bcfbef9986417926a68981d3c00bcfc35276504a0294b2dd2445968b4cab1e
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
- [![NetflixOSS Lifecycle](https://img.shields.io/osslifecycle/Netflix/osstracker.svg)]()
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": "232",
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
- ## Contributing
251
+ `fast_jsonapi` also has builtin [Skylight](https://www.skylight.io/) integration. To enable, add the following to an initializer:
180
252
 
181
- This gem is built using a gem building gem called [juwelier](https://github.com/flajann2/juwelier).
253
+ ```ruby
254
+ require 'fast_jsonapi/instrumentation/skylight'
255
+ ```
182
256
 
183
- Beyond just editing source code, you’ll be interacting with the gem using rake a lot. To see all the tasks available with a brief description, you can run:
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
- ```bash
186
- rake -T
259
+ ```ruby
260
+ require 'fast_jsonapi/instrumentation'
187
261
  ```
188
262
 
189
- ### Updating Project information
190
- You can update the project information of the gem by updating the [Rakefile](Rakefile). Then you need to generate a new gemspec:
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
- ```bash
193
- rake gemspec
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 rake task:
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
- rake spec
287
+ rspec
201
288
  ```
202
289
 
203
- ### Installation
290
+ To run tests without the performance tests (for quicker test runs):
204
291
 
205
292
  ```bash
206
- $ rake install
293
+ rspec spec --tag ~performance:true
207
294
  ```
208
295
 
209
- The install rake task builds the gem and then installs it. You're all
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
- # version:write like before
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!
@@ -1,16 +1,22 @@
1
- require 'active_record'
1
+ # frozen_string_literal: true
2
2
 
3
- ::ActiveRecord::Associations::Builder::HasOne.class_eval do
4
- # Based on
5
- # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/builder/collection_association.rb#L50
6
- # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/builder/singular_association.rb#L11
7
- def self.define_accessors(mixin, reflection)
8
- super
9
- name = reflection.name
10
- mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
11
- def #{name.to_s}_id
12
- association(:#{name}).reader.id
13
- end
14
- CODE
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FastJsonapi
2
4
  require 'fast_jsonapi/object_serializer'
3
5
  require 'extensions/has_one'
@@ -0,0 +1,2 @@
1
+ require 'fast_jsonapi/instrumentation/serializable_hash'
2
+ require 'fast_jsonapi/instrumentation/serialized_json'
@@ -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,2 @@
1
+ require 'fast_jsonapi/instrumentation/skylight/normalizers/serializable_hash'
2
+ require 'fast_jsonapi/instrumentation/skylight/normalizers/serialized_json'
@@ -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