alba 0.13.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 +34 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +33 -2
- data/.yardopts +2 -0
- data/CHANGELOG.md +35 -0
- data/Gemfile +10 -4
- data/README.md +311 -43
- data/Rakefile +4 -1
- data/SECURITY.md +12 -0
- data/alba.gemspec +2 -2
- data/benchmark/local.rb +341 -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 +56 -19
- data/lib/alba/association.rb +22 -7
- data/lib/alba/key_transformer.rb +2 -1
- data/lib/alba/many.rb +8 -4
- data/lib/alba/one.rb +8 -4
- data/lib/alba/resource.rb +180 -59
- data/lib/alba/version.rb +1 -1
- data/sider.yml +2 -4
- metadata +16 -8
- data/.travis.yml +0 -10
- data/Gemfile.lock +0 -92
- data/lib/alba/serializer.rb +0 -77
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
@@ -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.
|
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.
|
data/benchmark/local.rb
ADDED
@@ -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,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
|