alba 1.0.0 → 1.4.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 +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +26 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/dependabot.yml +26 -0
- data/.github/workflows/main.yml +10 -1
- data/.github/workflows/perf.yml +21 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +33 -8
- data/.yardopts +2 -0
- data/CHANGELOG.md +45 -0
- data/Gemfile +12 -6
- data/README.md +259 -24
- data/Rakefile +4 -1
- data/SECURITY.md +12 -0
- data/alba.gemspec +3 -3
- data/benchmark/collection.rb +392 -0
- data/benchmark/single_resource.rb +370 -0
- data/codecov.yml +8 -0
- data/gemfiles/all.gemfile +19 -0
- data/gemfiles/without_active_support.gemfile +17 -0
- data/gemfiles/without_oj.gemfile +17 -0
- data/lib/alba.rb +69 -26
- data/lib/alba/association.rb +14 -22
- data/lib/alba/default_inflector.rb +36 -0
- data/lib/alba/key_transform_factory.rb +33 -0
- data/lib/alba/many.rb +3 -2
- data/lib/alba/one.rb +3 -2
- data/lib/alba/resource.rb +171 -62
- data/lib/alba/typed_attribute.rb +61 -0
- data/lib/alba/version.rb +1 -1
- data/script/perf_check.rb +174 -0
- data/sider.yml +2 -4
- metadata +22 -10
- data/benchmark/local.rb +0 -198
- data/lib/alba/key_transformer.rb +0 -31
data/Rakefile
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rake/testtask"
|
3
3
|
|
4
|
+
ENV["BUNDLE_GEMFILE"] = File.expand_path("gemfiles/all.gemfile") if ENV["BUNDLE_GEMFILE"] == File.expand_path("Gemfile") || ENV["BUNDLE_GEMFILE"].empty? || ENV["BUNDLE_GEMFILE"].nil?
|
5
|
+
|
4
6
|
Rake::TestTask.new(:test) do |t|
|
5
7
|
t.libs << "test"
|
6
8
|
t.libs << "lib"
|
7
|
-
|
9
|
+
file_list = ENV["BUNDLE_GEMFILE"] == File.expand_path("gemfiles/all.gemfile") ? FileList["test/**/*_test.rb"] : FileList["test/dependencies/test_dependencies.rb"]
|
10
|
+
t.test_files = file_list
|
8
11
|
end
|
9
12
|
|
10
13
|
task :default => :test
|
data/SECURITY.md
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# Security Policy
|
2
|
+
|
3
|
+
## Supported Versions
|
4
|
+
|
5
|
+
| Version | Supported |
|
6
|
+
| ------- | ------------------ |
|
7
|
+
| 1.x.y | :white_check_mark: |
|
8
|
+
| < 1.0 | :x: |
|
9
|
+
|
10
|
+
## Reporting a Vulnerability
|
11
|
+
|
12
|
+
If you find a vulnerability of Alba, please contact me (OKURA Masafumi) via [email](masafumi.o1988@gmail.com). I'll report back within a few days.
|
data/alba.gemspec
CHANGED
@@ -7,14 +7,14 @@ Gem::Specification.new do |spec|
|
|
7
7
|
spec.email = ['masafumi.o1988@gmail.com']
|
8
8
|
|
9
9
|
spec.summary = 'Alba is the fastest JSON serializer for Ruby.'
|
10
|
-
spec.description = "Alba is
|
10
|
+
spec.description = "Alba is the fastest JSON serializer for Ruby. It focuses on performance, flexibility and usability."
|
11
11
|
spec.homepage = 'https://github.com/okuramasafumi/alba'
|
12
12
|
spec.license = 'MIT'
|
13
|
-
spec.required_ruby_version = Gem::Requirement.new('>= 2.5.
|
13
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
|
14
14
|
|
15
15
|
spec.metadata['homepage_uri'] = spec.homepage
|
16
16
|
spec.metadata['source_code_uri'] = 'https://github.com/okuramasafumi/alba'
|
17
|
-
spec.metadata['changelog_uri'] = 'https://github.com/okuramasafumi/alba/CHANGELOG.md'
|
17
|
+
spec.metadata['changelog_uri'] = 'https://github.com/okuramasafumi/alba/blob/main/CHANGELOG.md'
|
18
18
|
|
19
19
|
# Specify which files should be added to the gem when it is released.
|
20
20
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
@@ -0,0 +1,392 @@
|
|
1
|
+
# Benchmark script to run varieties of JSON serializers
|
2
|
+
# Fetch Alba from local, otherwise fetch latest from RubyGems
|
3
|
+
|
4
|
+
# --- Bundle dependencies ---
|
5
|
+
|
6
|
+
require "bundler/inline"
|
7
|
+
|
8
|
+
gemfile(true) do
|
9
|
+
source "https://rubygems.org"
|
10
|
+
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
11
|
+
|
12
|
+
gem "active_model_serializers"
|
13
|
+
gem "activerecord", "6.1.3"
|
14
|
+
gem "alba", path: '../'
|
15
|
+
gem "benchmark-ips"
|
16
|
+
gem "benchmark-memory"
|
17
|
+
gem "blueprinter"
|
18
|
+
gem "jbuilder"
|
19
|
+
gem "jsonapi-serializer" # successor of fast_jsonapi
|
20
|
+
gem "multi_json"
|
21
|
+
gem "primalize"
|
22
|
+
gem "oj"
|
23
|
+
gem "representable"
|
24
|
+
gem "simple_ams"
|
25
|
+
gem "sqlite3"
|
26
|
+
end
|
27
|
+
|
28
|
+
# --- Test data model setup ---
|
29
|
+
|
30
|
+
require "active_record"
|
31
|
+
require "logger"
|
32
|
+
require "oj"
|
33
|
+
require "sqlite3"
|
34
|
+
Oj.optimize_rails
|
35
|
+
|
36
|
+
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
|
37
|
+
# ActiveRecord::Base.logger = Logger.new($stdout)
|
38
|
+
|
39
|
+
ActiveRecord::Schema.define do
|
40
|
+
create_table :posts, force: true do |t|
|
41
|
+
t.string :body
|
42
|
+
end
|
43
|
+
|
44
|
+
create_table :comments, force: true do |t|
|
45
|
+
t.integer :post_id
|
46
|
+
t.string :body
|
47
|
+
t.integer :commenter_id
|
48
|
+
end
|
49
|
+
|
50
|
+
create_table :users, force: true do |t|
|
51
|
+
t.string :name
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class Post < ActiveRecord::Base
|
56
|
+
has_many :comments
|
57
|
+
has_many :commenters, through: :comments, class_name: 'User', source: :commenter
|
58
|
+
|
59
|
+
def attributes
|
60
|
+
{id: nil, body: nil, commenter_names: commenter_names}
|
61
|
+
end
|
62
|
+
|
63
|
+
def commenter_names
|
64
|
+
commenters.pluck(:name)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class Comment < ActiveRecord::Base
|
69
|
+
belongs_to :post
|
70
|
+
belongs_to :commenter, class_name: 'User'
|
71
|
+
|
72
|
+
def attributes
|
73
|
+
{id: nil, body: nil}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class User < ActiveRecord::Base
|
78
|
+
has_many :comments
|
79
|
+
end
|
80
|
+
|
81
|
+
# --- Alba serializers ---
|
82
|
+
|
83
|
+
require "alba"
|
84
|
+
|
85
|
+
class AlbaCommentResource
|
86
|
+
include ::Alba::Resource
|
87
|
+
attributes :id, :body
|
88
|
+
end
|
89
|
+
|
90
|
+
class AlbaPostResource
|
91
|
+
include ::Alba::Resource
|
92
|
+
attributes :id, :body
|
93
|
+
attribute :commenter_names do |post|
|
94
|
+
post.commenters.pluck(:name)
|
95
|
+
end
|
96
|
+
many :comments, resource: AlbaCommentResource
|
97
|
+
end
|
98
|
+
|
99
|
+
# --- ActiveModelSerializer serializers ---
|
100
|
+
|
101
|
+
require "active_model_serializers"
|
102
|
+
|
103
|
+
ActiveModelSerializers.logger = Logger.new(nil)
|
104
|
+
|
105
|
+
class AMSCommentSerializer < ActiveModel::Serializer
|
106
|
+
attributes :id, :body
|
107
|
+
end
|
108
|
+
|
109
|
+
class AMSPostSerializer < ActiveModel::Serializer
|
110
|
+
attributes :id, :body
|
111
|
+
attribute :commenter_names
|
112
|
+
has_many :comments, serializer: AMSCommentSerializer
|
113
|
+
|
114
|
+
def commenter_names
|
115
|
+
object.commenters.pluck(:name)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# --- Blueprint serializers ---
|
120
|
+
|
121
|
+
require "blueprinter"
|
122
|
+
|
123
|
+
class CommentBlueprint < Blueprinter::Base
|
124
|
+
fields :id, :body
|
125
|
+
end
|
126
|
+
|
127
|
+
class PostBlueprint < Blueprinter::Base
|
128
|
+
fields :id, :body, :commenter_names
|
129
|
+
association :comments, blueprint: CommentBlueprint
|
130
|
+
|
131
|
+
def commenter_names
|
132
|
+
commenters.pluck(:name)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# --- JBuilder serializers ---
|
137
|
+
|
138
|
+
require "jbuilder"
|
139
|
+
|
140
|
+
class Post
|
141
|
+
def to_builder
|
142
|
+
Jbuilder.new do |post|
|
143
|
+
post.call(self, :id, :body, :commenter_names, :comments)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def commenter_names
|
148
|
+
commenters.pluck(:name)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class Comment
|
153
|
+
def to_builder
|
154
|
+
Jbuilder.new do |comment|
|
155
|
+
comment.call(self, :id, :body)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# --- JSONAPI:Serializer serializers / (successor of fast_jsonapi) ---
|
161
|
+
|
162
|
+
class JsonApiStandardCommentSerializer
|
163
|
+
include JSONAPI::Serializer
|
164
|
+
|
165
|
+
attribute :id
|
166
|
+
attribute :body
|
167
|
+
end
|
168
|
+
|
169
|
+
class JsonApiStandardPostSerializer
|
170
|
+
include JSONAPI::Serializer
|
171
|
+
|
172
|
+
# set_type :post # optional
|
173
|
+
attribute :id
|
174
|
+
attribute :body
|
175
|
+
attribute :commenter_names
|
176
|
+
|
177
|
+
attribute :comments do |post|
|
178
|
+
post.comments.map { |comment| JsonApiSameFormatCommentSerializer.new(comment) }
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# --- JSONAPI:Serializer serializers that format the code the same flat way as the other gems here ---
|
183
|
+
|
184
|
+
# code to convert from JSON:API output to "flat" JSON, like the other serializers build
|
185
|
+
class JsonApiSameFormatSerializer
|
186
|
+
include JSONAPI::Serializer
|
187
|
+
|
188
|
+
def as_json(*_options)
|
189
|
+
hash = serializable_hash
|
190
|
+
|
191
|
+
if hash[:data].is_a? Hash
|
192
|
+
hash[:data][:attributes]
|
193
|
+
|
194
|
+
elsif hash[:data].is_a? Array
|
195
|
+
hash[:data].pluck(:attributes)
|
196
|
+
|
197
|
+
elsif hash[:data].nil?
|
198
|
+
{ }
|
199
|
+
|
200
|
+
else
|
201
|
+
raise "unexpected data type #{hash[:data].class}"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
class JsonApiSameFormatCommentSerializer < JsonApiSameFormatSerializer
|
207
|
+
attribute :id
|
208
|
+
attribute :body
|
209
|
+
end
|
210
|
+
|
211
|
+
class JsonApiSameFormatPostSerializer < JsonApiSameFormatSerializer
|
212
|
+
attribute :id
|
213
|
+
attribute :body
|
214
|
+
attribute :commenter_names
|
215
|
+
|
216
|
+
attribute :comments do |post|
|
217
|
+
post.comments.map { |comment| JsonApiSameFormatCommentSerializer.new(comment) }
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# --- Primalize serializers ---
|
222
|
+
#
|
223
|
+
class PrimalizeCommentResource < Primalize::Single
|
224
|
+
attributes id: integer, body: string
|
225
|
+
end
|
226
|
+
|
227
|
+
class PrimalizePostResource < Primalize::Single
|
228
|
+
alias post object
|
229
|
+
|
230
|
+
attributes(
|
231
|
+
id: integer,
|
232
|
+
body: string,
|
233
|
+
comments: array(primalize(PrimalizeCommentResource)),
|
234
|
+
commenter_names: array(string),
|
235
|
+
)
|
236
|
+
|
237
|
+
def commenter_names
|
238
|
+
post.commenters.pluck(:name)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
class PrimalizePostsResource < Primalize::Many
|
243
|
+
attributes posts: enumerable(PrimalizePostResource)
|
244
|
+
end
|
245
|
+
|
246
|
+
# --- Representable serializers ---
|
247
|
+
|
248
|
+
require "representable"
|
249
|
+
|
250
|
+
class CommentRepresenter < Representable::Decorator
|
251
|
+
include Representable::JSON
|
252
|
+
|
253
|
+
property :id
|
254
|
+
property :body
|
255
|
+
end
|
256
|
+
|
257
|
+
class PostsRepresenter < Representable::Decorator
|
258
|
+
include Representable::JSON::Collection
|
259
|
+
|
260
|
+
items class: Post do
|
261
|
+
property :id
|
262
|
+
property :body
|
263
|
+
property :commenter_names
|
264
|
+
collection :comments
|
265
|
+
end
|
266
|
+
|
267
|
+
def commenter_names
|
268
|
+
commenters.pluck(:name)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# --- SimpleAMS serializers ---
|
273
|
+
|
274
|
+
require "simple_ams"
|
275
|
+
|
276
|
+
class SimpleAMSCommentSerializer
|
277
|
+
include SimpleAMS::DSL
|
278
|
+
|
279
|
+
attributes :id, :body
|
280
|
+
end
|
281
|
+
|
282
|
+
class SimpleAMSPostSerializer
|
283
|
+
include SimpleAMS::DSL
|
284
|
+
|
285
|
+
attributes :id, :body
|
286
|
+
attribute :commenter_names
|
287
|
+
has_many :comments, serializer: SimpleAMSCommentSerializer
|
288
|
+
|
289
|
+
def commenter_names
|
290
|
+
object.commenters.pluck(:name)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# --- Test data creation ---
|
295
|
+
|
296
|
+
100.times do |i|
|
297
|
+
post = Post.create!(body: "post#{i}")
|
298
|
+
user1 = User.create!(name: "John#{i}")
|
299
|
+
user2 = User.create!(name: "Jane#{i}")
|
300
|
+
10.times do |n|
|
301
|
+
post.comments.create!(commenter: user1, body: "Comment1_#{i}_#{n}")
|
302
|
+
post.comments.create!(commenter: user2, body: "Comment2_#{i}_#{n}")
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
posts = Post.all.to_a
|
307
|
+
|
308
|
+
# --- Store the serializers in procs ---
|
309
|
+
|
310
|
+
alba = Proc.new { AlbaPostResource.new(posts).serialize }
|
311
|
+
alba_inline = Proc.new do
|
312
|
+
Alba.serialize(posts) do
|
313
|
+
attributes :id, :body
|
314
|
+
attribute :commenter_names do |post|
|
315
|
+
post.commenters.pluck(:name)
|
316
|
+
end
|
317
|
+
many :comments do
|
318
|
+
attributes :id, :body
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
ams = Proc.new { ActiveModelSerializers::SerializableResource.new(posts, {}).as_json }
|
323
|
+
blueprinter = Proc.new { PostBlueprint.render(posts) }
|
324
|
+
jbuilder = Proc.new do
|
325
|
+
Jbuilder.new do |json|
|
326
|
+
json.array!(posts) do |post|
|
327
|
+
json.post post.to_builder
|
328
|
+
end
|
329
|
+
end.target!
|
330
|
+
end
|
331
|
+
jsonapi = proc { JsonApiStandardPostSerializer.new(posts).to_json }
|
332
|
+
jsonapi_same_format = proc { JsonApiSameFormatPostSerializer.new(posts).to_json }
|
333
|
+
primalize = proc { PrimalizePostsResource.new(posts: posts).to_json }
|
334
|
+
rails = Proc.new do
|
335
|
+
ActiveSupport::JSON.encode(posts.map{ |post| post.serializable_hash(include: :comments) })
|
336
|
+
end
|
337
|
+
representable = Proc.new { PostsRepresenter.new(posts).to_json }
|
338
|
+
simple_ams = Proc.new { SimpleAMS::Renderer::Collection.new(posts, serializer: SimpleAMSPostSerializer).to_json }
|
339
|
+
|
340
|
+
# --- Execute the serializers to check their output ---
|
341
|
+
|
342
|
+
puts "Serializer outputs ----------------------------------"
|
343
|
+
{
|
344
|
+
alba: alba,
|
345
|
+
alba_inline: alba_inline,
|
346
|
+
ams: ams,
|
347
|
+
blueprinter: blueprinter,
|
348
|
+
jbuilder: jbuilder, # different order
|
349
|
+
jsonapi: jsonapi, # nested JSON:API format
|
350
|
+
jsonapi_same_format: jsonapi_same_format,
|
351
|
+
primalize: primalize,
|
352
|
+
rails: rails,
|
353
|
+
representable: representable,
|
354
|
+
simple_ams: simple_ams,
|
355
|
+
}.each { |name, serializer| puts "#{name.to_s.ljust(24, ' ')} #{serializer.call}" }
|
356
|
+
|
357
|
+
# --- Run the benchmarks ---
|
358
|
+
|
359
|
+
require 'benchmark/ips'
|
360
|
+
Benchmark.ips do |x|
|
361
|
+
x.report(:alba, &alba)
|
362
|
+
x.report(:alba_inline, &alba_inline)
|
363
|
+
x.report(:ams, &ams)
|
364
|
+
x.report(:blueprinter, &blueprinter)
|
365
|
+
x.report(:jbuilder, &jbuilder)
|
366
|
+
x.report(:jsonapi, &jsonapi)
|
367
|
+
x.report(:jsonapi_same_format, &jsonapi_same_format)
|
368
|
+
x.report(:primalize, &primalize)
|
369
|
+
x.report(:rails, &rails)
|
370
|
+
x.report(:representable, &representable)
|
371
|
+
x.report(:simple_ams, &simple_ams)
|
372
|
+
|
373
|
+
x.compare!
|
374
|
+
end
|
375
|
+
|
376
|
+
|
377
|
+
require 'benchmark/memory'
|
378
|
+
Benchmark.memory do |x|
|
379
|
+
x.report(:alba, &alba)
|
380
|
+
x.report(:alba_inline, &alba_inline)
|
381
|
+
x.report(:ams, &ams)
|
382
|
+
x.report(:blueprinter, &blueprinter)
|
383
|
+
x.report(:jbuilder, &jbuilder)
|
384
|
+
x.report(:jsonapi, &jsonapi)
|
385
|
+
x.report(:jsonapi_same_format, &jsonapi_same_format)
|
386
|
+
x.report(:primalize, &primalize)
|
387
|
+
x.report(:rails, &rails)
|
388
|
+
x.report(:representable, &representable)
|
389
|
+
x.report(:simple_ams, &simple_ams)
|
390
|
+
|
391
|
+
x.compare!
|
392
|
+
end
|
@@ -0,0 +1,370 @@
|
|
1
|
+
# Benchmark script to run varieties of JSON serializers
|
2
|
+
# Fetch Alba from local, otherwise fetch latest from RubyGems
|
3
|
+
|
4
|
+
# --- Bundle dependencies ---
|
5
|
+
|
6
|
+
require "bundler/inline"
|
7
|
+
|
8
|
+
gemfile(true) do
|
9
|
+
source "https://rubygems.org"
|
10
|
+
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
11
|
+
|
12
|
+
gem "active_model_serializers"
|
13
|
+
gem "activerecord", "6.1.3"
|
14
|
+
gem "alba", path: '../'
|
15
|
+
gem "benchmark-ips"
|
16
|
+
gem "blueprinter"
|
17
|
+
gem "jbuilder"
|
18
|
+
gem "jsonapi-serializer" # successor of fast_jsonapi
|
19
|
+
gem "multi_json"
|
20
|
+
gem "primalize"
|
21
|
+
gem "oj"
|
22
|
+
gem "representable"
|
23
|
+
gem "simple_ams"
|
24
|
+
gem "sqlite3"
|
25
|
+
end
|
26
|
+
|
27
|
+
# --- Test data model setup ---
|
28
|
+
|
29
|
+
require "active_record"
|
30
|
+
require "logger"
|
31
|
+
require "oj"
|
32
|
+
require "sqlite3"
|
33
|
+
Oj.optimize_rails
|
34
|
+
|
35
|
+
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
|
36
|
+
# ActiveRecord::Base.logger = Logger.new($stdout)
|
37
|
+
|
38
|
+
ActiveRecord::Schema.define do
|
39
|
+
create_table :posts, force: true do |t|
|
40
|
+
t.string :body
|
41
|
+
end
|
42
|
+
|
43
|
+
create_table :comments, force: true do |t|
|
44
|
+
t.integer :post_id
|
45
|
+
t.string :body
|
46
|
+
t.integer :commenter_id
|
47
|
+
end
|
48
|
+
|
49
|
+
create_table :users, force: true do |t|
|
50
|
+
t.string :name
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Post < ActiveRecord::Base
|
55
|
+
has_many :comments
|
56
|
+
has_many :commenters, through: :comments, class_name: 'User', source: :commenter
|
57
|
+
|
58
|
+
def attributes
|
59
|
+
{id: nil, body: nil, commenter_names: commenter_names}
|
60
|
+
end
|
61
|
+
|
62
|
+
def commenter_names
|
63
|
+
commenters.pluck(:name)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Comment < ActiveRecord::Base
|
68
|
+
belongs_to :post
|
69
|
+
belongs_to :commenter, class_name: 'User'
|
70
|
+
|
71
|
+
def attributes
|
72
|
+
{id: nil, body: nil}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class User < ActiveRecord::Base
|
77
|
+
has_many :comments
|
78
|
+
end
|
79
|
+
|
80
|
+
# --- Alba serializers ---
|
81
|
+
|
82
|
+
require "alba"
|
83
|
+
|
84
|
+
class AlbaCommentResource
|
85
|
+
include ::Alba::Resource
|
86
|
+
attributes :id, :body
|
87
|
+
end
|
88
|
+
|
89
|
+
class AlbaPostResource
|
90
|
+
include ::Alba::Resource
|
91
|
+
attributes :id, :body
|
92
|
+
attribute :commenter_names do |post|
|
93
|
+
post.commenters.pluck(:name)
|
94
|
+
end
|
95
|
+
many :comments, resource: AlbaCommentResource
|
96
|
+
end
|
97
|
+
|
98
|
+
# --- ActiveModelSerializer serializers ---
|
99
|
+
|
100
|
+
require "active_model_serializers"
|
101
|
+
|
102
|
+
class AMSCommentSerializer < ActiveModel::Serializer
|
103
|
+
attributes :id, :body
|
104
|
+
end
|
105
|
+
|
106
|
+
class AMSPostSerializer < ActiveModel::Serializer
|
107
|
+
attributes :id, :body
|
108
|
+
attribute :commenter_names
|
109
|
+
has_many :comments, serializer: AMSCommentSerializer
|
110
|
+
|
111
|
+
def commenter_names
|
112
|
+
object.commenters.pluck(:name)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# --- Blueprint serializers ---
|
117
|
+
|
118
|
+
require "blueprinter"
|
119
|
+
|
120
|
+
class CommentBlueprint < Blueprinter::Base
|
121
|
+
fields :id, :body
|
122
|
+
end
|
123
|
+
|
124
|
+
class PostBlueprint < Blueprinter::Base
|
125
|
+
fields :id, :body, :commenter_names
|
126
|
+
association :comments, blueprint: CommentBlueprint
|
127
|
+
|
128
|
+
def commenter_names
|
129
|
+
commenters.pluck(:name)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# --- JBuilder serializers ---
|
134
|
+
|
135
|
+
require "jbuilder"
|
136
|
+
|
137
|
+
class Post
|
138
|
+
def to_builder
|
139
|
+
Jbuilder.new do |post|
|
140
|
+
post.call(self, :id, :body, :commenter_names, :comments)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def commenter_names
|
145
|
+
commenters.pluck(:name)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class Comment
|
150
|
+
def to_builder
|
151
|
+
Jbuilder.new do |comment|
|
152
|
+
comment.call(self, :id, :body)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# --- JSONAPI:Serializer serializers / (successor of fast_jsonapi) ---
|
158
|
+
|
159
|
+
class JsonApiStandardCommentSerializer
|
160
|
+
include JSONAPI::Serializer
|
161
|
+
|
162
|
+
attribute :id
|
163
|
+
attribute :body
|
164
|
+
end
|
165
|
+
|
166
|
+
class JsonApiStandardPostSerializer
|
167
|
+
include JSONAPI::Serializer
|
168
|
+
|
169
|
+
# set_type :post # optional
|
170
|
+
attribute :id
|
171
|
+
attribute :body
|
172
|
+
attribute :commenter_names
|
173
|
+
|
174
|
+
attribute :comments do |post|
|
175
|
+
post.comments.map { |comment| JsonApiSameFormatCommentSerializer.new(comment) }
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# --- JSONAPI:Serializer serializers that format the code the same flat way as the other gems here ---
|
180
|
+
|
181
|
+
# code to convert from JSON:API output to "flat" JSON, like the other serializers build
|
182
|
+
class JsonApiSameFormatSerializer
|
183
|
+
include JSONAPI::Serializer
|
184
|
+
|
185
|
+
def as_json(*_options)
|
186
|
+
hash = serializable_hash
|
187
|
+
|
188
|
+
if hash[:data].is_a? Hash
|
189
|
+
hash[:data][:attributes]
|
190
|
+
|
191
|
+
elsif hash[:data].is_a? Array
|
192
|
+
hash[:data].pluck(:attributes)
|
193
|
+
|
194
|
+
elsif hash[:data].nil?
|
195
|
+
{ }
|
196
|
+
|
197
|
+
else
|
198
|
+
raise "unexpected data type #{hash[:data].class}"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
class JsonApiSameFormatCommentSerializer < JsonApiSameFormatSerializer
|
204
|
+
attribute :id
|
205
|
+
attribute :body
|
206
|
+
end
|
207
|
+
|
208
|
+
class JsonApiSameFormatPostSerializer < JsonApiSameFormatSerializer
|
209
|
+
attribute :id
|
210
|
+
attribute :body
|
211
|
+
attribute :commenter_names
|
212
|
+
|
213
|
+
attribute :comments do |post|
|
214
|
+
post.comments.map { |comment| JsonApiSameFormatCommentSerializer.new(comment) }
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# --- Primalize serializers ---
|
219
|
+
#
|
220
|
+
class PrimalizeCommentResource < Primalize::Single
|
221
|
+
attributes id: integer, body: string
|
222
|
+
end
|
223
|
+
|
224
|
+
class PrimalizePostResource < Primalize::Single
|
225
|
+
alias post object
|
226
|
+
|
227
|
+
attributes(
|
228
|
+
id: integer,
|
229
|
+
body: string,
|
230
|
+
comments: array(primalize(PrimalizeCommentResource)),
|
231
|
+
commenter_names: array(string),
|
232
|
+
)
|
233
|
+
|
234
|
+
def commenter_names
|
235
|
+
post.commenters.pluck(:name)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# --- Representable serializers ---
|
240
|
+
|
241
|
+
require "representable"
|
242
|
+
|
243
|
+
class CommentRepresenter < Representable::Decorator
|
244
|
+
include Representable::JSON
|
245
|
+
|
246
|
+
property :id
|
247
|
+
property :body
|
248
|
+
end
|
249
|
+
|
250
|
+
class PostRepresenter < Representable::Decorator
|
251
|
+
include Representable::JSON
|
252
|
+
|
253
|
+
property :id
|
254
|
+
property :body
|
255
|
+
property :commenter_names
|
256
|
+
collection :comments
|
257
|
+
|
258
|
+
def commenter_names
|
259
|
+
commenters.pluck(:name)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
# --- SimpleAMS serializers ---
|
264
|
+
|
265
|
+
require "simple_ams"
|
266
|
+
|
267
|
+
class SimpleAMSCommentSerializer
|
268
|
+
include SimpleAMS::DSL
|
269
|
+
|
270
|
+
attributes :id, :body
|
271
|
+
end
|
272
|
+
|
273
|
+
class SimpleAMSPostSerializer
|
274
|
+
include SimpleAMS::DSL
|
275
|
+
|
276
|
+
attributes :id, :body
|
277
|
+
attribute :commenter_names
|
278
|
+
has_many :comments, serializer: SimpleAMSCommentSerializer
|
279
|
+
|
280
|
+
def commenter_names
|
281
|
+
object.commenters.pluck(:name)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# --- Test data creation ---
|
286
|
+
|
287
|
+
post = Post.create!(body: 'post')
|
288
|
+
user1 = User.create!(name: 'John')
|
289
|
+
user2 = User.create!(name: 'Jane')
|
290
|
+
post.comments.create!(commenter: user1, body: 'Comment1')
|
291
|
+
post.comments.create!(commenter: user2, body: 'Comment2')
|
292
|
+
post.reload
|
293
|
+
|
294
|
+
# --- Store the serializers in procs ---
|
295
|
+
|
296
|
+
alba = Proc.new { AlbaPostResource.new(post).serialize }
|
297
|
+
alba_inline = Proc.new do
|
298
|
+
Alba.serialize(post) do
|
299
|
+
attributes :id, :body
|
300
|
+
attribute :commenter_names do |post|
|
301
|
+
post.commenters.pluck(:name)
|
302
|
+
end
|
303
|
+
many :comments do
|
304
|
+
attributes :id, :body
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
ams = Proc.new { AMSPostSerializer.new(post, {}).to_json }
|
309
|
+
blueprinter = Proc.new { PostBlueprint.render(post) }
|
310
|
+
jbuilder = Proc.new { post.to_builder.target! }
|
311
|
+
jsonapi = proc { JsonApiStandardPostSerializer.new(post).to_json }
|
312
|
+
jsonapi_same_format = proc { JsonApiSameFormatPostSerializer.new(post).to_json }
|
313
|
+
primalize = proc { PrimalizePostResource.new(post).to_json }
|
314
|
+
rails = Proc.new { ActiveSupport::JSON.encode(post.serializable_hash(include: :comments)) }
|
315
|
+
representable = Proc.new { PostRepresenter.new(post).to_json }
|
316
|
+
simple_ams = Proc.new { SimpleAMS::Renderer.new(post, serializer: SimpleAMSPostSerializer).to_json }
|
317
|
+
|
318
|
+
# --- Execute the serializers to check their output ---
|
319
|
+
|
320
|
+
puts "Serializer outputs ----------------------------------"
|
321
|
+
{
|
322
|
+
alba: alba,
|
323
|
+
alba_inline: alba_inline,
|
324
|
+
ams: ams,
|
325
|
+
blueprinter: blueprinter,
|
326
|
+
jbuilder: jbuilder, # different order
|
327
|
+
jsonapi: jsonapi, # nested JSON:API format
|
328
|
+
jsonapi_same_format: jsonapi_same_format,
|
329
|
+
primalize: primalize,
|
330
|
+
rails: rails,
|
331
|
+
representable: representable,
|
332
|
+
simple_ams: simple_ams,
|
333
|
+
}.each { |name, serializer| puts "#{name.to_s.ljust(24, ' ')} #{serializer.call}" }
|
334
|
+
|
335
|
+
# --- Run the benchmarks ---
|
336
|
+
|
337
|
+
require 'benchmark/ips'
|
338
|
+
Benchmark.ips do |x|
|
339
|
+
x.report(:alba, &alba)
|
340
|
+
x.report(:alba_inline, &alba_inline)
|
341
|
+
x.report(:ams, &ams)
|
342
|
+
x.report(:blueprinter, &blueprinter)
|
343
|
+
x.report(:jbuilder, &jbuilder)
|
344
|
+
x.report(:jsonapi, &jsonapi)
|
345
|
+
x.report(:jsonapi_same_format, &jsonapi_same_format)
|
346
|
+
x.report(:primalize, &primalize)
|
347
|
+
x.report(:rails, &rails)
|
348
|
+
x.report(:representable, &representable)
|
349
|
+
x.report(:simple_ams, &simple_ams)
|
350
|
+
|
351
|
+
x.compare!
|
352
|
+
end
|
353
|
+
|
354
|
+
|
355
|
+
require 'benchmark/memory'
|
356
|
+
Benchmark.memory do |x|
|
357
|
+
x.report(:alba, &alba)
|
358
|
+
x.report(:alba_inline, &alba_inline)
|
359
|
+
x.report(:ams, &ams)
|
360
|
+
x.report(:blueprinter, &blueprinter)
|
361
|
+
x.report(:jbuilder, &jbuilder)
|
362
|
+
x.report(:jsonapi, &jsonapi)
|
363
|
+
x.report(:jsonapi_same_format, &jsonapi_same_format)
|
364
|
+
x.report(:primalize, &primalize)
|
365
|
+
x.report(:rails, &rails)
|
366
|
+
x.report(:representable, &representable)
|
367
|
+
x.report(:simple_ams, &simple_ams)
|
368
|
+
|
369
|
+
x.compare!
|
370
|
+
end
|