fast_jsonapi 1.1.1 → 1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|