alba 1.1.0 → 1.5.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.
@@ -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%