alba 1.1.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,441 @@
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 "jserializer"
20
+ gem "jsonapi-serializer" # successor of fast_jsonapi
21
+ gem "multi_json"
22
+ gem "panko_serializer"
23
+ gem "pg"
24
+ gem "primalize"
25
+ gem "oj"
26
+ gem "representable"
27
+ gem "simple_ams"
28
+ gem "sqlite3"
29
+ end
30
+
31
+ # --- Test data model setup ---
32
+
33
+ require "pg"
34
+ require "active_record"
35
+ require "active_record/connection_adapters/postgresql_adapter"
36
+ require "logger"
37
+ require "oj"
38
+ require "sqlite3"
39
+ Oj.optimize_rails
40
+
41
+ ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
42
+ # ActiveRecord::Base.logger = Logger.new($stdout)
43
+
44
+ ActiveRecord::Schema.define do
45
+ create_table :posts, force: true do |t|
46
+ t.string :body
47
+ end
48
+
49
+ create_table :comments, force: true do |t|
50
+ t.integer :post_id
51
+ t.string :body
52
+ t.integer :commenter_id
53
+ end
54
+
55
+ create_table :users, force: true do |t|
56
+ t.string :name
57
+ end
58
+ end
59
+
60
+ class Post < ActiveRecord::Base
61
+ has_many :comments
62
+ has_many :commenters, through: :comments, class_name: 'User', source: :commenter
63
+
64
+ def attributes
65
+ {id: nil, body: nil, commenter_names: commenter_names}
66
+ end
67
+
68
+ def commenter_names
69
+ commenters.pluck(:name)
70
+ end
71
+ end
72
+
73
+ class Comment < ActiveRecord::Base
74
+ belongs_to :post
75
+ belongs_to :commenter, class_name: 'User'
76
+
77
+ def attributes
78
+ {id: nil, body: nil}
79
+ end
80
+ end
81
+
82
+ class User < ActiveRecord::Base
83
+ has_many :comments
84
+ end
85
+
86
+ # --- Alba serializers ---
87
+
88
+ require "alba"
89
+
90
+ class AlbaCommentResource
91
+ include ::Alba::Resource
92
+ attributes :id, :body
93
+ end
94
+
95
+ class AlbaPostResource
96
+ include ::Alba::Resource
97
+ attributes :id, :body
98
+ attribute :commenter_names do |post|
99
+ post.commenters.pluck(:name)
100
+ end
101
+ many :comments, resource: AlbaCommentResource
102
+ end
103
+
104
+ # --- ActiveModelSerializer serializers ---
105
+
106
+ require "active_model_serializers"
107
+
108
+ ActiveModelSerializers.logger = Logger.new(nil)
109
+
110
+ class AMSCommentSerializer < ActiveModel::Serializer
111
+ attributes :id, :body
112
+ end
113
+
114
+ class AMSPostSerializer < ActiveModel::Serializer
115
+ attributes :id, :body
116
+ attribute :commenter_names
117
+ has_many :comments, serializer: AMSCommentSerializer
118
+
119
+ def commenter_names
120
+ object.commenters.pluck(:name)
121
+ end
122
+ end
123
+
124
+ # --- Blueprint serializers ---
125
+
126
+ require "blueprinter"
127
+
128
+ class CommentBlueprint < Blueprinter::Base
129
+ fields :id, :body
130
+ end
131
+
132
+ class PostBlueprint < Blueprinter::Base
133
+ fields :id, :body, :commenter_names
134
+ association :comments, blueprint: CommentBlueprint
135
+
136
+ def commenter_names
137
+ commenters.pluck(:name)
138
+ end
139
+ end
140
+
141
+ # --- JBuilder serializers ---
142
+
143
+ require "jbuilder"
144
+
145
+ class Post
146
+ def to_builder
147
+ Jbuilder.new do |post|
148
+ post.call(self, :id, :body, :commenter_names, :comments)
149
+ end
150
+ end
151
+
152
+ def commenter_names
153
+ commenters.pluck(:name)
154
+ end
155
+ end
156
+
157
+ class Comment
158
+ def to_builder
159
+ Jbuilder.new do |comment|
160
+ comment.call(self, :id, :body)
161
+ end
162
+ end
163
+ end
164
+
165
+ # --- Jserializer serializers ---
166
+
167
+ require 'jserializer'
168
+
169
+ class JserializerCommentSerializer < Jserializer::Base
170
+ attributes :id, :body
171
+ end
172
+
173
+ class JserializerPostSerializer < Jserializer::Base
174
+ attributes :id, :body, :commenter_names
175
+ has_many :comments, serializer: JserializerCommentSerializer
176
+ def commenter_names
177
+ object.commenters.pluck(:name)
178
+ end
179
+ end
180
+
181
+ # --- JSONAPI:Serializer serializers / (successor of fast_jsonapi) ---
182
+
183
+ class JsonApiStandardCommentSerializer
184
+ include JSONAPI::Serializer
185
+
186
+ attribute :id
187
+ attribute :body
188
+ end
189
+
190
+ class JsonApiStandardPostSerializer
191
+ include JSONAPI::Serializer
192
+
193
+ # set_type :post # optional
194
+ attribute :id
195
+ attribute :body
196
+ attribute :commenter_names
197
+
198
+ attribute :comments do |post|
199
+ post.comments.map { |comment| JsonApiSameFormatCommentSerializer.new(comment) }
200
+ end
201
+ end
202
+
203
+ # --- JSONAPI:Serializer serializers that format the code the same flat way as the other gems here ---
204
+
205
+ # code to convert from JSON:API output to "flat" JSON, like the other serializers build
206
+ class JsonApiSameFormatSerializer
207
+ include JSONAPI::Serializer
208
+
209
+ def as_json(*_options)
210
+ hash = serializable_hash
211
+
212
+ if hash[:data].is_a? Hash
213
+ hash[:data][:attributes]
214
+
215
+ elsif hash[:data].is_a? Array
216
+ hash[:data].pluck(:attributes)
217
+
218
+ elsif hash[:data].nil?
219
+ { }
220
+
221
+ else
222
+ raise "unexpected data type #{hash[:data].class}"
223
+ end
224
+ end
225
+ end
226
+
227
+ class JsonApiSameFormatCommentSerializer < JsonApiSameFormatSerializer
228
+ attribute :id
229
+ attribute :body
230
+ end
231
+
232
+ class JsonApiSameFormatPostSerializer < JsonApiSameFormatSerializer
233
+ attribute :id
234
+ attribute :body
235
+ attribute :commenter_names
236
+
237
+ attribute :comments do |post|
238
+ post.comments.map { |comment| JsonApiSameFormatCommentSerializer.new(comment) }
239
+ end
240
+ end
241
+
242
+ # --- Panko serializers ---
243
+ #
244
+
245
+ require "panko_serializer"
246
+
247
+ class PankoCommentSerializer < Panko::Serializer
248
+ attributes :id, :body
249
+ end
250
+
251
+
252
+ class PankoPostSerializer < Panko::Serializer
253
+ attributes :id, :body, :commenter_names
254
+
255
+ has_many :comments, serializer: PankoCommentSerializer
256
+
257
+ def commenter_names
258
+ object.comments.pluck(:name)
259
+ end
260
+ end
261
+
262
+ # --- Primalize serializers ---
263
+ #
264
+ class PrimalizeCommentResource < Primalize::Single
265
+ attributes id: integer, body: string
266
+ end
267
+
268
+ class PrimalizePostResource < Primalize::Single
269
+ alias post object
270
+
271
+ attributes(
272
+ id: integer,
273
+ body: string,
274
+ comments: array(primalize(PrimalizeCommentResource)),
275
+ commenter_names: array(string),
276
+ )
277
+
278
+ def commenter_names
279
+ post.commenters.pluck(:name)
280
+ end
281
+ end
282
+
283
+ class PrimalizePostsResource < Primalize::Many
284
+ attributes posts: enumerable(PrimalizePostResource)
285
+ end
286
+
287
+ # --- Representable serializers ---
288
+
289
+ require "representable"
290
+
291
+ class CommentRepresenter < Representable::Decorator
292
+ include Representable::JSON
293
+
294
+ property :id
295
+ property :body
296
+ end
297
+
298
+ class PostsRepresenter < Representable::Decorator
299
+ include Representable::JSON::Collection
300
+
301
+ items class: Post do
302
+ property :id
303
+ property :body
304
+ property :commenter_names
305
+ collection :comments
306
+ end
307
+
308
+ def commenter_names
309
+ commenters.pluck(:name)
310
+ end
311
+ end
312
+
313
+ # --- SimpleAMS serializers ---
314
+
315
+ require "simple_ams"
316
+
317
+ class SimpleAMSCommentSerializer
318
+ include SimpleAMS::DSL
319
+
320
+ attributes :id, :body
321
+ end
322
+
323
+ class SimpleAMSPostSerializer
324
+ include SimpleAMS::DSL
325
+
326
+ attributes :id, :body
327
+ attribute :commenter_names
328
+ has_many :comments, serializer: SimpleAMSCommentSerializer
329
+
330
+ def commenter_names
331
+ object.commenters.pluck(:name)
332
+ end
333
+ end
334
+
335
+ # --- Test data creation ---
336
+
337
+ 100.times do |i|
338
+ post = Post.create!(body: "post#{i}")
339
+ user1 = User.create!(name: "John#{i}")
340
+ user2 = User.create!(name: "Jane#{i}")
341
+ 10.times do |n|
342
+ post.comments.create!(commenter: user1, body: "Comment1_#{i}_#{n}")
343
+ post.comments.create!(commenter: user2, body: "Comment2_#{i}_#{n}")
344
+ end
345
+ end
346
+
347
+ posts = Post.all.to_a
348
+
349
+ # --- Store the serializers in procs ---
350
+
351
+ alba = Proc.new { AlbaPostResource.new(posts).serialize }
352
+ alba_inline = Proc.new do
353
+ Alba.serialize(posts) do
354
+ attributes :id, :body
355
+ attribute :commenter_names do |post|
356
+ post.commenters.pluck(:name)
357
+ end
358
+ many :comments do
359
+ attributes :id, :body
360
+ end
361
+ end
362
+ end
363
+ ams = Proc.new { ActiveModelSerializers::SerializableResource.new(posts, {}).as_json }
364
+ blueprinter = Proc.new { PostBlueprint.render(posts) }
365
+ jbuilder = Proc.new do
366
+ Jbuilder.new do |json|
367
+ json.array!(posts) do |post|
368
+ json.post post.to_builder
369
+ end
370
+ end.target!
371
+ end
372
+ jserializer = Proc.new { JserializerPostSerializer.new(posts, is_collection: true).to_json }
373
+ jsonapi = proc { JsonApiStandardPostSerializer.new(posts).to_json }
374
+ jsonapi_same_format = proc { JsonApiSameFormatPostSerializer.new(posts).to_json }
375
+ panko = proc { Panko::ArraySerializer.new(posts, each_serializer: PankoPostSerializer).to_json }
376
+ primalize = proc { PrimalizePostsResource.new(posts: posts).to_json }
377
+ rails = Proc.new do
378
+ ActiveSupport::JSON.encode(posts.map{ |post| post.serializable_hash(include: :comments) })
379
+ end
380
+ representable = Proc.new { PostsRepresenter.new(posts).to_json }
381
+ simple_ams = Proc.new { SimpleAMS::Renderer::Collection.new(posts, serializer: SimpleAMSPostSerializer).to_json }
382
+
383
+ # --- Execute the serializers to check their output ---
384
+
385
+ puts "Serializer outputs ----------------------------------"
386
+ {
387
+ alba: alba,
388
+ alba_inline: alba_inline,
389
+ ams: ams,
390
+ blueprinter: blueprinter,
391
+ jbuilder: jbuilder, # different order
392
+ jserializer: jserializer,
393
+ jsonapi: jsonapi, # nested JSON:API format
394
+ jsonapi_same_format: jsonapi_same_format,
395
+ panko: panko,
396
+ primalize: primalize,
397
+ rails: rails,
398
+ representable: representable,
399
+ simple_ams: simple_ams,
400
+ }.each { |name, serializer| puts "#{name.to_s.ljust(24, ' ')} #{serializer.call}" }
401
+
402
+ # --- Run the benchmarks ---
403
+
404
+ require 'benchmark/ips'
405
+ Benchmark.ips do |x|
406
+ x.report(:alba, &alba)
407
+ x.report(:alba_inline, &alba_inline)
408
+ x.report(:ams, &ams)
409
+ x.report(:blueprinter, &blueprinter)
410
+ x.report(:jbuilder, &jbuilder)
411
+ x.report(:jserializer, &jserializer)
412
+ x.report(:jsonapi, &jsonapi)
413
+ x.report(:jsonapi_same_format, &jsonapi_same_format)
414
+ x.report(:panko, &panko)
415
+ x.report(:primalize, &primalize)
416
+ x.report(:rails, &rails)
417
+ x.report(:representable, &representable)
418
+ x.report(:simple_ams, &simple_ams)
419
+
420
+ x.compare!
421
+ end
422
+
423
+
424
+ require 'benchmark/memory'
425
+ Benchmark.memory do |x|
426
+ x.report(:alba, &alba)
427
+ x.report(:alba_inline, &alba_inline)
428
+ x.report(:ams, &ams)
429
+ x.report(:blueprinter, &blueprinter)
430
+ x.report(:jbuilder, &jbuilder)
431
+ x.report(:jserializer, &jserializer)
432
+ x.report(:jsonapi, &jsonapi)
433
+ x.report(:jsonapi_same_format, &jsonapi_same_format)
434
+ x.report(:panko, &panko)
435
+ x.report(:primalize, &primalize)
436
+ x.report(:rails, &rails)
437
+ x.report(:representable, &representable)
438
+ x.report(:simple_ams, &simple_ams)
439
+
440
+ x.compare!
441
+ end
@@ -13,18 +13,26 @@ gemfile(true) do
13
13
  gem "activerecord", "6.1.3"
