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.
@@ -0,0 +1,61 @@
1
+ module Alba
2
+ # Representing typed attributes to encapsulate logic about types
3
+ class TypedAttribute
4
+ # @param name [Symbol, String]
5
+ # @param type [Symbol, Class]
6
+ # @param converter [Proc]
7
+ def initialize(name:, type:, converter:)
8
+ @name = name
9
+ @type = type
10
+ @converter = case converter
11
+ when true then default_converter
12
+ when false, nil then null_converter
13
+ else converter
14
+ end
15
+ end
16
+
17
+ # @param object [Object] target to check and convert type with
18
+ # @return [String, Integer, Boolean] type-checked or type-converted object
19
+ def value(object)
20
+ value, result = check(object)
21
+ result ? value : @converter.call(value)
22
+ rescue TypeError
23
+ raise TypeError, "Attribute #{@name} is expected to be #{@type} but actually #{display_value_for(value)}."
24
+ end
25
+
26
+ private
27
+
28
+ def check(object)
29
+ value = object.public_send(@name)
30
+ type_correct = case @type
31
+ when :String, ->(klass) { klass == String } then value.is_a?(String)
32
+ when :Integer, ->(klass) { klass == Integer } then value.is_a?(Integer)
33
+ when :Boolean then [true, false].include?(value)
34
+ else
35
+ raise Alba::UnsupportedType, "Unknown type: #{@type}"
36
+ end
37
+ [value, type_correct]
38
+ end
39
+
40
+ def default_converter
41
+ case @type
42
+ when :String, ->(klass) { klass == String }
43
+ ->(object) { object.to_s }
44
+ when :Integer, ->(klass) { klass == Integer }
45
+ ->(object) { Integer(object) }
46
+ when :Boolean
47
+ ->(object) { !!object }
48
+ else
49
+ raise Alba::UnsupportedType, "Unknown type: #{@type}"
50
+ end
51
+ end
52
+
53
+ def null_converter
54
+ ->(_) { raise TypeError }
55
+ end
56
+
57
+ def display_value_for(value)
58
+ value.nil? ? 'nil' : value.class.name
59
+ end
60
+ end
61
+ end
data/lib/alba/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Alba
2
- VERSION = '1.0.0'.freeze
2
+ VERSION = '1.4.0'.freeze
3
3
  end
