alba 1.0.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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