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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: c8e9a2f191f153e38d68cdafd1d61432a065fa608f069cf29403e5392358f5ea
4
- data.tar.gz: 4f11774e609f4a286850bf7a217cdb4525479097b5e594b27abf00e14586ab51
2
+ SHA1:
3
+ metadata.gz: 346edf85242b61d0747883aaa7b62da16b1a8fec
4
+ data.tar.gz: 36d21132fe65b9c88e2f1afce9aa935ae094d7ec
5
5
  SHA512:
6
- metadata.gz: dbad0552348467cbd2c40e9d49431a6995a152037e0224dcb9d2b7cf85c05656fb7a6c23e5a24ad13e8f54ab73019290096d127c55d690e33eb49df91f260ab9
7
- data.tar.gz: a5a007f236ee50327bba65c678288a3adfb7c44d0864f2a5bcfe18f63a9275cf836e551c318d07dbd30a3eb4a705a121cea94e7d5668f702e4b119e00d73d8f0
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 Serializer Movie name year
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 member through ` options[:include] `.
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[:include] = [:actors]
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)
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- begin
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
@@ -0,0 +1,7 @@
1
+ require 'skylight'
2
+
3
+ SKYLIGHT_NORMALIZER_BASE_CLASS = begin
4
+ ::Skylight::Core::Normalizers::Normalizer
5
+ rescue NameError
6
+ ::Skylight::Normalizers::Normalizer
7
+ 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 < Skylight::Normalizers::Normalizer
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 < Skylight::Normalizers::Normalizer
8
+ class SerializedJson < SKYLIGHT_NORMALIZER_BASE_CLASS
9
9
 
10
10
  register FastJsonapi::ObjectSerializer::SERIALIZED_JSON_NOTIFICATION
11
11
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'logger'
4
+
3
5
  # Usage:
4
6
  # class Movie
5
7
  # def to_json(payload)
@@ -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'.freeze
14
- SERIALIZED_JSON_NOTIFICATION = 'render.fast_jsonapi.serialized_json'.freeze
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
- @transform_method = mapping[transform_name.to_sym]
120
+ self.transform_method = mapping[transform_name.to_sym]
112
121
  end
113
122
 
114
123
  def run_key_transform(input)
115
- if @transform_method.present?
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
- singular_name = relationship_name.to_s.singularize
168
- serializer_key = options[:serializer] || singular_name.to_sym
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 belongs_to(relationship_name, options = {})
181
+ def has_one(relationship_name, options = {}, &block)
186
182
  name = relationship_name.to_sym
187
- serializer_key = options[:serializer] || relationship_name.to_sym
188
- key = options[:key] || run_key_transform(relationship_name)
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 has_one(relationship_name, options = {})
187
+ def belongs_to(relationship_name, options = {}, &block)
204
188
  name = relationship_name.to_sym
205
- serializer_key = options[:serializer] || name
206
- key = options[:key] || run_key_transform(relationship_name)
207
- record_type = options[:record_type] || run_key_transform(relationship_name)
208
- add_relationship(name, {
209
- key: key,
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] || (relationship_name.to_s + '_id').to_sym,
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
- serializer: compute_serializer_name(serializer_key),
215
- relationship_type: :has_one,
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
- return (namespace + serializer_name).to_sym if namespace.present?
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
- :cached
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
- return { id: id.to_s, type: record_type } if id.present?
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
- { id: record.id.to_s, type: record_type }
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.public_send(relationship[:id_method_name]),
52
+ fetch_id(record, relationship, params),
44
53
  relationship[:record_type]
45
54
  ) unless polymorphic
46
55
 
47
- object_method_name = relationship.fetch(:object_method_name, relationship[:name])
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 attributes_hash(record)
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) ? method.call(record) : record.public_send(method)
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
- id = record_id ? record.send(record_id) : record.id
79
- temp_hash = id_hash(id, record_type) || { id: nil, type: record_type }
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
- id = record_id ? record.send(record_id) : record.id
89
- record_hash = id_hash(id, record_type) || { id: nil, type: record_type }
90
- record_hash[:attributes] = attributes_hash(record) if attributes_to_serialize.present?
91
- record_hash[:relationships] = relationships_hash(record) if relationships_to_serialize.present?
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
- # includes handler
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 get_included_records(record, includes_list, known_included_objects)
104
- includes_list.each_with_object([]) do |item, included_records|
105
- object_method_name = @relationships_to_serialize[item][:object_method_name]
106
- record_type = @relationships_to_serialize[item][:record_type]
107
- serializer = @relationships_to_serialize[item][:serializer].to_s.constantize
108
- relationship_type = @relationships_to_serialize[item][:relationship_type]
109
- included_objects = record.send(object_method_name)
110
- next if included_objects.blank?
111
- included_objects = [included_objects] unless relationship_type == :has_many
112
- included_objects.each do |inc_obj|
113
- code = "#{record_type}_#{inc_obj.id}"
114
- next if known_included_objects.key?(code)
115
- known_included_objects[code] = inc_obj
116
- included_records << serializer.record_hash(inc_obj)
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
@@ -0,0 +1,3 @@
1
+ module FastJsonapi
2
+ VERSION = "1.2"
3
+ 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.1.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-02-01 00:00:00.000000000 Z
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: '0'
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.7.6
231
+ rubygems_version: 2.2.2
230
232
  signing_key:
231
233
  specification_version: 4
232
234
  summary: fast JSON API(jsonapi.org) serializer