@@ -0,0 +1,174 @@
1
+ # Benchmark script to run varieties of JSON serializers
2
+ # Fetch Alba from local, otherwise fetch latest from RubyGems
3
+ # exit(status)
4
+
5
+ # --- Bundle dependencies ---
6
+
7
+ require "bundler/inline"
8
+
9
+ gemfile(true) do
10
+ source "https://rubygems.org"
11
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
12
+
13
+ gem "activerecord", "~> 6.1.3"
14
+ gem "alba", path: '../'
15
+ gem "benchmark-ips"
16
+ gem "blueprinter"
17
+ gem "jbuilder"
18
+ gem "multi_json"
19
+ gem "oj"
20
+ gem "sqlite3"
21
+ end
22
+
23
+ # --- Test data model setup ---
24
+
25
+ require "active_record"
26
+ require "oj"
27
+ require "sqlite3"
28
+ Oj.optimize_rails
29
+
30
+ ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
31
+
32
+ ActiveRecord::Schema.define do
33
+ create_table :posts, force: true do |t|
34
+ t.string :body
35
+ end
36
+
37
+ create_table :comments, force: true do |t|
38
+ t.integer :post_id
39
+ t.string :body
40
+ t.integer :commenter_id
41
+ end
42
+
43
+ create_table :users, force: true do |t|
44
+ t.string :name
45
+ end
46
+ end
47
+
48
+ class Post < ActiveRecord::Base
49
+ has_many :comments
50
+ has_many :commenters, through: :comments, class_name: 'User', source: :commenter
51
+
52
+ def attributes
53
+ {id: nil, body: nil, commenter_names: commenter_names}
54
+ end
55
+
56
+ def commenter_names
57
+ commenters.pluck(:name)
58
+ end
59
+ end
60
+
61
+ class Comment < ActiveRecord::Base
62
+ belongs_to :post
63
+ belongs_to :commenter, class_name: 'User'
64
+
65
+ def attributes
66
+ {id: nil, body: nil}
67
+ end
68
+ end
69
+
70
+ class User < ActiveRecord::Base
71
+ has_many :comments
72
+ end
73
+
74
+ # --- Alba serializers ---
75
+
76
+ require "alba"
77
+
78
+ class AlbaCommentResource
79
+ include ::Alba::Resource
80
+ attributes :id, :body
81
+ end
82
+
83
+ class AlbaPostResource
84
+ include ::Alba::Resource
85
+ attributes :id, :body
86
+ attribute :commenter_names do |post|
87
+ post.commenters.pluck(:name)
88
+ end
89
+ many :comments, resource: AlbaCommentResource
90
+ end
91
+
92
+ # --- Blueprint serializers ---
93
+
94
+ require "blueprinter"
95
+
96
+ class CommentBlueprint < Blueprinter::Base
97
+ fields :id, :body
98
+ end
99
+
100
+ class PostBlueprint < Blueprinter::Base
101
+ fields :id, :body, :commenter_names
102
+ association :comments, blueprint: CommentBlueprint
103
+
104
+ def commenter_names
105
+ commenters.pluck(:name)
106
+ end
107
+ end
108
+
109
+ # --- JBuilder serializers ---
110
+
111
+ require "jbuilder"
112
+
113
+ class Post
114
+ def to_builder
115
+ Jbuilder.new do |post|
116
+ post.call(self, :id, :body, :commenter_names, :comments)
117
+ end
118
+ end
119
+
120
+ def commenter_names
121
+ commenters.pluck(:name)
122
+ end
123
+ end
124
+
125
+ class Comment
126
+ def to_builder
127
+ Jbuilder.new do |comment|
128
+ comment.call(self, :id, :body)
129
+ end
130
+ end
131
+ end
132
+
133
+ # --- Test data creation ---
134
+
135
+ 100.times do |i|
136
+ post = Post.create!(body: "post#{i}")
137
+ user1 = User.create!(name: "John#{i}")
138
+ user2 = User.create!(name: "Jane#{i}")
139
+ 10.times do |n|
140
+ post.comments.create!(commenter: user1, body: "Comment1_#{i}_#{n}")
141
+ post.comments.create!(commenter: user2, body: "Comment2_#{i}_#{n}")
142
+ end
143
+ end
144
+
145
+ posts = Post.all.to_a
146
+
147
+ # --- Store the serializers in procs ---
148
+
149
+ alba = Proc.new { AlbaPostResource.new(posts).serialize }
150
+ blueprinter = Proc.new { PostBlueprint.render(posts) }
151
+ jbuilder = Proc.new do
152
+ Jbuilder.new do |json|
153
+ json.array!(posts) do |post|
154
+ json.post post.to_builder
155
+ end
156
+ end.target!
157
+ end
158
+
159
+ # --- Run the benchmarks ---
160
+
161
+ require 'benchmark/ips'
162
+ result = Benchmark.ips do |x|
163
+ x.report(:alba, &alba)
164
+ x.report(:blueprinter, &blueprinter)
165
+ x.report(:jbuilder, &jbuilder)
166
+ end
167
+
168
+ entries = result.entries.map {|entry| [entry.label, entry.iterations]}
169
+ alba_ips = entries.find {|e| e.first == :alba }.last
170
+ blueprinter_ips = entries.find {|e| e.first == :blueprinter }.last
171
+ jbuidler_ips = entries.find {|e| e.first == :jbuilder }.last
172
+ # Alba should be as fast as jbuilder and faster than blueprinter
173
+ alba_is_fast_enough = (alba_ips - jbuidler_ips) > -10.0 && (alba_ips - blueprinter_ips) > 10.0
174
+ exit(alba_is_fast_enough)
data/sider.yml CHANGED
@@ -49,10 +49,8 @@ linter:
49
49
  # norc: true
50
50
 
51
51
  # # https://help.sider.review/getting-started/custom-configuration#ignore
52
- # ignore:
53
- # - "*.pdf"
54
- # - "*.mp4"
55
- # - "images/**"
52
+ ignore:
53
+ - 'test/**/*'
56
54
 
57
55
  # # https://help.sider.review/getting-started/custom-configuration#branchesexclude
58
56
  # branches:
metadata CHANGED
@@ -1,25 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alba
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OKURA Masafumi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-04-07 00:00:00.000000000 Z
11
+ date: 2021-06-30 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: Alba is designed to be a simple, easy to use and fast alternative to
14
- existing JSON serializers. Its performance is better than almost all gems which
15
- do similar things. The internal is so simple that it's easy to hack and maintain.
13
+ description: Alba is the fastest JSON serializer for Ruby. It focuses on performance,
14
+ flexibility and usability.
16
15
  email:
