fast_jsonapi 1.0.17 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +106 -38
- data/lib/extensions/has_one.rb +19 -13
- data/lib/fast_jsonapi.rb +2 -0
- data/lib/fast_jsonapi/instrumentation.rb +2 -0
- data/lib/fast_jsonapi/instrumentation/serializable_hash.rb +15 -0
- data/lib/fast_jsonapi/instrumentation/serialized_json.rb +15 -0
- data/lib/fast_jsonapi/instrumentation/skylight.rb +2 -0
- data/lib/fast_jsonapi/instrumentation/skylight/normalizers/serializable_hash.rb +22 -0
- data/lib/fast_jsonapi/instrumentation/skylight/normalizers/serialized_json.rb +22 -0
- data/lib/fast_jsonapi/multi_to_json.rb +92 -0
- data/lib/fast_jsonapi/object_serializer.rb +120 -91
- data/lib/fast_jsonapi/serialization_core.rb +44 -32
- data/lib/generators/serializer/USAGE +8 -0
- data/lib/generators/serializer/serializer_generator.rb +19 -0
- data/lib/generators/serializer/templates/serializer.rb.tt +6 -0
- metadata +48 -88
- data/.document +0 -5
- data/.rspec +0 -1
- data/Gemfile +0 -4
- data/Gemfile.lock +0 -158
- data/README.rdoc +0 -231
- data/Rakefile +0 -55
- data/VERSION +0 -1
- data/docs/collection_serializer_output.json +0 -35
- data/docs/object_serializer.json +0 -30
- data/fast_jsonapi.gemspec +0 -108
- data/spec/lib/extensions/active_record_spec.rb +0 -67
- data/spec/lib/object_serializer_caching_spec.rb +0 -68
- data/spec/lib/object_serializer_class_methods_spec.rb +0 -69
- data/spec/lib/object_serializer_hyphen_spec.rb +0 -40
- data/spec/lib/object_serializer_performance_spec.rb +0 -87
- data/spec/lib/object_serializer_spec.rb +0 -126
- data/spec/lib/object_serializer_struct_spec.rb +0 -31
- data/spec/lib/serialization_core_spec.rb +0 -84
- data/spec/shared/contexts/ams_context.rb +0 -83
- data/spec/shared/contexts/movie_context.rb +0 -192
- data/spec/spec_helper.rb +0 -15
@@ -1,76 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_support/core_ext/object'
|
2
4
|
require 'active_support/concern'
|
3
5
|
require 'active_support/inflector'
|
4
|
-
require 'oj'
|
5
|
-
require 'multi_json'
|
6
6
|
require 'fast_jsonapi/serialization_core'
|
7
7
|
|
8
|
-
begin
|
9
|
-
require 'skylight'
|
10
|
-
SKYLIGHT_ENABLED = true
|
11
|
-
rescue LoadError
|
12
|
-
SKYLIGHT_ENABLED = false
|
13
|
-
end
|
14
|
-
|
15
8
|
module FastJsonapi
|
16
9
|
module ObjectSerializer
|
17
10
|
extend ActiveSupport::Concern
|
18
11
|
include SerializationCore
|
19
12
|
|
20
|
-
|
21
|
-
|
22
|
-
# To remove Skylight
|
23
|
-
# Remove the included do block
|
24
|
-
# Remove the Gemfile entry
|
25
|
-
if SKYLIGHT_ENABLED
|
26
|
-
include Skylight::Helpers
|
27
|
-
|
28
|
-
instrument_method :serializable_hash
|
29
|
-
instrument_method :to_json
|
30
|
-
end
|
13
|
+
SERIALIZABLE_HASH_NOTIFICATION = 'render.fast_jsonapi.serializable_hash'.freeze
|
14
|
+
SERIALIZED_JSON_NOTIFICATION = 'render.fast_jsonapi.serialized_json'.freeze
|
31
15
|
|
16
|
+
included do
|
32
17
|
# Set record_type based on the name of the serializer class
|
33
|
-
set_type
|
18
|
+
set_type(reflected_record_type) if reflected_record_type
|
34
19
|
end
|
35
20
|
|
36
21
|
def initialize(resource, options = {})
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
self.class.has_permitted_includes(@includes) if @includes.present?
|
41
|
-
@known_included_objects = {} # keep track of inc objects that have already been serialized
|
42
|
-
end
|
43
|
-
# @records if enumerables like Array, ActiveRecord::Relation but if Struct just make it a @record
|
44
|
-
if resource.respond_to?(:each) && !resource.respond_to?(:each_pair)
|
45
|
-
@records = resource
|
46
|
-
else
|
47
|
-
@record = resource
|
48
|
-
end
|
22
|
+
process_options(options)
|
23
|
+
|
24
|
+
@resource = resource
|
49
25
|
end
|
50
26
|
|
51
27
|
def serializable_hash
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
return hash_for_multiple_records(serializable_hash) if @records
|
56
|
-
serializable_hash
|
28
|
+
return hash_for_collection if is_collection?(@resource)
|
29
|
+
|
30
|
+
hash_for_one_record
|
57
31
|
end
|
32
|
+
alias_method :to_hash, :serializable_hash
|
33
|
+
|
34
|
+
def hash_for_one_record
|
35
|
+
serializable_hash = { data: nil }
|
36
|
+
serializable_hash[:meta] = @meta if @meta.present?
|
37
|
+
|
38
|
+
return serializable_hash unless @resource
|
58
39
|
|
59
|
-
|
60
|
-
serializable_hash[:
|
61
|
-
serializable_hash[:included] = self.class.get_included_records(@record, @includes, @known_included_objects) if @includes.present?
|
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?
|
62
42
|
serializable_hash
|
63
43
|
end
|
64
44
|
|
65
|
-
def
|
45
|
+
def hash_for_collection
|
46
|
+
serializable_hash = {}
|
47
|
+
|
66
48
|
data = []
|
67
49
|
included = []
|
68
|
-
@
|
50
|
+
@resource.each do |record|
|
69
51
|
data << self.class.record_hash(record)
|
70
52
|
included.concat self.class.get_included_records(record, @includes, @known_included_objects) if @includes.present?
|
71
53
|
end
|
54
|
+
|
72
55
|
serializable_hash[:data] = data
|
73
56
|
serializable_hash[:included] = included if @includes.present?
|
57
|
+
serializable_hash[:meta] = @meta if @meta.present?
|
74
58
|
serializable_hash
|
75
59
|
end
|
76
60
|
|
@@ -78,24 +62,74 @@ module FastJsonapi
|
|
78
62
|
self.class.to_json(serializable_hash)
|
79
63
|
end
|
80
64
|
|
65
|
+
private
|
66
|
+
|
67
|
+
def process_options(options)
|
68
|
+
return if options.blank?
|
69
|
+
|
70
|
+
@known_included_objects = {}
|
71
|
+
@meta = options[:meta]
|
72
|
+
|
73
|
+
if options[:include].present?
|
74
|
+
@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"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def is_collection?(resource)
|
90
|
+
resource.respond_to?(:each) && !resource.respond_to?(:each_pair)
|
91
|
+
end
|
92
|
+
|
81
93
|
class_methods do
|
94
|
+
def reflected_record_type
|
95
|
+
return @reflected_record_type if defined?(@reflected_record_type)
|
96
|
+
|
97
|
+
@reflected_record_type ||= begin
|
98
|
+
if self.name.end_with?('Serializer')
|
99
|
+
self.name.split('::').last.chomp('Serializer').underscore.to_sym
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def set_key_transform(transform_name)
|
105
|
+
mapping = {
|
106
|
+
camel: :camelize,
|
107
|
+
camel_lower: [:camelize, :lower],
|
108
|
+
dash: :dasherize,
|
109
|
+
underscore: :underscore
|
110
|
+
}
|
111
|
+
@transform_method = mapping[transform_name.to_sym]
|
112
|
+
end
|
113
|
+
|
114
|
+
def run_key_transform(input)
|
115
|
+
if @transform_method.present?
|
116
|
+
input.to_s.send(*@transform_method).to_sym
|
117
|
+
else
|
118
|
+
input.to_sym
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
82
122
|
def use_hyphen
|
83
|
-
|
123
|
+
warn('DEPRECATION WARNING: use_hyphen is deprecated and will be removed from fast_jsonapi 2.0 use (set_key_transform :dash) instead')
|
124
|
+
set_key_transform :dash
|
84
125
|
end
|
85
126
|
|
86
127
|
def set_type(type_name)
|
87
|
-
self.record_type = type_name
|
88
|
-
if @hyphenated
|
89
|
-
self.record_type = type_name.to_s.dasherize.to_sym
|
90
|
-
end
|
128
|
+
self.record_type = run_key_transform(type_name)
|
91
129
|
end
|
92
130
|
|
93
|
-
def
|
94
|
-
|
95
|
-
class_name = self.name.demodulize
|
96
|
-
range_end = class_name.rindex('Serializer')
|
97
|
-
class_name[0...range_end].underscore.to_sym
|
98
|
-
end
|
131
|
+
def set_id(id_name)
|
132
|
+
self.record_id = id_name
|
99
133
|
end
|
100
134
|
|
101
135
|
def cache_options(cache_options)
|
@@ -103,19 +137,18 @@ module FastJsonapi
|
|
103
137
|
self.cache_length = cache_options[:cache_length] || 5.minutes
|
104
138
|
end
|
105
139
|
|
106
|
-
def attributes(*attributes_list)
|
140
|
+
def attributes(*attributes_list, &block)
|
107
141
|
attributes_list = attributes_list.first if attributes_list.first.class.is_a?(Array)
|
108
142
|
self.attributes_to_serialize = {} if self.attributes_to_serialize.nil?
|
109
143
|
attributes_list.each do |attr_name|
|
110
144
|
method_name = attr_name
|
111
|
-
key = method_name
|
112
|
-
|
113
|
-
key = attr_name.to_s.dasherize.to_sym
|
114
|
-
end
|
115
|
-
attributes_to_serialize[key] = method_name
|
145
|
+
key = run_key_transform(method_name)
|
146
|
+
attributes_to_serialize[key] = block || method_name
|
116
147
|
end
|
117
148
|
end
|
118
149
|
|
150
|
+
alias_method :attribute, :attributes
|
151
|
+
|
119
152
|
def add_relationship(name, relationship)
|
120
153
|
self.relationships_to_serialize = {} if relationships_to_serialize.nil?
|
121
154
|
self.cachable_relationships_to_serialize = {} if cachable_relationships_to_serialize.nil?
|
@@ -130,15 +163,11 @@ module FastJsonapi
|
|
130
163
|
end
|
131
164
|
|
132
165
|
def has_many(relationship_name, options = {})
|
133
|
-
singular_name = relationship_name.to_s.singularize
|
134
|
-
record_type = options[:record_type] || singular_name.to_sym
|
135
166
|
name = relationship_name.to_sym
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
end
|
141
|
-
serializer_key = options[:serializer] || record_type
|
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)
|
142
171
|
relationship = {
|
143
172
|
key: key,
|
144
173
|
name: name,
|
@@ -147,21 +176,18 @@ module FastJsonapi
|
|
147
176
|
object_method_name: options[:object_method_name] || name,
|
148
177
|
serializer: compute_serializer_name(serializer_key),
|
149
178
|
relationship_type: :has_many,
|
150
|
-
cached: options[:cached] || false
|
179
|
+
cached: options[:cached] || false,
|
180
|
+
polymorphic: fetch_polymorphic_option(options)
|
151
181
|
}
|
152
182
|
add_relationship(name, relationship)
|
153
183
|
end
|
154
184
|
|
155
185
|
def belongs_to(relationship_name, options = {})
|
156
186
|
name = relationship_name.to_sym
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
key = options[:key] || relationship_name.to_s.dasherize.to_sym
|
162
|
-
record_type = options[:record_type] || relationship_name.to_s.dasherize.to_sym
|
163
|
-
end
|
164
|
-
relationship = {
|
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, {
|
165
191
|
key: key,
|
166
192
|
name: name,
|
167
193
|
id_method_name: options[:id_method_name] || (relationship_name.to_s + '_id').to_sym,
|
@@ -169,21 +195,17 @@ module FastJsonapi
|
|
169
195
|
object_method_name: options[:object_method_name] || name,
|
170
196
|
serializer: compute_serializer_name(serializer_key),
|
171
197
|
relationship_type: :belongs_to,
|
172
|
-
cached: options[:cached] || true
|
173
|
-
|
174
|
-
|
198
|
+
cached: options[:cached] || true,
|
199
|
+
polymorphic: fetch_polymorphic_option(options)
|
200
|
+
})
|
175
201
|
end
|
176
202
|
|
177
203
|
def has_one(relationship_name, options = {})
|
178
204
|
name = relationship_name.to_sym
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
key = options[:key] || relationship_name.to_s.dasherize.to_sym
|
184
|
-
record_type = options[:record_type] || relationship_name.to_s.dasherize.to_sym
|
185
|
-
end
|
186
|
-
relationship = {
|
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, {
|
187
209
|
key: key,
|
188
210
|
name: name,
|
189
211
|
id_method_name: options[:id_method_name] || (relationship_name.to_s + '_id').to_sym,
|
@@ -191,9 +213,9 @@ module FastJsonapi
|
|
191
213
|
object_method_name: options[:object_method_name] || name,
|
192
214
|
serializer: compute_serializer_name(serializer_key),
|
193
215
|
relationship_type: :has_one,
|
194
|
-
cached: options[:cached] || false
|
195
|
-
|
196
|
-
|
216
|
+
cached: options[:cached] || false,
|
217
|
+
polymorphic: fetch_polymorphic_option(options)
|
218
|
+
})
|
197
219
|
end
|
198
220
|
|
199
221
|
def compute_serializer_name(serializer_key)
|
@@ -202,6 +224,13 @@ module FastJsonapi
|
|
202
224
|
return (namespace + serializer_name).to_sym if namespace.present?
|
203
225
|
(serializer_key.to_s.classify + 'Serializer').to_sym
|
204
226
|
end
|
227
|
+
|
228
|
+
def fetch_polymorphic_option(options)
|
229
|
+
option = options[:polymorphic]
|
230
|
+
return false unless option.present?
|
231
|
+
return option if option.respond_to? :keys
|
232
|
+
{}
|
233
|
+
end
|
205
234
|
end
|
206
235
|
end
|
207
236
|
end
|
@@ -1,4 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_support/concern'
|
4
|
+
require 'fast_jsonapi/multi_to_json'
|
2
5
|
|
3
6
|
module FastJsonapi
|
4
7
|
module SerializationCore
|
@@ -11,6 +14,7 @@ module FastJsonapi
|
|
11
14
|
:cachable_relationships_to_serialize,
|
12
15
|
:uncachable_relationships_to_serialize,
|
13
16
|
:record_type,
|
17
|
+
:record_id,
|
14
18
|
:cache_length,
|
15
19
|
:cached
|
16
20
|
end
|
@@ -26,58 +30,78 @@ module FastJsonapi
|
|
26
30
|
id_hash(ids, record_type) # ids variable is just a single id here
|
27
31
|
end
|
28
32
|
|
33
|
+
def id_hash_from_record(record, record_types)
|
34
|
+
# memoize the record type within the record_types dictionary, then assigning to record_type:
|
35
|
+
record_type = record_types[record.class] ||= record.class.name.underscore.to_sym
|
36
|
+
{ id: record.id.to_s, type: record_type }
|
37
|
+
end
|
38
|
+
|
39
|
+
def ids_hash_from_record_and_relationship(record, relationship)
|
40
|
+
polymorphic = relationship[:polymorphic]
|
41
|
+
|
42
|
+
return ids_hash(
|
43
|
+
record.public_send(relationship[:id_method_name]),
|
44
|
+
relationship[:record_type]
|
45
|
+
) unless polymorphic
|
46
|
+
|
47
|
+
object_method_name = relationship.fetch(:object_method_name, relationship[:name])
|
48
|
+
return unless associated_object = record.send(object_method_name)
|
49
|
+
|
50
|
+
return associated_object.map do |object|
|
51
|
+
id_hash_from_record object, polymorphic
|
52
|
+
end if associated_object.respond_to? :map
|
53
|
+
|
54
|
+
id_hash_from_record associated_object, polymorphic
|
55
|
+
end
|
56
|
+
|
29
57
|
def attributes_hash(record)
|
30
|
-
|
31
|
-
|
32
|
-
attributes_hash[key] = record.send(method_name)
|
58
|
+
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)
|
33
60
|
end
|
34
|
-
attributes_hash
|
35
61
|
end
|
36
62
|
|
37
63
|
def relationships_hash(record, relationships = nil)
|
38
|
-
relationships_hash = {}
|
39
64
|
relationships = relationships_to_serialize if relationships.nil?
|
40
65
|
|
41
|
-
relationships.
|
66
|
+
relationships.each_with_object({}) do |(_k, relationship), hash|
|
42
67
|
name = relationship[:key]
|
43
|
-
id_method_name = relationship[:id_method_name]
|
44
|
-
record_type = relationship[:record_type]
|
45
68
|
empty_case = relationship[:relationship_type] == :has_many ? [] : nil
|
46
|
-
|
47
|
-
data:
|
69
|
+
hash[name] = {
|
70
|
+
data: ids_hash_from_record_and_relationship(record, relationship) || empty_case
|
48
71
|
}
|
49
72
|
end
|
50
|
-
relationships_hash
|
51
73
|
end
|
52
74
|
|
53
75
|
def record_hash(record)
|
54
76
|
if cached
|
55
77
|
record_hash = Rails.cache.fetch(record.cache_key, expires_in: cache_length) do
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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?
|
81
|
+
temp_hash[:relationships] = {}
|
82
|
+
temp_hash[:relationships] = relationships_hash(record, cachable_relationships_to_serialize) if cachable_relationships_to_serialize.present?
|
83
|
+
temp_hash
|
61
84
|
end
|
62
85
|
record_hash[:relationships] = record_hash[:relationships].merge(relationships_hash(record, uncachable_relationships_to_serialize)) if uncachable_relationships_to_serialize.present?
|
63
86
|
record_hash
|
64
87
|
else
|
65
|
-
|
88
|
+
id = record_id ? record.send(record_id) : record.id
|
89
|
+
record_hash = id_hash(id, record_type) || { id: nil, type: record_type }
|
66
90
|
record_hash[:attributes] = attributes_hash(record) if attributes_to_serialize.present?
|
67
91
|
record_hash[:relationships] = relationships_hash(record) if relationships_to_serialize.present?
|
68
92
|
record_hash
|
69
93
|
end
|
70
94
|
end
|
71
95
|
|
96
|
+
# Override #to_json for alternative implementation
|
72
97
|
def to_json(payload)
|
73
|
-
|
98
|
+
FastJsonapi::MultiToJson.to_json(payload) if payload.present?
|
74
99
|
end
|
75
100
|
|
76
101
|
# includes handler
|
77
102
|
|
78
103
|
def get_included_records(record, includes_list, known_included_objects)
|
79
|
-
|
80
|
-
includes_list.each do |item|
|
104
|
+
includes_list.each_with_object([]) do |item, included_records|
|
81
105
|
object_method_name = @relationships_to_serialize[item][:object_method_name]
|
82
106
|
record_type = @relationships_to_serialize[item][:record_type]
|
83
107
|
serializer = @relationships_to_serialize[item][:serializer].to_s.constantize
|
@@ -92,18 +116,6 @@ module FastJsonapi
|
|
92
116
|
included_records << serializer.record_hash(inc_obj)
|
93
117
|
end
|
94
118
|
end
|
95
|
-
included_records
|
96
|
-
end
|
97
|
-
|
98
|
-
def has_permitted_includes(requested_includes)
|
99
|
-
# requested includes should be within relationships defined on serializer
|
100
|
-
allowed_includes = @relationships_to_serialize.keys
|
101
|
-
intersection = allowed_includes & requested_includes
|
102
|
-
if intersection.sort == requested_includes.sort
|
103
|
-
true
|
104
|
-
else
|
105
|
-
raise ArgumentError, "One of keys from #{requested_includes} is not specified as a relationship on the serializer"
|
106
|
-
end
|
107
119
|
end
|
108
120
|
end
|
109
121
|
end
|