fast_jsonapi 1.1.1 → 1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +85 -10
- data/lib/extensions/has_one.rb +3 -5
- data/lib/fast_jsonapi/instrumentation/skylight/normalizers/base.rb +7 -0
- data/lib/fast_jsonapi/instrumentation/skylight/normalizers/serializable_hash.rb +2 -2
- data/lib/fast_jsonapi/instrumentation/skylight/normalizers/serialized_json.rb +2 -2
- data/lib/fast_jsonapi/multi_to_json.rb +2 -0
- data/lib/fast_jsonapi/object_serializer.rb +82 -64
- data/lib/fast_jsonapi/serialization_core.rb +108 -38
- data/lib/fast_jsonapi/version.rb +3 -0
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 346edf85242b61d0747883aaa7b62da16b1a8fec
|
4
|
+
data.tar.gz: 36d21132fe65b9c88e2f1afce9aa935ae094d7ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9bb346d89aa234162c0bdc5f269e82735890ba6ebe60bb6e448506ef1d2d299c08be234b8acf4827fdc8860bee1439cc5dded54107b857ed536d2229c894519c
|
7
|
+
data.tar.gz: 3e4f56de96e171a931a5479edd16600fd6bc3fb586b1152a8b63a4e39fc73075d01a276bba831f577d74a951c494a5744d584e3a33f482c4139f410db40ebffb
|
data/README.md
CHANGED
@@ -29,6 +29,7 @@ Fast JSON API serialized 250 records in 3.01 ms
|
|
29
29
|
* [Key Transforms](#key-transforms)
|
30
30
|
* [Collection Serialization](#collection-serialization)
|
31
31
|
* [Caching](#caching)
|
32
|
+
* [Params](#params)
|
32
33
|
* [Contributing](#contributing)
|
33
34
|
|
34
35
|
|
@@ -60,7 +61,7 @@ $ bundle install
|
|
60
61
|
You can use the bundled generator if you are using the library inside of
|
61
62
|
a Rails project:
|
62
63
|
|
63
|
-
rails g
|
64
|
+
rails g serializer Movie name year
|
64
65
|
|
65
66
|
This will create a new serializer in `app/serializers/movie_serializer.rb`
|
66
67
|
|
@@ -173,7 +174,7 @@ By default, attributes are read directly from the model property of the same nam
|
|
173
174
|
```ruby
|
174
175
|
class MovieSerializer
|
175
176
|
include FastJsonapi::ObjectSerializer
|
176
|
-
|
177
|
+
|
177
178
|
attribute :name
|
178
179
|
end
|
179
180
|
```
|
@@ -183,9 +184,9 @@ Custom attributes that must be serialized but do not exist on the model can be d
|
|
183
184
|
```ruby
|
184
185
|
class MovieSerializer
|
185
186
|
include FastJsonapi::ObjectSerializer
|
186
|
-
|
187
|
+
|
187
188
|
attributes :name, :year
|
188
|
-
|
189
|
+
|
189
190
|
attribute :name_with_year do |object|
|
190
191
|
"#{object.name} (#{object.year})"
|
191
192
|
end
|
@@ -197,21 +198,51 @@ The block syntax can also be used to override the property on the object:
|
|
197
198
|
```ruby
|
198
199
|
class MovieSerializer
|
199
200
|
include FastJsonapi::ObjectSerializer
|
200
|
-
|
201
|
+
|
201
202
|
attribute :name do |object|
|
202
203
|
"#{object.name} Part 2"
|
203
204
|
end
|
204
205
|
end
|
205
206
|
```
|
206
207
|
|
208
|
+
### Links Per Object
|
209
|
+
Links are defined in FastJsonapi using the `link` method. By default, link are read directly from the model property of the same name.In this example, `public_url` is expected to be a property of the object being serialized.
|
210
|
+
|
211
|
+
You can configure the method to use on the object for example a link with key `self` will get set to the value returned by a method called `url` on the movie object.
|
212
|
+
|
213
|
+
You can also use a block to define a url as shown in `custom_url`. You can access params in these blocks as well as shown in `personalized_url`
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
class MovieSerializer
|
217
|
+
include FastJsonapi::ObjectSerializer
|
218
|
+
|
219
|
+
link :public_url
|
220
|
+
|
221
|
+
link :self, :url
|
222
|
+
|
223
|
+
link :custom_url do |object|
|
224
|
+
"http://movies.com/#{object.name}-(#{object.year})"
|
225
|
+
end
|
226
|
+
|
227
|
+
link :personalized_url do |object, params|
|
228
|
+
"http://movies.com/#{object.name}-#{params[:user].reference_code}"
|
229
|
+
end
|
230
|
+
end
|
231
|
+
```
|
232
|
+
|
207
233
|
### Compound Document
|
208
234
|
|
209
|
-
Support for top-level included
|
235
|
+
Support for top-level and nested included associations through ` options[:include] `.
|
210
236
|
|
211
237
|
```ruby
|
212
238
|
options = {}
|
213
239
|
options[:meta] = { total: 2 }
|
214
|
-
options[:
|
240
|
+
options[:links] = {
|
241
|
+
self: '...',
|
242
|
+
next: '...',
|
243
|
+
prev: '...'
|
244
|
+
}
|
245
|
+
options[:include] = [:actors, :'actors.agency', :'actors.agency.state']
|
215
246
|
MovieSerializer.new([movie, movie], options).serialized_json
|
216
247
|
```
|
217
248
|
|
@@ -219,11 +250,17 @@ MovieSerializer.new([movie, movie], options).serialized_json
|
|
219
250
|
|
220
251
|
```ruby
|
221
252
|
options[:meta] = { total: 2 }
|
253
|
+
options[:links] = {
|
254
|
+
self: '...',
|
255
|
+
next: '...',
|
256
|
+
prev: '...'
|
257
|
+
}
|
222
258
|
hash = MovieSerializer.new([movie, movie], options).serializable_hash
|
223
259
|
json_string = MovieSerializer.new([movie, movie], options).serialized_json
|
224
260
|
```
|
225
261
|
|
226
262
|
### Caching
|
263
|
+
Requires a `cache_key` method be defined on model:
|
227
264
|
|
228
265
|
```ruby
|
229
266
|
class MovieSerializer
|
@@ -234,17 +271,56 @@ class MovieSerializer
|
|
234
271
|
end
|
235
272
|
```
|
236
273
|
|
274
|
+
### Params
|
275
|
+
|
276
|
+
In some cases, attribute values might require more information than what is
|
277
|
+
available on the record, for example, access privileges or other information
|
278
|
+
related to a current authenticated user. The `options[:params]` value covers these
|
279
|
+
cases by allowing you to pass in a hash of additional parameters necessary for
|
280
|
+
your use case.
|
281
|
+
|
282
|
+
Leveraging the new params is easy, when you define a custom attribute or relationship with a
|
283
|
+
block you opt-in to using params by adding it as a block parameter.
|
284
|
+
|
285
|
+
```ruby
|
286
|
+
class MovieSerializer
|
287
|
+
class MovieSerializer
|
288
|
+
include FastJsonapi::ObjectSerializer
|
289
|
+
|
290
|
+
attributes :name, :year
|
291
|
+
attribute :can_view_early do |movie, params|
|
292
|
+
# in here, params is a hash containing the `:current_user` key
|
293
|
+
params[:current_user].is_employee? ? true : false
|
294
|
+
end
|
295
|
+
|
296
|
+
belongs_to :primary_agent do |movie, params|
|
297
|
+
# in here, params is a hash containing the `:current_user` key
|
298
|
+
params[:current_user].is_employee? ? true : false
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
# ...
|
303
|
+
current_user = User.find(cookies[:current_user_id])
|
304
|
+
serializer = MovieSerializer.new(movie, {params: {current_user: current_user}})
|
305
|
+
serializer.serializable_hash
|
306
|
+
```
|
307
|
+
|
308
|
+
Custom attributes and relationships that only receive the resource are still possible by defining
|
309
|
+
the block to only receive one argument.
|
310
|
+
|
237
311
|
### Customizable Options
|
238
312
|
|
239
313
|
Option | Purpose | Example
|
240
314
|
------------ | ------------- | -------------
|
241
315
|
set_type | Type name of Object | ```set_type :movie ```
|
242
316
|
set_id | ID of Object | ```set_id :owner_id ```
|
243
|
-
cache_options | Hash to enable caching and set cache length | ```cache_options enabled: true, cache_length: 12.hours```
|
317
|
+
cache_options | Hash to enable caching and set cache length | ```cache_options enabled: true, cache_length: 12.hours, race_condition_ttl: 10.seconds```
|
244
318
|
id_method_name | Set custom method name to get ID of an object | ```has_many :locations, id_method_name: :place_ids ```
|
245
319
|
object_method_name | Set custom method name to get related objects | ```has_many :locations, object_method_name: :places ```
|
246
320
|
record_type | Set custom Object Type for a relationship | ```belongs_to :owner, record_type: :user```
|
247
|
-
serializer | Set custom Serializer for a relationship | ```has_many :actors, serializer: :custom_actor```
|
321
|
+
serializer | Set custom Serializer for a relationship | ```has_many :actors, serializer: :custom_actor``` or ```has_many :actors, serializer: MyApp::Api::V1::ActorSerializer```
|
322
|
+
polymorphic | Allows different record types for a polymorphic association | ```has_many :targets, polymorphic: true```
|
323
|
+
polymorphic | Sets custom record types for each object class in a polymorphic association | ```has_many :targets, polymorphic: { Person => :person, Group => :group }```
|
248
324
|
|
249
325
|
### Instrumentation
|
250
326
|
|
@@ -304,4 +380,3 @@ rspec spec --tag performance:true
|
|
304
380
|
Join the Netflix Studio Engineering team and help us build gems like this!
|
305
381
|
|
306
382
|
* [Senior Ruby Engineer](https://jobs.netflix.com/jobs/864893)
|
307
|
-
* [Senior Platform Engineer](https://jobs.netflix.com/jobs/865783)
|
data/lib/extensions/has_one.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
require 'active_record'
|
5
|
-
|
3
|
+
if defined?(::ActiveRecord)
|
6
4
|
::ActiveRecord::Associations::Builder::HasOne.class_eval do
|
7
5
|
# Based on
|
8
6
|
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/builder/collection_association.rb#L50
|
@@ -12,11 +10,11 @@ begin
|
|
12
10
|
name = reflection.name
|
13
11
|
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
14
12
|
def #{name}_id
|
13
|
+
# if an attribute is already defined with this methods name we should just use it
|
14
|
+
return read_attribute(__method__) if has_attribute?(__method__)
|
15
15
|
association(:#{name}).reader.try(:id)
|
16
16
|
end
|
17
17
|
CODE
|
18
18
|
end
|
19
19
|
end
|
20
|
-
rescue LoadError
|
21
|
-
# active_record can't be loaded so we shouldn't try to monkey-patch it.
|
22
20
|
end
|
@@ -1,11 +1,11 @@
|
|
1
|
-
require 'skylight'
|
1
|
+
require 'fast_jsonapi/instrumentation/skylight/normalizers/base'
|
2
2
|
require 'fast_jsonapi/instrumentation/serializable_hash'
|
3
3
|
|
4
4
|
module FastJsonapi
|
5
5
|
module Instrumentation
|
6
6
|
module Skylight
|
7
7
|
module Normalizers
|
8
|
-
class SerializableHash <
|
8
|
+
class SerializableHash < SKYLIGHT_NORMALIZER_BASE_CLASS
|
9
9
|
|
10
10
|
register FastJsonapi::ObjectSerializer::SERIALIZABLE_HASH_NOTIFICATION
|
11
11
|
|
@@ -1,11 +1,11 @@
|
|
1
|
-
require 'skylight'
|
1
|
+
require 'fast_jsonapi/instrumentation/skylight/normalizers/base'
|
2
2
|
require 'fast_jsonapi/instrumentation/serializable_hash'
|
3
3
|
|
4
4
|
module FastJsonapi
|
5
5
|
module Instrumentation
|
6
6
|
module Skylight
|
7
7
|
module Normalizers
|
8
|
-
class SerializedJson <
|
8
|
+
class SerializedJson < SKYLIGHT_NORMALIZER_BASE_CLASS
|
9
9
|
|
10
10
|
register FastJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION
|
11
11
|
|
@@ -10,8 +10,8 @@ module FastJsonapi
|
|
10
10
|
extend ActiveSupport::Concern
|
11
11
|
include SerializationCore
|
12
12
|
|
13
|
-
SERIALIZABLE_HASH_NOTIFICATION = 'render.fast_jsonapi.serializable_hash'
|
14
|
-
SERIALIZED_JSON_NOTIFICATION = 'render.fast_jsonapi.serialized_json'
|
13
|
+
SERIALIZABLE_HASH_NOTIFICATION = 'render.fast_jsonapi.serializable_hash'
|
14
|
+
SERIALIZED_JSON_NOTIFICATION = 'render.fast_jsonapi.serialized_json'
|
15
15
|
|
16
16
|
included do
|
17
17
|
# Set record_type based on the name of the serializer class
|
@@ -34,11 +34,12 @@ module FastJsonapi
|
|
34
34
|
def hash_for_one_record
|
35
35
|
serializable_hash = { data: nil }
|
36
36
|
serializable_hash[:meta] = @meta if @meta.present?
|
37
|
+
serializable_hash[:links] = @links if @links.present?
|
37
38
|
|
38
39
|
return serializable_hash unless @resource
|
39
40
|
|
40
|
-
serializable_hash[:data] = self.class.record_hash(@resource)
|
41
|
-
serializable_hash[:included] = self.class.get_included_records(@resource, @includes, @known_included_objects) if @includes.present?
|
41
|
+
serializable_hash[:data] = self.class.record_hash(@resource, @params)
|
42
|
+
serializable_hash[:included] = self.class.get_included_records(@resource, @includes, @known_included_objects, @params) if @includes.present?
|
42
43
|
serializable_hash
|
43
44
|
end
|
44
45
|
|
@@ -48,13 +49,14 @@ module FastJsonapi
|
|
48
49
|
data = []
|
49
50
|
included = []
|
50
51
|
@resource.each do |record|
|
51
|
-
data << self.class.record_hash(record)
|
52
|
-
included.concat self.class.get_included_records(record, @includes, @known_included_objects) if @includes.present?
|
52
|
+
data << self.class.record_hash(record, @params)
|
53
|
+
included.concat self.class.get_included_records(record, @includes, @known_included_objects, @params) if @includes.present?
|
53
54
|
end
|
54
55
|
|
55
56
|
serializable_hash[:data] = data
|
56
57
|
serializable_hash[:included] = included if @includes.present?
|
57
58
|
serializable_hash[:meta] = @meta if @meta.present?
|
59
|
+
serializable_hash[:links] = @links if @links.present?
|
58
60
|
serializable_hash
|
59
61
|
end
|
60
62
|
|
@@ -69,20 +71,13 @@ module FastJsonapi
|
|
69
71
|
|
70
72
|
@known_included_objects = {}
|
71
73
|
@meta = options[:meta]
|
74
|
+
@links = options[:links]
|
75
|
+
@params = options[:params] || {}
|
76
|
+
raise ArgumentError.new("`params` option passed to serializer must be a hash") unless @params.is_a?(Hash)
|
72
77
|
|
73
78
|
if options[:include].present?
|
74
79
|
@includes = options[:include].delete_if(&:blank?).map(&:to_sym)
|
75
|
-
validate_includes!(@includes)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def validate_includes!(includes)
|
80
|
-
return if includes.blank?
|
81
|
-
|
82
|
-
existing_relationships = self.class.relationships_to_serialize.keys.to_set
|
83
|
-
|
84
|
-
unless existing_relationships.superset?(includes.to_set)
|
85
|
-
raise ArgumentError, "One of keys from #{includes} is not specified as a relationship on the serializer"
|
80
|
+
self.class.validate_includes!(@includes)
|
86
81
|
end
|
87
82
|
end
|
88
83
|
|
@@ -91,6 +86,20 @@ module FastJsonapi
|
|
91
86
|
end
|
92
87
|
|
93
88
|
class_methods do
|
89
|
+
|
90
|
+
def inherited(subclass)
|
91
|
+
super(subclass)
|
92
|
+
subclass.attributes_to_serialize = attributes_to_serialize.dup if attributes_to_serialize.present?
|
93
|
+
subclass.relationships_to_serialize = relationships_to_serialize.dup if relationships_to_serialize.present?
|
94
|
+
subclass.cachable_relationships_to_serialize = cachable_relationships_to_serialize.dup if cachable_relationships_to_serialize.present?
|
95
|
+
subclass.uncachable_relationships_to_serialize = uncachable_relationships_to_serialize.dup if uncachable_relationships_to_serialize.present?
|
96
|
+
subclass.transform_method = transform_method
|
97
|
+
subclass.cache_length = cache_length
|
98
|
+
subclass.race_condition_ttl = race_condition_ttl
|
99
|
+
subclass.data_links = data_links
|
100
|
+
subclass.cached = cached
|
101
|
+
end
|
102
|
+
|
94
103
|
def reflected_record_type
|
95
104
|
return @reflected_record_type if defined?(@reflected_record_type)
|
96
105
|
|
@@ -108,11 +117,11 @@ module FastJsonapi
|
|
108
117
|
dash: :dasherize,
|
109
118
|
underscore: :underscore
|
110
119
|
}
|
111
|
-
|
120
|
+
self.transform_method = mapping[transform_name.to_sym]
|
112
121
|
end
|
113
122
|
|
114
123
|
def run_key_transform(input)
|
115
|
-
if
|
124
|
+
if self.transform_method.present?
|
116
125
|
input.to_s.send(*@transform_method).to_sym
|
117
126
|
else
|
118
127
|
input.to_sym
|
@@ -135,6 +144,7 @@ module FastJsonapi
|
|
135
144
|
def cache_options(cache_options)
|
136
145
|
self.cached = cache_options[:enabled] || false
|
137
146
|
self.cache_length = cache_options[:cache_length] || 5.minutes
|
147
|
+
self.race_condition_ttl = cache_options[:race_condition_ttl] || 5.seconds
|
138
148
|
end
|
139
149
|
|
140
150
|
def attributes(*attributes_list, &block)
|
@@ -162,67 +172,54 @@ module FastJsonapi
|
|
162
172
|
self.relationships_to_serialize[name] = relationship
|
163
173
|
end
|
164
174
|
|
165
|
-
def has_many(relationship_name, options = {})
|
175
|
+
def has_many(relationship_name, options = {}, &block)
|
166
176
|
name = relationship_name.to_sym
|
167
|
-
|
168
|
-
|
169
|
-
key = options[:key] || run_key_transform(relationship_name)
|
170
|
-
record_type = options[:record_type] || run_key_transform(singular_name)
|
171
|
-
relationship = {
|
172
|
-
key: key,
|
173
|
-
name: name,
|
174
|
-
id_method_name: options[:id_method_name] || (singular_name + '_ids').to_sym,
|
175
|
-
record_type: record_type,
|
176
|
-
object_method_name: options[:object_method_name] || name,
|
177
|
-
serializer: compute_serializer_name(serializer_key),
|
178
|
-
relationship_type: :has_many,
|
179
|
-
cached: options[:cached] || false,
|
180
|
-
polymorphic: fetch_polymorphic_option(options)
|
181
|
-
}
|
182
|
-
add_relationship(name, relationship)
|
177
|
+
hash = create_relationship_hash(relationship_name, :has_many, options, block)
|
178
|
+
add_relationship(name, hash)
|
183
179
|
end
|
184
180
|
|
185
|
-
def
|
181
|
+
def has_one(relationship_name, options = {}, &block)
|
186
182
|
name = relationship_name.to_sym
|
187
|
-
|
188
|
-
|
189
|
-
record_type = options[:record_type] || run_key_transform(relationship_name)
|
190
|
-
add_relationship(name, {
|
191
|
-
key: key,
|
192
|
-
name: name,
|
193
|
-
id_method_name: options[:id_method_name] || (relationship_name.to_s + '_id').to_sym,
|
194
|
-
record_type: record_type,
|
195
|
-
object_method_name: options[:object_method_name] || name,
|
196
|
-
serializer: compute_serializer_name(serializer_key),
|
197
|
-
relationship_type: :belongs_to,
|
198
|
-
cached: options[:cached] || true,
|
199
|
-
polymorphic: fetch_polymorphic_option(options)
|
200
|
-
})
|
183
|
+
hash = create_relationship_hash(relationship_name, :has_one, options, block)
|
184
|
+
add_relationship(name, hash)
|
201
185
|
end
|
202
186
|
|
203
|
-
def
|
187
|
+
def belongs_to(relationship_name, options = {}, &block)
|
204
188
|
name = relationship_name.to_sym
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
189
|
+
hash = create_relationship_hash(relationship_name, :belongs_to, options, block)
|
190
|
+
add_relationship(name, hash)
|
191
|
+
end
|
192
|
+
|
193
|
+
def create_relationship_hash(base_key, relationship_type, options, block)
|
194
|
+
name = base_key.to_sym
|
195
|
+
if relationship_type == :has_many
|
196
|
+
base_serialization_key = base_key.to_s.singularize
|
197
|
+
base_key_sym = base_serialization_key.to_sym
|
198
|
+
id_postfix = '_ids'
|
199
|
+
else
|
200
|
+
base_serialization_key = base_key
|
201
|
+
base_key_sym = name
|
202
|
+
id_postfix = '_id'
|
203
|
+
end
|
204
|
+
{
|
205
|
+
key: options[:key] || run_key_transform(base_key),
|
210
206
|
name: name,
|
211
|
-
id_method_name: options[:id_method_name] ||
|
212
|
-
record_type: record_type,
|
207
|
+
id_method_name: options[:id_method_name] || "#{base_serialization_key}#{id_postfix}".to_sym,
|
208
|
+
record_type: options[:record_type] || run_key_transform(base_key_sym),
|
213
209
|
object_method_name: options[:object_method_name] || name,
|
214
|
-
|
215
|
-
|
210
|
+
object_block: block,
|
211
|
+
serializer: compute_serializer_name(options[:serializer] || base_key_sym),
|
212
|
+
relationship_type: relationship_type,
|
216
213
|
cached: options[:cached] || false,
|
217
214
|
polymorphic: fetch_polymorphic_option(options)
|
218
|
-
}
|
215
|
+
}
|
219
216
|
end
|
220
217
|
|
221
218
|
def compute_serializer_name(serializer_key)
|
219
|
+
return serializer_key unless serializer_key.is_a? Symbol
|
222
220
|
namespace = self.name.gsub(/()?\w+Serializer$/, '')
|
223
221
|
serializer_name = serializer_key.to_s.classify + 'Serializer'
|
224
|
-
|
225
|
-
(serializer_key.to_s.classify + 'Serializer').to_sym
|
222
|
+
(namespace + serializer_name).to_sym
|
226
223
|
end
|
227
224
|
|
228
225
|
def fetch_polymorphic_option(options)
|
@@ -231,6 +228,27 @@ module FastJsonapi
|
|
231
228
|
return option if option.respond_to? :keys
|
232
229
|
{}
|
233
230
|
end
|
231
|
+
|
232
|
+
def link(link_name, link_method_name = nil, &block)
|
233
|
+
self.data_links = {} if self.data_links.nil?
|
234
|
+
link_method_name = link_name if link_method_name.nil?
|
235
|
+
key = run_key_transform(link_name)
|
236
|
+
self.data_links[key] = block || link_method_name
|
237
|
+
end
|
238
|
+
|
239
|
+
def validate_includes!(includes)
|
240
|
+
return if includes.blank?
|
241
|
+
|
242
|
+
includes.detect do |include_item|
|
243
|
+
klass = self
|
244
|
+
parse_include_item(include_item).each do |parsed_include|
|
245
|
+
relationship_to_include = klass.relationships_to_serialize[parsed_include]
|
246
|
+
raise ArgumentError, "#{parsed_include} is not specified as a relationship on #{klass.name}" unless relationship_to_include
|
247
|
+
raise NotImplementedError if relationship_to_include[:polymorphic].is_a?(Hash)
|
248
|
+
klass = relationship_to_include[:serializer].to_s.constantize
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
234
252
|
end
|
235
253
|
end
|
236
254
|
end
|
@@ -4,6 +4,8 @@ require 'active_support/concern'
|
|
4
4
|
require 'fast_jsonapi/multi_to_json'
|
5
5
|
|
6
6
|
module FastJsonapi
|
7
|
+
MandatoryField = Class.new(StandardError)
|
8
|
+
|
7
9
|
module SerializationCore
|
8
10
|
extend ActiveSupport::Concern
|
9
11
|
|
@@ -13,16 +15,23 @@ module FastJsonapi
|
|
13
15
|
:relationships_to_serialize,
|
14
16
|
:cachable_relationships_to_serialize,
|
15
17
|
:uncachable_relationships_to_serialize,
|
18
|
+
:transform_method,
|
16
19
|
:record_type,
|
17
20
|
:record_id,
|
18
21
|
:cache_length,
|
19
|
-
:
|
22
|
+
:race_condition_ttl,
|
23
|
+
:cached,
|
24
|
+
:data_links
|
20
25
|
end
|
21
26
|
end
|
22
27
|
|
23
28
|
class_methods do
|
24
|
-
def id_hash(id, record_type)
|
25
|
-
|
29
|
+
def id_hash(id, record_type, default_return=false)
|
30
|
+
if id.present?
|
31
|
+
{ id: id.to_s, type: record_type }
|
32
|
+
else
|
33
|
+
default_return ? { id: nil, type: record_type } : nil
|
34
|
+
end
|
26
35
|
end
|
27
36
|
|
28
37
|
def ids_hash(ids, record_type)
|
@@ -33,19 +42,18 @@ module FastJsonapi
|
|
33
42
|
def id_hash_from_record(record, record_types)
|
34
43
|
# memoize the record type within the record_types dictionary, then assigning to record_type:
|
35
44
|
record_type = record_types[record.class] ||= record.class.name.underscore.to_sym
|
36
|
-
|
45
|
+
id_hash(record.id, record_type)
|
37
46
|
end
|
38
47
|
|
39
|
-
def ids_hash_from_record_and_relationship(record, relationship)
|
48
|
+
def ids_hash_from_record_and_relationship(record, relationship, params = {})
|
40
49
|
polymorphic = relationship[:polymorphic]
|
41
50
|
|
42
51
|
return ids_hash(
|
43
|
-
record
|
52
|
+
fetch_id(record, relationship, params),
|
44
53
|
relationship[:record_type]
|
45
54
|
) unless polymorphic
|
46
55
|
|
47
|
-
|
48
|
-
return unless associated_object = record.send(object_method_name)
|
56
|
+
return unless associated_object = fetch_associated_object(record, relationship, params)
|
49
57
|
|
50
58
|
return associated_object.map do |object|
|
51
59
|
id_hash_from_record object, polymorphic
|
@@ -54,69 +62,131 @@ module FastJsonapi
|
|
54
62
|
id_hash_from_record associated_object, polymorphic
|
55
63
|
end
|
56
64
|
|
57
|
-
def
|
65
|
+
def links_hash(record, params = {})
|
66
|
+
data_links.each_with_object({}) do |(key, method), link_hash|
|
67
|
+
link_hash[key] = if method.is_a?(Proc)
|
68
|
+
method.arity == 1 ? method.call(record) : method.call(record, params)
|
69
|
+
else
|
70
|
+
record.public_send(method)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def attributes_hash(record, params = {})
|
58
76
|
attributes_to_serialize.each_with_object({}) do |(key, method), attr_hash|
|
59
|
-
attr_hash[key] = method.is_a?(Proc)
|
77
|
+
attr_hash[key] = if method.is_a?(Proc)
|
78
|
+
method.arity == 1 ? method.call(record) : method.call(record, params)
|
79
|
+
else
|
80
|
+
record.public_send(method)
|
81
|
+
end
|
60
82
|
end
|
61
83
|
end
|
62
84
|
|
63
|
-
def relationships_hash(record, relationships = nil)
|
85
|
+
def relationships_hash(record, relationships = nil, params = {})
|
64
86
|
relationships = relationships_to_serialize if relationships.nil?
|
65
87
|
|
66
88
|
relationships.each_with_object({}) do |(_k, relationship), hash|
|
67
89
|
name = relationship[:key]
|
68
90
|
empty_case = relationship[:relationship_type] == :has_many ? [] : nil
|
69
91
|
hash[name] = {
|
70
|
-
data: ids_hash_from_record_and_relationship(record, relationship) || empty_case
|
92
|
+
data: ids_hash_from_record_and_relationship(record, relationship, params) || empty_case
|
71
93
|
}
|
72
94
|
end
|
73
95
|
end
|
74
96
|
|
75
|
-
def record_hash(record)
|
97
|
+
def record_hash(record, params = {})
|
76
98
|
if cached
|
77
|
-
record_hash = Rails.cache.fetch(record.cache_key, expires_in: cache_length) do
|
78
|
-
|
79
|
-
temp_hash =
|
80
|
-
temp_hash[:attributes] = attributes_hash(record) if attributes_to_serialize.present?
|
99
|
+
record_hash = Rails.cache.fetch(record.cache_key, expires_in: cache_length, race_condition_ttl: race_condition_ttl) do
|
100
|
+
temp_hash = id_hash(id_from_record(record), record_type, true)
|
101
|
+
temp_hash[:attributes] = attributes_hash(record, params) if attributes_to_serialize.present?
|
81
102
|
temp_hash[:relationships] = {}
|
82
|
-
temp_hash[:relationships] = relationships_hash(record, cachable_relationships_to_serialize) if cachable_relationships_to_serialize.present?
|
103
|
+
temp_hash[:relationships] = relationships_hash(record, cachable_relationships_to_serialize, params) if cachable_relationships_to_serialize.present?
|
104
|
+
temp_hash[:links] = links_hash(record, params) if data_links.present?
|
83
105
|
temp_hash
|
84
106
|
end
|
85
|
-
record_hash[:relationships] = record_hash[:relationships].merge(relationships_hash(record, uncachable_relationships_to_serialize)) if uncachable_relationships_to_serialize.present?
|
107
|
+
record_hash[:relationships] = record_hash[:relationships].merge(relationships_hash(record, uncachable_relationships_to_serialize, params)) if uncachable_relationships_to_serialize.present?
|
86
108
|
record_hash
|
87
109
|
else
|
88
|
-
|
89
|
-
record_hash =
|
90
|
-
record_hash[:
|
91
|
-
record_hash[:
|
110
|
+
record_hash = id_hash(id_from_record(record), record_type, true)
|
111
|
+
record_hash[:attributes] = attributes_hash(record, params) if attributes_to_serialize.present?
|
112
|
+
record_hash[:relationships] = relationships_hash(record, nil, params) if relationships_to_serialize.present?
|
113
|
+
record_hash[:links] = links_hash(record, params) if data_links.present?
|
92
114
|
record_hash
|
93
115
|
end
|
94
116
|
end
|
95
117
|
|
118
|
+
def id_from_record(record)
|
119
|
+
return record.send(record_id) if record_id
|
120
|
+
raise MandatoryField, 'id is a mandatory field in the jsonapi spec' unless record.respond_to?(:id)
|
121
|
+
record.id
|
122
|
+
end
|
123
|
+
|
96
124
|
# Override #to_json for alternative implementation
|
97
125
|
def to_json(payload)
|
98
126
|
FastJsonapi::MultiToJson.to_json(payload) if payload.present?
|
99
127
|
end
|
100
128
|
|
101
|
-
|
129
|
+
def parse_include_item(include_item)
|
130
|
+
return [include_item.to_sym] unless include_item.to_s.include?('.')
|
131
|
+
include_item.to_s.split('.').map { |item| item.to_sym }
|
132
|
+
end
|
102
133
|
|
103
|
-
def
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
134
|
+
def remaining_items(items)
|
135
|
+
return unless items.size > 1
|
136
|
+
|
137
|
+
items_copy = items.dup
|
138
|
+
items_copy.delete_at(0)
|
139
|
+
[items_copy.join('.').to_sym]
|
140
|
+
end
|
141
|
+
|
142
|
+
# includes handler
|
143
|
+
def get_included_records(record, includes_list, known_included_objects, params = {})
|
144
|
+
return unless includes_list.present?
|
145
|
+
|
146
|
+
includes_list.sort.each_with_object([]) do |include_item, included_records|
|
147
|
+
items = parse_include_item(include_item)
|
148
|
+
items.each do |item|
|
149
|
+
next unless relationships_to_serialize && relationships_to_serialize[item]
|
150
|
+
raise NotImplementedError if @relationships_to_serialize[item][:polymorphic].is_a?(Hash)
|
151
|
+
record_type = @relationships_to_serialize[item][:record_type]
|
152
|
+
serializer = @relationships_to_serialize[item][:serializer].to_s.constantize
|
153
|
+
relationship_type = @relationships_to_serialize[item][:relationship_type]
|
154
|
+
|
155
|
+
included_objects = fetch_associated_object(record, @relationships_to_serialize[item], params)
|
156
|
+
next if included_objects.blank?
|
157
|
+
included_objects = [included_objects] unless relationship_type == :has_many
|
158
|
+
|
159
|
+
included_objects.each do |inc_obj|
|
160
|
+
if remaining_items(items)
|
161
|
+
serializer_records = serializer.get_included_records(inc_obj, remaining_items(items), known_included_objects)
|
162
|
+
included_records.concat(serializer_records) unless serializer_records.empty?
|
163
|
+
end
|
164
|
+
|
165
|
+
code = "#{record_type}_#{inc_obj.id}"
|
166
|
+
next if known_included_objects.key?(code)
|
167
|
+
|
168
|
+
known_included_objects[code] = inc_obj
|
169
|
+
included_records << serializer.record_hash(inc_obj, params)
|
170
|
+
end
|
117
171
|
end
|
118
172
|
end
|
119
173
|
end
|
174
|
+
|
175
|
+
def fetch_associated_object(record, relationship, params)
|
176
|
+
return relationship[:object_block].call(record, params) unless relationship[:object_block].nil?
|
177
|
+
record.send(relationship[:object_method_name])
|
178
|
+
end
|
179
|
+
|
180
|
+
def fetch_id(record, relationship, params)
|
181
|
+
unless relationship[:object_block].nil?
|
182
|
+
object = relationship[:object_block].call(record, params)
|
183
|
+
|
184
|
+
return object.map(&:id) if object.respond_to? :map
|
185
|
+
return object.id
|
186
|
+
end
|
187
|
+
|
188
|
+
record.public_send(relationship[:id_method_name])
|
189
|
+
end
|
120
190
|
end
|
121
191
|
end
|
122
192
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fast_jsonapi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: '1.2'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shishir Kakaraddi
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2018-
|
13
|
+
date: 2018-05-20 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activesupport
|
@@ -197,11 +197,13 @@ files:
|
|
197
197
|
- lib/fast_jsonapi/instrumentation/serializable_hash.rb
|
198
198
|
- lib/fast_jsonapi/instrumentation/serialized_json.rb
|
199
199
|
- lib/fast_jsonapi/instrumentation/skylight.rb
|
200
|
+
- lib/fast_jsonapi/instrumentation/skylight/normalizers/base.rb
|
200
201
|
- lib/fast_jsonapi/instrumentation/skylight/normalizers/serializable_hash.rb
|
201
202
|
- lib/fast_jsonapi/instrumentation/skylight/normalizers/serialized_json.rb
|
202
203
|
- lib/fast_jsonapi/multi_to_json.rb
|
203
204
|
- lib/fast_jsonapi/object_serializer.rb
|
204
205
|
- lib/fast_jsonapi/serialization_core.rb
|
206
|
+
- lib/fast_jsonapi/version.rb
|
205
207
|
- lib/generators/serializer/USAGE
|
206
208
|
- lib/generators/serializer/serializer_generator.rb
|
207
209
|
- lib/generators/serializer/templates/serializer.rb.tt
|
@@ -218,7 +220,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
218
220
|
requirements:
|
219
221
|
- - ">="
|
220
222
|
- !ruby/object:Gem::Version
|
221
|
-
version:
|
223
|
+
version: 2.0.0
|
222
224
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
223
225
|
requirements:
|
224
226
|
- - ">="
|
@@ -226,7 +228,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
226
228
|
version: '0'
|
227
229
|
requirements: []
|
228
230
|
rubyforge_project:
|
229
|
-
rubygems_version: 2.
|
231
|
+
rubygems_version: 2.2.2
|
230
232
|
signing_key:
|
231
233
|
specification_version: 4
|
232
234
|
summary: fast JSON API(jsonapi.org) serializer
|