17
16
  - masafumi.o1988@gmail.com
18
17
  executables: []
19
18
  extensions: []
20
19
  extra_rdoc_files: []
21
20
  files:
21
+ - ".github/ISSUE_TEMPLATE/bug_report.md"
22
+ - ".github/ISSUE_TEMPLATE/feature_request.md"
23
+ - ".github/dependabot.yml"
22
24
  - ".github/workflows/main.yml"
25
+ - ".github/workflows/perf.yml"
23
26
  - ".gitignore"
24
27
  - ".rubocop.yml"
25
28
  - ".yardopts"
@@ -29,17 +32,26 @@ files:
29
32
  - LICENSE.txt
30
33
  - README.md
31
34
  - Rakefile
35
+ - SECURITY.md
32
36
  - alba.gemspec
33
- - benchmark/local.rb
37
+ - benchmark/collection.rb
38
+ - benchmark/single_resource.rb
34
39
  - bin/console
35
40
  - bin/setup
41
+ - codecov.yml
42
+ - gemfiles/all.gemfile
43
+ - gemfiles/without_active_support.gemfile
44
+ - gemfiles/without_oj.gemfile
36
45
  - lib/alba.rb
37
46
  - lib/alba/association.rb
38
- - lib/alba/key_transformer.rb
47
+ - lib/alba/default_inflector.rb
48
+ - lib/alba/key_transform_factory.rb
39
49
  - lib/alba/many.rb
40
50
  - lib/alba/one.rb
41
51
  - lib/alba/resource.rb
52
+ - lib/alba/typed_attribute.rb
42
53
  - lib/alba/version.rb
54
+ - script/perf_check.rb
43
55
  - sider.yml
44
56
  homepage: https://github.com/okuramasafumi/alba
45
57
  licenses:
@@ -47,7 +59,7 @@ licenses:
47
59
  metadata:
48
60
  homepage_uri: https://github.com/okuramasafumi/alba
49
61
  source_code_uri: https://github.com/okuramasafumi/alba
50
- changelog_uri: https://github.com/okuramasafumi/alba/CHANGELOG.md
62
+ changelog_uri: https://github.com/okuramasafumi/alba/blob/main/CHANGELOG.md
51
63
  post_install_message:
52
64
  rdoc_options: []
53
65
  require_paths:
@@ -56,14 +68,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
56
68
  requirements:
57
69
  - - ">="
58
70
  - !ruby/object:Gem::Version
59
- version: 2.5.7
71
+ version: 2.5.0
60
72
  required_rubygems_version: !ruby/object:Gem::Requirement
61
73
  requirements:
62
74
  - - ">="
63
75
  - !ruby/object:Gem::Version
64
76
  version: '0'
65
77
  requirements: []
66
- rubygems_version: 3.2.14
78
+ rubygems_version: 3.2.16
67
79
  signing_key:
68
80
  specification_version: 4
69
81
  summary: Alba is the fastest JSON serializer for Ruby.
