dulead-jsonapi-serializer 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,29 @@
1
+ module FastJsonapi
2
+ class Scalar
3
+ attr_reader :key, :method, :conditional_proc
4
+
5
+ def initialize(key:, method:, options: {})
6
+ @key = key
7
+ @method = method
8
+ @conditional_proc = options[:if]
9
+ end
10
+
11
+ def serialize(record, serialization_params, output_hash)
12
+ if conditionally_allowed?(record, serialization_params)
13
+ if method.is_a?(Proc)
14
+ output_hash[key] = FastJsonapi.call_proc(method, record, serialization_params)
15
+ else
16
+ output_hash[key] = record.public_send(method)
17
+ end
18
+ end
19
+ end
20
+
21
+ def conditionally_allowed?(record, serialization_params)
22
+ if conditional_proc.present?
23
+ FastJsonapi.call_proc(conditional_proc, record, serialization_params)
24
+ else
25
+ true
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+ require 'digest/sha1'
5
+
6
+ module FastJsonapi
7
+ MandatoryField = Class.new(StandardError)
8
+
9
+ module SerializationCore
10
+ extend ActiveSupport::Concern
11
+
12
+ included do
13
+ class << self
14
+ attr_accessor :attributes_to_serialize,
15
+ :relationships_to_serialize,
16
+ :cachable_relationships_to_serialize,
17
+ :uncachable_relationships_to_serialize,
18
+ :transform_method,
19
+ :record_type,
20
+ :record_type_block,
21
+ :record_id,
22
+ :cache_store_instance,
23
+ :cache_store_options,
24
+ :data_links,
25
+ :meta_to_serialize
26
+ end
27
+ end
28
+
29
+ class_methods do
30
+ def id_hash(id, record_type, default_return = false)
31
+ if id.present?
32
+ { id: id.to_s, type: record_type }
33
+ else
34
+ default_return ? { id: nil, type: record_type } : nil
35
+ end
36
+ end
37
+
38
+ def links_hash(record, params = {})
39
+ data_links.each_with_object({}) do |(_k, link), hash|
40
+ link.serialize(record, params, hash)
41
+ end
42
+ end
43
+
44
+ def attributes_hash(record, fieldset = nil, params = {})
45
+ attributes = attributes_to_serialize
46
+ attributes = attributes.slice(*fieldset) if fieldset.present?
47
+ attributes = {} if fieldset == []
48
+
49
+ attributes.each_with_object({}) do |(_k, attribute), hash|
50
+ attribute.serialize(record, params, hash)
51
+ end
52
+ end
53
+
54
+ def relationships_hash(record, relationships = nil, fieldset = nil, includes_list = nil, params = {})
55
+ relationships = relationships_to_serialize if relationships.nil?
56
+ relationships = relationships.slice(*fieldset) if fieldset.present?
57
+ relationships = {} if fieldset == []
58
+
59
+ relationships.each_with_object({}) do |(key, relationship), hash|
60
+ included = includes_list.present? && includes_list.include?(key)
61
+ relationship.serialize(record, included, params, hash)
62
+ end
63
+ end
64
+
65
+ def meta_hash(record, params = {})
66
+ FastJsonapi.call_proc(meta_to_serialize, record, params)
67
+ end
68
+
69
+ def record_hash(record, fieldset, includes_list, params = {})
70
+ if cache_store_instance
71
+ cache_opts = record_cache_options(cache_store_options, fieldset, includes_list, params)
72
+ record_hash = cache_store_instance.fetch(record, **cache_opts) do
73
+ temp_hash = id_hash(id_from_record(record, params), type_from_record(record, record_type), true)
74
+ temp_hash[:attributes] = attributes_hash(record, fieldset, params) if attributes_to_serialize.present?
75
+ temp_hash[:relationships] = relationships_hash(record, cachable_relationships_to_serialize, fieldset, includes_list, params) if cachable_relationships_to_serialize.present?
76
+ temp_hash[:links] = links_hash(record, params) if data_links.present?
77
+ temp_hash
78
+ end
79
+ record_hash[:relationships] = (record_hash[:relationships] || {}).merge(relationships_hash(record, uncachable_relationships_to_serialize, fieldset, includes_list, params)) if uncachable_relationships_to_serialize.present?
80
+ else
81
+ record_hash = id_hash(id_from_record(record, params), type_from_record(record, record_type), true)
82
+ record_hash[:attributes] = attributes_hash(record, fieldset, params) if attributes_to_serialize.present?
83
+ record_hash[:relationships] = relationships_hash(record, nil, fieldset, includes_list, params) if relationships_to_serialize.present?
84
+ record_hash[:links] = links_hash(record, params) if data_links.present?
85
+ end
86
+
87
+ record_hash[:meta] = meta_hash(record, params) if meta_to_serialize.present?
88
+ record_hash
89
+ end
90
+
91
+ # Cache options helper. Use it to adapt cache keys/rules.
92
+ #
93
+ # If a fieldset is specified, it modifies the namespace to include the
94
+ # fields from the fieldset.
95
+ #
96
+ # @param options [Hash] default cache options
97
+ # @param fieldset [Array, nil] passed fieldset values
98
+ # @param includes_list [Array, nil] passed included values
99
+ # @param params [Hash] the serializer params
100
+ #
101
+ # @return [Hash] processed options hash
102
+ # rubocop:disable Lint/UnusedMethodArgument
103
+ def record_cache_options(options, fieldset, includes_list, params)
104
+ return options unless fieldset
105
+
106
+ options = options ? options.dup : {}
107
+ options[:namespace] ||= 'jsonapi-serializer'
108
+
109
+ fieldset_key = fieldset.join('_')
110
+
111
+ # Use a fixed-length fieldset key if the current length is more than
112
+ # the length of a SHA1 digest
113
+ if fieldset_key.length > 40
114
+ fieldset_key = Digest::SHA1.hexdigest(fieldset_key)
115
+ end
116
+
117
+ options[:namespace] = "#{options[:namespace]}-fieldset:#{fieldset_key}"
118
+ options
119
+ end
120
+ # rubocop:enable Lint/UnusedMethodArgument
121
+
122
+ def id_from_record(record, params)
123
+ return FastJsonapi.call_proc(record_id, record, params) if record_id.is_a?(Proc)
124
+ return record.send(record_id) if record_id
125
+ raise MandatoryField, 'id is a mandatory field in the jsonapi spec' unless record.respond_to?(:id)
126
+
127
+ record.id
128
+ end
129
+
130
+ def type_from_record(record, record_type)
131
+ return FastJsonapi.call_proc(record_type_block, record) if record_type_block.is_a?(Proc)
132
+
133
+ record_type
134
+ end
135
+
136
+ # It chops out the root association (first part) from each include.
137
+ #
138
+ # It keeps an unique list and collects all of the rest of the include
139
+ # value to hand it off to the next related to include serializer.
140
+ #
141
+ # This method will turn that include array into a Hash that looks like:
142
+ #
143
+ # {
144
+ # authors: Set.new([
145
+ # 'books',
146
+ # 'books.genre',
147
+ # 'books.genre.books',
148
+ # 'books.genre.books.authors',
149
+ # 'books.genre.books.genre'
150
+ # ]),
151
+ # genre: Set.new(['books'])
152
+ # }
153
+ #
154
+ # Because the serializer only cares about the root associations
155
+ # included, it only needs the first segment of each include
156
+ # (for books, it's the "authors" and "genre") and it doesn't need to
157
+ # waste cycles parsing the rest of the include value. That will be done
158
+ # by the next serializer in line.
159
+ #
160
+ # @param includes_list [List] to be parsed
161
+ # @return [Hash]
162
+ def parse_includes_list(includes_list)
163
+ includes_list.each_with_object({}) do |include_item, include_sets|
164
+ include_base, include_remainder = include_item.to_s.split('.', 2)
165
+ include_sets[include_base.to_sym] ||= Set.new
166
+ include_sets[include_base.to_sym] << include_remainder if include_remainder
167
+ end
168
+ end
169
+
170
+ # includes handler
171
+ def get_included_records(record, includes_list, known_included_objects, fieldsets, params = {})
172
+ return unless includes_list.present?
173
+ return [] unless relationships_to_serialize
174
+
175
+ includes_list = parse_includes_list(includes_list)
176
+
177
+ includes_list.each_with_object([]) do |include_item, included_records|
178
+ relationship_item = relationships_to_serialize[include_item.first]
179
+
180
+ next unless relationship_item&.include_relationship?(record, params)
181
+
182
+ included_objects = Array(relationship_item.fetch_associated_object(record, params))
183
+ next if included_objects.empty?
184
+
185
+ static_serializer = relationship_item.static_serializer
186
+ static_record_type = relationship_item.static_record_type
187
+
188
+ included_objects.each do |inc_obj|
189
+ serializer = static_serializer || relationship_item.serializer_for(inc_obj, params)
190
+ record_type = static_record_type || serializer.record_type
191
+
192
+ if include_item.last.any?
193
+ serializer_records = serializer.get_included_records(inc_obj, include_item.last, known_included_objects, fieldsets, params)
194
+ included_records.concat(serializer_records) unless serializer_records.empty?
195
+ end
196
+
197
+ code = "#{record_type}_#{serializer.id_from_record(inc_obj, params)}"
198
+ next if known_included_objects.include?(code)
199
+
200
+ known_included_objects << code
201
+
202
+ included_records << serializer.record_hash(inc_obj, fieldsets[record_type], includes_list, params)
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,3 @@
1
+ module FastJsonapi
2
+ VERSION = JSONAPI::Serializer::VERSION
3
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jsonapi/serializer/errors'
4
+
5
+ module FastJsonapi
6
+ require 'fast_jsonapi/object_serializer'
7
+ if defined?(::Rails)
8
+ require 'fast_jsonapi/railtie'
9
+ elsif defined?(::ActiveRecord)
10
+ require 'extensions/has_one'
11
+ end
12
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Generates a serializer for the given model.
3
+
4
+ Example:
5
+ rails generate serializer Movie
6
+
7
+ This will create:
8
+ app/serializers/movie_serializer.rb
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators/base'
4
+
5
+ class SerializerGenerator < Rails::Generators::NamedBase
6
+ source_root File.expand_path('templates', __dir__)
7
+
8
+ argument :attributes, type: :array, default: [], banner: 'field field'
9
+
10
+ def create_serializer_file
11
+ template 'serializer.rb.tt', File.join('app', 'serializers', class_path, "#{file_name}_serializer.rb")
12
+ end
13
+
14
+ private
15
+
16
+ def attributes_names
17
+ attributes.map { |a| a.name.to_sym.inspect }
18
+ end
19
+ end
@@ -0,0 +1,6 @@
1
+ <% module_namespacing do -%>
2
+ class <%= class_name %>Serializer
3
+ include JSONAPI::Serializer
4
+ attributes <%= attributes_names.join(", ") %>
5
+ end
6
+ <% end -%>
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONAPI
4
+ module Serializer
5
+ class Error < StandardError; end
6
+
7
+ class UnsupportedIncludeError < Error
8
+ attr_reader :include_item, :klass
9
+
10
+ def initialize(include_item, klass)
11
+ super()
12
+ @include_item = include_item
13
+ @klass = klass
14
+ end
15
+
16
+ def message
17
+ "#{include_item} is not specified as a relationship on #{klass}"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ require 'active_support/notifications'
2
+
3
+ module JSONAPI
4
+ module Serializer
5
+ # Support for instrumentation
6
+ module Instrumentation
7
+ # Performance instrumentation namespace
8
+ NOTIFICATION_NAMESPACE = 'render.jsonapi-serializer.'.freeze
9
+
10
+ # Patch methods to use instrumentation...
11
+ %w[
12
+ serializable_hash
13
+ get_included_records
14
+ relationships_hash
15
+ ].each do |method_name|
16
+ define_method(method_name) do |*args|
17
+ ActiveSupport::Notifications.instrument(
18
+ NOTIFICATION_NAMESPACE + method_name,
19
+ { name: self.class.name, serializer: self.class }
20
+ ) do
21
+ super(*args)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ module JSONAPI
2
+ module Serializer
3
+ VERSION = '2.2.0'.freeze
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ require 'fast_jsonapi'
2
+
3
+ module JSONAPI
4
+ module Serializer
5
+ # TODO: Move and cleanup the old implementation...
6
+ def self.included(base)
7
+ base.class_eval do
8
+ include FastJsonapi::ObjectSerializer
9
+ end
10
+ end
11
+ end
12
+ end
metadata ADDED
@@ -0,0 +1,249 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dulead-jsonapi-serializer
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.2.0
5
+ platform: ruby
6
+ authors:
7
+ - JSON:API Serializer Community
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-01-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '4.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: ffaker
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: jsonapi-rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 0.0.5
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: 0.0.5
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rubocop
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rubocop-performance
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rubocop-rspec
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: simplecov
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: sqlite3
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ description: Fast, simple and easy to use JSON:API serialization library (also known
196
+ as fast_jsonapi).
197
+ email: ''
198
+ executables: []
199
+ extensions: []
200
+ extra_rdoc_files:
201
+ - LICENSE.txt
202
+ - README.md
203
+ files:
204
+ - LICENSE.txt
205
+ - README.md
206
+ - lib/extensions/has_one.rb
207
+ - lib/fast_jsonapi.rb
208
+ - lib/fast_jsonapi/attribute.rb
209
+ - lib/fast_jsonapi/helpers.rb
210
+ - lib/fast_jsonapi/instrumentation.rb
211
+ - lib/fast_jsonapi/instrumentation/skylight.rb
212
+ - lib/fast_jsonapi/link.rb
213
+ - lib/fast_jsonapi/object_serializer.rb
214
+ - lib/fast_jsonapi/railtie.rb
215
+ - lib/fast_jsonapi/relationship.rb
216
+ - lib/fast_jsonapi/scalar.rb
217
+ - lib/fast_jsonapi/serialization_core.rb
218
+ - lib/fast_jsonapi/version.rb
219
+ - lib/generators/serializer/USAGE
220
+ - lib/generators/serializer/serializer_generator.rb
221
+ - lib/generators/serializer/templates/serializer.rb.tt
222
+ - lib/jsonapi/serializer.rb
223
+ - lib/jsonapi/serializer/errors.rb
224
+ - lib/jsonapi/serializer/instrumentation.rb
225
+ - lib/jsonapi/serializer/version.rb
226
+ homepage: https://github.com/mnifakram/jsonapi-serializer
227
+ licenses:
228
+ - Apache-2.0
229
+ metadata: {}
230
+ post_install_message:
231
+ rdoc_options: []
232
+ require_paths:
233
+ - lib
234
+ required_ruby_version: !ruby/object:Gem::Requirement
235
+ requirements:
236
+ - - ">="
237
+ - !ruby/object:Gem::Version
238
+ version: '0'
239
+ required_rubygems_version: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - ">="
242
+ - !ruby/object:Gem::Version
243
+ version: '0'
244
+ requirements: []
245
+ rubygems_version: 3.2.32
246
+ signing_key:
247
+ specification_version: 4
248
+ summary: Fast JSON:API serialization library
249
+ test_files: []