14
14
  gem "alba", path: '../'
15
15
  gem "benchmark-ips"
16
+ gem "benchmark-memory"
16
17
  gem "blueprinter"
17
18
  gem "jbuilder"
19
+ gem "jserializer"
18
20
  gem "jsonapi-serializer" # successor of fast_jsonapi
19
21
  gem "multi_json"
22
+ gem "panko_serializer"
23
+ gem "pg"
24
+ gem "primalize"
20
25
  gem "oj"
21
26
  gem "representable"
27
+ gem "simple_ams"
22
28
  gem "sqlite3"
23
29
  end
24
30
 
25
31
  # --- Test data model setup ---
26
32
 
33
+ require "pg"
27
34
  require "active_record"
35
+ require "active_record/connection_adapters/postgresql_adapter"
28
36
  require "logger"
29
37
  require "oj"
30
38
  require "sqlite3"
@@ -152,6 +160,23 @@ class Comment
152
160
  end
153
161
  end
154
162
 
163
+ # --- Jserializer serializers ---
164
+
165
+ require 'jserializer'
166
+
167
+ class JserializerCommentSerializer < Jserializer::Base
168
+ attributes :id, :body
169
+ end
170
+
171
+ class JserializerPostSerializer < Jserializer::Base
172
+ attributes :id, :body, :commenter_names
173
+ has_many :comments, serializer: JserializerCommentSerializer
174
+ def commenter_names
175
+ object.commenters.pluck(:name)
176
+ end
177
+ end
178
+
179
+
155
180
  # --- JSONAPI:Serializer serializers / (successor of fast_jsonapi) ---
