alba 0.13.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
- t.test_files = FileList["test/**/*_test.rb"]
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
@@ -10,11 +10,11 @@ Gem::Specification.new do |spec|
10
10
  spec.description = "Alba is designed to be a simple, easy to use and fast alternative to existing JSON serializers. Its performance is better than almost all gems which do similar things. The internal is so simple that it's easy to hack and maintain."
11
11
  spec.homepage = 'https://github.com/okuramasafumi/alba'
12
12
  spec.license = 'MIT'
13
- spec.required_ruby_version = Gem::Requirement.new('>= 2.5.7')
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/master/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,341 @@
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 "sqlite3"
24
+ end
25
+
26
+ # --- Test data model setup ---
27
+
28
+ require "active_record"
29
+ require "logger"
30
+ require "oj"
31
+ require "sqlite3"
32
+ Oj.optimize_rails
33
+
34
+ ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
35
+ # ActiveRecord::Base.logger = Logger.new($stdout)
36
+
37
+ ActiveRecord::Schema.define do
38
+ create_table :posts, force: true do |t|
39
+ t.string :body
40
+ end
41
+
42
+ create_table :comments, force: true do |t|
43
+ t.integer :post_id
44
+ t.string :body
45
+ t.integer :commenter_id
46
+ end
47
+
48
+ create_table :users, force: true do |t|
49
+ t.string :name
50
+ end
51
+ end
52
+
53
+ class Post < ActiveRecord::Base
54
+ has_many :comments
55
+ has_many :commenters, through: :comments, class_name: 'User', source: :commenter
56
+
57
+ def attributes
58
+ {id: nil, body: nil, commenter_names: commenter_names}
59
+ end
60
+
61
+ def commenter_names
62
+ commenters.pluck(:name)
63
+ end
64
+ end
65
+
66
+ class Comment < ActiveRecord::Base
67
+ belongs_to :post
68
+ belongs_to :commenter, class_name: 'User'
69
+
70
+ def attributes
71
+ {id: nil, body: nil}
72
+ end
73
+ end
74
+
75
+ class User < ActiveRecord::Base
76
+ has_many :comments
77
+ end
78
+
79
+ # --- Alba serializers ---
80
+
81
+ require "alba"
82
+
83
+ class AlbaCommentResource
84
+ include ::Alba::Resource
85
+ attributes :id, :body
86
+ end
87
+
88
+ class AlbaPostResource
89
+ include ::Alba::Resource
90
+ attributes :id, :body
91
+ attribute :commenter_names do |post|
92
+ post.commenters.pluck(:name)
93
+ end
94
+ many :comments, resource: AlbaCommentResource
95
+ end
96
+
97
+ # --- ActiveModelSerializer serializers ---
98
+
99
+ require "active_model_serializers"
100
+
101
+ class AMSCommentSerializer < ActiveModel::Serializer
102
+ attributes :id, :body
103
+ end
104
+
105
+ class AMSPostSerializer < ActiveModel::Serializer
106
+ attributes :id, :body
107
+ attribute :commenter_names
108
+ has_many :comments, serializer: AMSCommentSerializer
109
+
110
+ def commenter_names
111
+ object.commenters.pluck(:name)
112
+ end
113
+ end
114
+
115
+ # --- Blueprint serializers ---
116
+
117
+ require "blueprinter"
118
+
119
+ class CommentBlueprint < Blueprinter::Base
120
+ fields :id, :body
121
+ end
122
+
123
+ class PostBlueprint < Blueprinter::Base
124
+ fields :id, :body, :commenter_names
125
+ association :comments, blueprint: CommentBlueprint
126
+
127
+ def commenter_names
128
+ commenters.pluck(:name)
129
+ end
130
+ end
131
+
132
+ # --- JBuilder serializers ---
133
+
134
+ require "jbuilder"
135
+
136
+ class Post
137
+ def to_builder
138
+ Jbuilder.new do |post|
139
+ post.call(self, :id, :body, :commenter_names, :comments)
140
+ end
141
+ end
142
+
143
+ def commenter_names
144
+ commenters.pluck(:name)
145
+ end
146
+ end
147
+
148
+ class Comment
149
+ def to_builder
150
+ Jbuilder.new do |comment|
151
+ comment.call(self, :id, :body)
152
+ end
153
+ end
154
+ end
155
+
156
+ # --- JSONAPI:Serializer serializers / (successor of fast_jsonapi) ---
157
+
158
+ class JsonApiStandardCommentSerializer
159
+ include JSONAPI::Serializer
160
+
161
+ attribute :id
162
+ attribute :body
163
+ end
164
+
165
+ class JsonApiStandardPostSerializer
166
+ include JSONAPI::Serializer
167
+
168
+ # set_type :post # optional
169
+ attribute :id
170
+ attribute :body
171
+ attribute :commenter_names
172
+
173
+ attribute :comments do |post|
174
+ post.comments.map { |comment| JsonApiSameFormatCommentSerializer.new(comment) }
175
+ end
176
+ end
177
+
178
+ # --- JSONAPI:Serializer serializers that format the code the same flat way as the other gems here ---
179
+
180
+ # code to convert from JSON:API output to "flat" JSON, like the other serializers build
181
+ class JsonApiSameFormatSerializer
182
+ include JSONAPI::Serializer
183
+
184
+ def as_json(*_options)
185
+ hash = serializable_hash
186
+
187
+ if hash[:data].is_a? Hash
188
+ hash[:data][:attributes]
189
+
190
+ elsif hash[:data].is_a? Array
191
+ hash[:data].pluck(:attributes)
192
+
193
+ elsif hash[:data].nil?
194
+ { }
195
+
196
+ else
197
+ raise "unexpected data type #{hash[:data].class}"
198
+ end
199
+ end
200
+ end
201
+
202
+ class JsonApiSameFormatCommentSerializer < JsonApiSameFormatSerializer
203
+ attribute :id
204
+ attribute :body
205
+ end
206
+
207
+ class JsonApiSameFormatPostSerializer < JsonApiSameFormatSerializer
208
+ attribute :id
209
+ attribute :body
210
+ attribute :commenter_names
211
+
212
+ attribute :comments do |post|
213
+ post.comments.map { |comment| JsonApiSameFormatCommentSerializer.new(comment) }
214
+ end
215
+ end
216
+
217
+ # --- Primalize serializers ---
218
+ #
219
+ class PrimalizeCommentResource < Primalize::Single
220
+ attributes id: integer, body: string
221
+ end
222
+
223
+ class PrimalizePostResource < Primalize::Single
224
+ alias post object
225
+
226
+ attributes(
227
+ id: integer,
228
+ body: string,
229
+ comments: array(primalize(PrimalizeCommentResource)),
230
+ commenter_names: array(string),
231
+ )
232
+
233
+ def commenter_names
234
+ post.commenters.pluck(:name)
235
+ end
236
+ end
237
+
238
+ # --- Representable serializers ---
239
+
240
+ require "representable"
241
+
242
+ class CommentRepresenter < Representable::Decorator
243
+ include Representable::JSON
244
+
245
+ property :id
246
+ property :body
247
+ end
248
+
249
+ class PostRepresenter < Representable::Decorator
250
+ include Representable::JSON
251
+
252
+ property :id
253
+ property :body
254
+ property :commenter_names
255
+ collection :comments
256
+
257
+ def commenter_names
258
+ commenters.pluck(:name)
259
+ end
260
+ end
261
+
262
+ # --- Test data creation ---
263
+
264
+ post = Post.create!(body: 'post')
265
+ user1 = User.create!(name: 'John')
266
+ user2 = User.create!(name: 'Jane')
267
+ post.comments.create!(commenter: user1, body: 'Comment1')
268
+ post.comments.create!(commenter: user2, body: 'Comment2')
269
+ post.reload
270
+
271
+ # --- Store the serializers in procs ---
272
+
273
+ alba = Proc.new { AlbaPostResource.new(post).serialize }
274
+ alba_inline = Proc.new do
275
+ Alba.serialize(post) do
276
+ attributes :id, :body
277
+ attribute :commenter_names do |post|
278
+ post.commenters.pluck(:name)
279
+ end
280
+ many :comments do
281
+ attributes :id, :body
282
+ end
283
+ end
284
+ end
285
+ ams = Proc.new { AMSPostSerializer.new(post, {}).to_json }
286
+ blueprinter = Proc.new { PostBlueprint.render(post) }
287
+ jbuilder = Proc.new { post.to_builder.target! }
288
+ jsonapi = proc { JsonApiStandardPostSerializer.new(post).to_json }
289
+ jsonapi_same_format = proc { JsonApiSameFormatPostSerializer.new(post).to_json }
290
+ primalize = proc { PrimalizePostResource.new(post).to_json }
291
+ rails = Proc.new { ActiveSupport::JSON.encode(post.serializable_hash(include: :comments)) }
292
+ representable = Proc.new { PostRepresenter.new(post).to_json }
293
+
294
+ # --- Execute the serializers to check their output ---
295
+
296
+ puts "Serializer outputs ----------------------------------"
297
+ {
298
+ alba: alba,
299
+ alba_inline: alba_inline,
300
+ ams: ams,
301
+ blueprinter: blueprinter,
302
+ jbuilder: jbuilder, # different order
303
+ jsonapi: jsonapi, # nested JSON:API format
304
+ jsonapi_same_format: jsonapi_same_format,
305
+ primalize: primalize,
306
+ rails: rails,
307
+ representable: representable
308
+ }.each { |name, serializer| puts "#{name.to_s.ljust(24, ' ')} #{serializer.call}" }
309
+
310
+ # --- Run the benchmarks ---
311
+
312
+ require 'benchmark'
313
+ time = 1000
314
+ Benchmark.bmbm do |x|
315
+ x.report(:alba) { time.times(&alba) }
316
+ x.report(:alba_inline) { time.times(&alba_inline) }
317
+ x.report(:ams) { time.times(&ams) }
318
+ x.report(:blueprinter) { time.times(&blueprinter) }
319
+ x.report(:jbuilder) { time.times(&jbuilder) }
320
+ x.report(:jsonapi) { time.times(&jsonapi) }
321
+ x.report(:jsonapi_same_format) { time.times(&jsonapi_same_format) }
322
+ x.report(:primalize) { time.times(&primalize) }
323
+ x.report(:rails) { time.times(&rails) }
324
+ x.report(:representable) { time.times(&representable) }
325
+ end
326
+
327
+ require 'benchmark/ips'
328
+ Benchmark.ips do |x|
329
+ x.report(:alba, &alba)
330
+ x.report(:alba_inline, &alba_inline)
331
+ x.report(:ams, &ams)
332
+ x.report(:blueprinter, &blueprinter)
333
+ x.report(:jbuilder, &jbuilder)
334
+ x.report(:jsonapi, &jsonapi)
335
+ x.report(:jsonapi_same_format, &jsonapi_same_format)
336
+ x.report(:primalize, &primalize)
337
+ x.report(:rails, &rails)
338
+ x.report(:representable, &representable)
339
+
340
+ x.compare!
341
+ end
data/codecov.yml ADDED
@@ -0,0 +1,8 @@
1
+ coverage:
2
+ status:
3
+ project:
4
+ default:
5
+ informational: true
6
+ patch:
7
+ default:
8
+ target: 90%
@@ -0,0 +1,19 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'activesupport', require: false # For backend
4
+ gem 'ffaker', require: false # For testing
5
+ gem 'minitest', '~> 5.14' # For test
6
+ gem 'rake', '~> 13.0' # For test and automation
7
+ gem 'rubocop', '>= 0.79.0', require: false # For lint
8
+ gem 'rubocop-minitest', '~> 0.11.0', require: false # For lint
9
+ gem 'rubocop-performance', '~> 1.10.1', require: false # For lint
10
+ gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
11
+ gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
12
+ gem 'simplecov', '~> 0.21.0', require: false # For test coverage
13
+ gem 'simplecov-cobertura', require: false # For test coverage
14
+ gem 'yard', require: false
15
+
16
+ platforms :ruby do
17
+ gem 'oj', '~> 3.11', require: false # For backend
18
+ gem 'ruby-prof', require: false # For performance profiling
19
+ end
@@ -0,0 +1,17 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'minitest', '~> 5.14' # For test
4
+ gem 'rake', '~> 13.0' # For test and automation
5
+ gem 'rubocop', '>= 0.79.0', require: false # For lint
6
+ gem 'rubocop-minitest', '~> 0.11.0', require: false # For lint
7
+ gem 'rubocop-performance', '~> 1.10.1', require: false # For lint
8
+ gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
9
+ gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
10
+ gem 'simplecov', '~> 0.21.0', require: false # For test coverage
11
+ gem 'simplecov-cobertura', require: false # For test coverage
12
+ gem 'yard', require: false
13
+
14
+ platforms :ruby do
15
+ gem 'oj', '~> 3.11', require: false # For backend
16
+ gem 'ruby-prof', require: false # For performance profiling
17
+ end
@@ -0,0 +1,17 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'activesupport', require: false # For backend
4
+ gem 'minitest', '~> 5.14' # For test
5
+ gem 'rake', '~> 13.0' # For test and automation
6
+ gem 'rubocop', '>= 0.79.0', require: false # For lint
7
+ gem 'rubocop-minitest', '~> 0.11.0', require: false # For lint
8
+ gem 'rubocop-performance', '~> 1.10.1', require: false # For lint
9
+ gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
10
+ gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
11
+ gem 'simplecov', '~> 0.21.0', require: false # For test coverage
12
+ gem 'simplecov-cobertura', require: false # For test coverage
13
+ gem 'yard', require: false
14
+
15
+ platforms :ruby do
16
+ gem 'ruby-prof', require: false # For performance profiling
17
+ end