data/benchmark/local.rb DELETED
@@ -1,198 +0,0 @@
1
- # Benchmark script to run varieties of JSON serializers
2
- # Fetch Alba from local, otherwise fetch latest from RubyGems
3
-
4
- require "bundler/inline"
5
-
6
- gemfile(true) do
7
- source "https://rubygems.org"
8
-
9
- git_source(:github) { |repo| "https://github.com/#{repo}.git" }
10
-
11
- gem "activerecord", "6.1.3"
12
- gem "sqlite3"
13
- gem "jbuilder"
14
- gem "active_model_serializers"
15
- gem "blueprinter"
16
- gem "representable"
17
- gem "alba", path: '../'
18
- gem "oj"
19
- gem "multi_json"
20
- end
21
-
22
- require "active_record"
23
- require "sqlite3"
24
- require "logger"
25
- require "oj"
26
- Oj.optimize_rails
27
-
28
- ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
29
- # ActiveRecord::Base.logger = Logger.new(STDOUT)
30
-
31
- ActiveRecord::Schema.define do
32
- create_table :posts, force: true do |t|
33
- t.string :body
34
- end
35
-
36
- create_table :comments, force: true do |t|
37
- t.integer :post_id
38
- t.string :body
39
- t.integer :commenter_id
40
- end
41
-
42
- create_table :users, force: true do |t|
43
- t.string :name
44
- end
45
- end
46
-
47
- class Post < ActiveRecord::Base
48
- has_many :comments
49
- has_many :commenters, through: :comments, class_name: 'User', source: :commenter
50
-
51
- def attributes
52
- {id: nil, body: nil, commenter_names: commenter_names}
53
- end
54
-
55
- def commenter_names
56
- commenters.pluck(:name)
57
- end
58
- end
59
-
60
- class Comment < ActiveRecord::Base
61
- belongs_to :post
62
- belongs_to :commenter, class_name: 'User'
63
-
64
- def attributes
65
- {id: nil, body: nil}
66
- end
67
- end
68
-
69
- class User < ActiveRecord::Base
70
- has_many :comments
71
- end
72
-
73
- require "alba"
74
- Alba.backend = :oj
75
-
76
- class AlbaCommentResource
77
- include ::Alba::Resource
78
- attributes :id, :body
79
- end
80
-
81
- class AlbaPostResource
82
- include ::Alba::Resource
83
- attributes :id, :body
84
- many :comments, resource: AlbaCommentResource
85
- attribute :commenter_names do |post|
86
- post.commenters.pluck(:name)
87
- end
88
- end
89
-
90
- require "jbuilder"
91
- class Post
92
- def to_builder
93
- Jbuilder.new do |post|
94
- post.call(self, :id, :body, :comments, :commenter_names)
95
- end
96
- end
97
-
98
- def commenter_names
99
- commenters.pluck(:name)
100
- end
101
- end
102
-
103
- class Comment
104
- def to_builder
105
- Jbuilder.new do |comment|
106
- comment.call(self, :id, :body)
107
- end
108
- end
109
- end
110
-
111
- require "active_model_serializers"
112
-
113
- class AMSCommentSerializer < ActiveModel::Serializer
114
- attributes :id, :body
115
- end
116
-
117
- class AMSPostSerializer < ActiveModel::Serializer
118
- attributes :id, :body
119
- has_many :comments, serializer: AMSCommentSerializer
120
- attribute :commenter_names
121
- def commenter_names
122
- object.commenters.pluck(:name)
123
- end
124
- end
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
- def commenter_names
136
- commenters.pluck(:name)
137
- end
138
- end
139
-
140
- require "representable"
141
-
142
- class CommentRepresenter < Representable::Decorator
143
- include Representable::JSON
144
-
145
- property :id
146
- property :body
147
- end
148
-
149
- class PostRepresenter < Representable::Decorator
150
- include Representable::JSON
151
-
152
- property :id
153
- property :body
154
- property :commenter_names
155
- collection :comments
156
-
157
- def commenter_names
158
- commenters.pluck(:name)
159
- end
160
- end
161
-
162
- post = Post.create!(body: 'post')
163
- user1 = User.create!(name: 'John')
164
- user2 = User.create!(name: 'Jane')
165
- post.comments.create!(commenter: user1, body: 'Comment1')
166
- post.comments.create!(commenter: user2, body: 'Comment2')
167
- post.reload
168
-
169
- alba = Proc.new { AlbaPostResource.new(post).serialize }
170
- jbuilder = Proc.new { post.to_builder.target! }
171
- ams = Proc.new { AMSPostSerializer.new(post, {}).to_json }
172
- rails = Proc.new { ActiveSupport::JSON.encode(post.serializable_hash(include: :comments)) }
173
- blueprinter = Proc.new { PostBlueprint.render(post) }
174
- representable = Proc.new { PostRepresenter.new(post).to_json }
175
- alba_inline = Proc.new do
176
- Alba.serialize(post) do
177
- attributes :id, :body
178
- attribute :commenter_names do |post|
179
- post.commenters.pluck(:name)
180
- end
181
- many :comments do
182
- attributes :id, :body
183
- end
184
- end
185
- end
186
- [alba, jbuilder, ams, rails, blueprinter, representable, alba_inline].each {|x| puts x.call }
187
-
188
- require 'benchmark'
189
- time = 1000
190
- Benchmark.bmbm do |x|
191
- x.report(:alba) { time.times(&alba) }
192
- x.report(:jbuilder) { time.times(&jbuilder) }
193
- x.report(:ams) { time.times(&ams) }
194
- x.report(:rails) { time.times(&rails) }
195
- x.report(:blueprinter) { time.times(&blueprinter) }
196
- x.report(:representable) { time.times(&representable) }
197
- x.report(:alba_inline) { time.times(&alba_inline) }
198
- end