156
181
 
157
182
  class JsonApiStandardCommentSerializer
@@ -213,6 +238,46 @@ class JsonApiSameFormatPostSerializer < JsonApiSameFormatSerializer
213
238
  end
214
239
  end
215
240
 
241
+ # --- Panko serializers ---
242
+ #
243
+ require "panko_serializer"
244
+
245
+ class PankoCommentSerializer < Panko::Serializer
246
+ attributes :id, :body
247
+ end
248
+
249
+
250
+ class PankoPostSerializer < Panko::Serializer
251
+ attributes :id, :body, :commenter_names
252
+
253
+ has_many :comments, serializer: PankoCommentSerializer
254
+
255
+ def commenter_names
256
+ object.comments.pluck(:name)
257
+ end
258
+ end
259
+
260
+ # --- Primalize serializers ---
261
+ #
262
+ class PrimalizeCommentResource < Primalize::Single
263
+ attributes id: integer, body: string
264
+ end
265
+
266
+ class PrimalizePostResource < Primalize::Single
267
+ alias post object
268
+
269
+ attributes(
270
+ id: integer,
271
+ body: string,
272
+ comments: array(primalize(PrimalizeCommentResource)),
273
+ commenter_names: array(string),
274
+ )
275
+
276
+ def commenter_names
277
+ post.commenters.pluck(:name)
278
+ end
279
+ end
280
+
216
281
  # --- Representable serializers ---
