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 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