217
282
 
218
283
  require "representable"
@@ -237,6 +302,28 @@ class PostRepresenter < Representable::Decorator
237
302
  end
238
303
  end
239
304
 
305
+ # --- SimpleAMS serializers ---
306
+
307
+ require "simple_ams"
308
+
309
+ class SimpleAMSCommentSerializer
310
+ include SimpleAMS::DSL
311
+
312
+ attributes :id, :body
313
+ end
314
+
315
+ class SimpleAMSPostSerializer
316
+ include SimpleAMS::DSL
317
+
318
+ attributes :id, :body
319
+ attribute :commenter_names
320
+ has_many :comments, serializer: SimpleAMSCommentSerializer
321
+
322
+ def commenter_names
323
+ object.commenters.pluck(:name)
324
+ end
325
+ end
326
+
240
327
  # --- Test data creation ---
241
328
 
242
329
  post = Post.create!(body: 'post')
@@ -263,10 +350,14 @@ end
263
350
  ams = Proc.new { AMSPostSerializer.new(post, {}).to_json }
264
351
  blueprinter = Proc.new { PostBlueprint.render(post) }
265
352
  jbuilder = Proc.new { post.to_builder.target! }
353
+ jserializer = Proc.new { JserializerPostSerializer.new(post).to_json }
266
354
  jsonapi = proc { JsonApiStandardPostSerializer.new(post).to_json }
267
355
  jsonapi_same_format = proc { JsonApiSameFormatPostSerializer.new(post).to_json }
356
+ panko = proc { PankoPostSerializer.new.serialize_to_json(post) }
357
+ primalize = proc { PrimalizePostResource.new(post).to_json }
268
358
  rails = Proc.new { ActiveSupport::JSON.encode(post.serializable_hash(include: :comments)) }
269
359
  representable = Proc.new { PostRepresenter.new(post).to_json }
360
+ simple_ams = Proc.new { SimpleAMS::Renderer.new(post, serializer: SimpleAMSPostSerializer).to_json }
270
361
 
271
362
  # --- Execute the serializers to check their output ---
272
363
 
@@ -277,28 +368,18 @@ puts "Serializer outputs ----------------------------------"
277
368
  ams: ams,
278
369
  blueprinter: blueprinter,
279
370
  jbuilder: jbuilder, # different order
371
+ jserializer: jserializer,
280
372
  jsonapi: jsonapi, # nested JSON:API format
281
373
  jsonapi_same_format: jsonapi_same_format,
374
+ panko: panko,
375
+ primalize: primalize,
282
376
  rails: rails,
283
- representable: representable
377
+ representable: representable,
378
+ simple_ams: simple_ams,
284
379
  }.each { |name, serializer| puts "#{name.to_s.ljust(24, ' ')} #{serializer.call}" }
285
380
 
286
381
  # --- Run the benchmarks ---
287
382
 
288
- require 'benchmark'
289
- time = 1000
290
- Benchmark.bmbm do |x|
291
- x.report(:alba) { time.times(&alba) }
292
- x.report(:alba_inline) { time.times(&alba_inline) }
293
- x.report(:ams) { time.times(&ams) }
294
- x.report(:blueprinter) { time.times(&blueprinter) }
295
- x.report(:jbuilder) { time.times(&jbuilder) }
296
- x.report(:jsonapi) { time.times(&jsonapi) }
297
- x.report(:jsonapi_same_format) { time.times(&jsonapi_same_format) }
298
- x.report(:rails) { time.times(&rails) }
299
- x.report(:representable) { time.times(&representable) }
300
- end
301
-
302
383
  require 'benchmark/ips'
303
384
  Benchmark.ips do |x|
304
385
  x.report(:alba, &alba)
@@ -306,10 +387,34 @@ Benchmark.ips do |x|
306
387
  x.report(:ams, &ams)
307
388
  x.report(:blueprinter, &blueprinter)
308
389
  x.report(:jbuilder, &jbuilder)
390
+ x.report(:jserializer, &jserializer)
391
+ x.report(:jsonapi, &jsonapi)
392
+ x.report(:jsonapi_same_format, &jsonapi_same_format)
393
+ x.report(:panko, &panko)
394
+ x.report(:primalize, &primalize)
395
+ x.report(:rails, &rails)
396
+ x.report(:representable, &representable)
397
+ x.report(:simple_ams, &simple_ams)
398
+
399
+ x.compare!
400
+ end
401
+
402
+
403
+ require 'benchmark/memory'
404
+ Benchmark.memory do |x|
405
+ x.report(:alba, &alba)
406
+ x.report(:alba_inline, &alba_inline)
407
+ x.report(:ams, &ams)
408
+ x.report(:blueprinter, &blueprinter)
409
+ x.report(:jbuilder, &jbuilder)
410
+ x.report(:jserializer, &jserializer)
309
411
  x.report(:jsonapi, &jsonapi)
310
412
  x.report(:jsonapi_same_format, &jsonapi_same_format)
413
+ x.report(:panko, &panko)
414
+ x.report(:primalize, &primalize)
311
415
  x.report(:rails, &rails)
312
416
  x.report(:representable, &representable)
417
+ x.report(:simple_ams, &simple_ams)
313
418
 
314
419
  x.compare!
315
420
  end
data/codecov.yml CHANGED
@@ -3,3 +3,6 @@ coverage:
3
3
  project:
4
4
  default:
5
5
  informational: true
6
+ patch:
7
+ default:
8
+ target: 90%