alba 3.4.0 → 3.6.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/README.md +7 -41
  4. data/lib/alba/association.rb +4 -1
  5. data/lib/alba/conditional_attribute.rb +11 -14
  6. data/lib/alba/layout.rb +8 -6
  7. data/lib/alba/railtie.rb +6 -5
  8. data/lib/alba/resource.rb +59 -15
  9. data/lib/alba/typed_attribute.rb +2 -0
  10. data/lib/alba/version.rb +1 -1
  11. data/lib/alba.rb +72 -38
  12. metadata +4 -52
  13. data/.codeclimate.yml +0 -12
  14. data/.editorconfig +0 -10
  15. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -26
  16. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  17. data/.github/dependabot.yml +0 -12
  18. data/.github/workflows/codeql-analysis.yml +0 -70
  19. data/.github/workflows/lint.yml +0 -17
  20. data/.github/workflows/main.yml +0 -39
  21. data/.gitignore +0 -11
  22. data/.rubocop.yml +0 -156
  23. data/.yardopts +0 -4
  24. data/CODE_OF_CONDUCT.md +0 -132
  25. data/CONTRIBUTING.md +0 -30
  26. data/Gemfile +0 -29
  27. data/HACKING.md +0 -42
  28. data/Rakefile +0 -17
  29. data/SECURITY.md +0 -12
  30. data/alba.gemspec +0 -33
  31. data/benchmark/Gemfile +0 -24
  32. data/benchmark/README.md +0 -119
  33. data/benchmark/collection.rb +0 -275
  34. data/benchmark/prep.rb +0 -56
  35. data/benchmark/single_resource.rb +0 -300
  36. data/bin/console +0 -15
  37. data/bin/setup +0 -8
  38. data/codecov.yml +0 -8
  39. data/docs/migrate_from_active_model_serializers.md +0 -359
  40. data/docs/migrate_from_jbuilder.md +0 -237
  41. data/docs/rails.md +0 -56
  42. data/gemfiles/without_active_support.gemfile +0 -19
  43. data/gemfiles/without_oj.gemfile +0 -19
  44. data/logo/alba-card.png +0 -0
  45. data/logo/alba-sign.png +0 -0
  46. data/logo/alba-typography.png +0 -0
data/HACKING.md DELETED
@@ -1,42 +0,0 @@
1
- # Hacking document
2
-
3
- This document is intended to provide detailed information about the internal design and implementation of Alba. You are recommended to read through it if you want to hack Alba.
4
-
5
- ## Design
6
-
7
- The design of Alba is simple. `Alba::Resource` module is the only interface end users use by `include`ing it. Other classes and modules are referenced by `Alba::Resource`.
8
-
9
- When a class `include`s `Alba::Resource` module it defines `ClassMethods` such as `attributes` and `InstanceMethods` such as `serialize`. Instance methods work with information collected by class methods.
10
-
11
- ## Methods
12
-
13
- The main methods users directly use are listed below.
14
-
15
- Class methods (DSL):
16
-
17
- * `attribute` for block style attribute
18
- * `attributes` for symbol style attribute
19
- * `association` and its aliases such as `one` for association
20
- * `nested` for nested attribute
21
-
22
- Instance methods:
23
-
24
- * `serializable_hash` and `to_h` for hash from target object
25
- * `serialize` and `to_json` for serialized JSON string
26
-
27
- Other methods are rather trivial. They'll be added to this list when it turned out it's important enough.
28
-
29
- ## Implementation
30
-
31
- In `Alba::Resource` module there are some things to note.
32
-
33
- `@object` is an object for serialization. It's either a singular object or a collection.
34
-
35
- Attribute object can be either `Symbol`, `Proc`, `Alba::Association` or `Alba::TypedAttribute`.
36
-
37
- * `Symbol` attributes come from `attributes` method and are sent to `__send__` as method name
38
- * `Proc` attributes come from `attribute` method and are `instance_exec`uted
39
- * `Alba::Association` attributes come from `association` method and `to_h` method on the object is called
40
- * `Alba::TypedAttribute` attributes come when users specify `type` option and `value` method on the object is called
41
-
42
- When users provide `if` option, the attribute object becomes an `Array`. It contains two element, attribute itself and condition.
data/Rakefile DELETED
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler/gem_tasks'
4
- require 'rake/testtask'
5
-
6
- if ENV['BUNDLE_GEMFILE'] == File.expand_path('Gemfile') || ENV['BUNDLE_GEMFILE'].empty? || ENV['BUNDLE_GEMFILE'].nil?
7
- ENV['BUNDLE_GEMFILE'] = File.expand_path('Gemfile')
8
- end
9
-
10
- Rake::TestTask.new(:test) do |t|
11
- t.libs << 'test'
12
- t.libs << 'lib'
13
- file_list = ENV['BUNDLE_GEMFILE'] == File.expand_path('Gemfile') ? FileList['test/**/*_test.rb'] : FileList['test/dependencies/test_dependencies.rb']
14
- t.test_files = file_list
15
- end
16
-
17
- task default: :test
data/SECURITY.md DELETED
@@ -1,12 +0,0 @@
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 DELETED
@@ -1,33 +0,0 @@
1
- require_relative 'lib/alba/version'
2
-
3
- Gem::Specification.new do |spec|
4
- spec.name = 'alba'
5
- spec.version = Alba::VERSION
6
- spec.authors = ['OKURA Masafumi']
7
- spec.email = ['masafumi.o1988@gmail.com']
8
-
9
- spec.summary = 'Alba is the fastest JSON serializer for Ruby.'
10
- spec.description = "Alba is the fastest JSON serializer for Ruby. It focuses on performance, flexibility and usability."
11
- spec.homepage = 'https://github.com/okuramasafumi/alba'
12
- spec.license = 'MIT'
13
- spec.required_ruby_version = Gem::Requirement.new('>= 3.0.0')
14
-
15
- spec.metadata = {
16
- 'bug_tracker_uri' => 'https://github.com/okuramasafumi/alba/issues',
17
- 'changelog_uri' => 'https://github.com/okuramasafumi/alba/blob/main/CHANGELOG.md',
18
- 'documentation_uri' => 'https://rubydoc.info/github/okuramasafumi/alba',
19
- 'source_code_uri' => 'https://github.com/okuramasafumi/alba',
20
- 'rubygems_mfa_required' => 'true'
21
- }
22
-
23
- # Specify which files should be added to the gem when it is released.
24
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
- spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
27
- end
28
- spec.bindir = 'exe'
29
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
- spec.require_paths = ['lib']
31
-
32
- spec.add_dependency "ostruct", "~> 0.6"
33
- end
data/benchmark/Gemfile DELETED
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source 'https://rubygems.org'
4
- git_source(:github) { |repo| "https://github.com/#{repo}.git" }
5
-
6
- gem 'active_model_serializers'
7
- gem 'activerecord', '~> 7.1'
8
- gem 'alba', path: '../'
9
- gem 'benchmark-ips'
10
- gem 'benchmark-memory'
11
- gem 'blueprinter'
12
- gem 'fast_serializer_ruby'
13
- gem 'jbuilder'
14
- gem 'jserializer'
15
- gem 'multi_json'
16
- gem 'oj'
17
- gem 'oj_serializers'
18
- gem 'panko_serializer'
19
- gem 'pg'
20
- gem 'primalize'
21
- gem 'representable'
22
- gem 'simple_ams'
23
- gem 'sqlite3', '~> 1.4'
24
- gem 'turbostreamer'
data/benchmark/README.md DELETED
@@ -1,119 +0,0 @@
1
- ## Benchmark for json serializers
2
-
3
- This directory contains a few different benchmark scripts. They all use inline Bundler definitions so you can run them by `ruby benchmark/collection.rb` for instance.
4
-
5
- ## Result
6
-
7
- As a reference, here's the benchmark result run in my (@okuramasafumi) machine.
8
-
9
- Machine spec:
10
-
11
- |Key|Value|
12
- |---|---|
13
- |OS|macOS 14.7|
14
- |CPU|Apple M1 Pro|
15
- |RAM|16GB|
16
- |Ruby|ruby 3.3.5 (2024-09-03 revision ef084cc8f4) [arm64-darwin23]|
17
-
18
- Library versions:
19
-
20
- |Library|Version|
21
- |---|---|
22
- |alba|3.2.0|
23
- |blueprinter|1.1.0|
24
- |fast_serializer_ruby|0.6.9|
25
- |jserializer|0.2.1|
26
- |oj|3.16.6|
27
- |simple_ams|0.2.6|
28
- |representable|3.2.0|
29
- |turbostreamer|1.11.0|
30
- |jbuilder|2.13.0|
31
- |panko_serializer|0.8.2|
32
- |active_model_serializers|0.10.14|
33
-
34
- `benchmark-ips` with `Oj.optimize_rails`:
35
-
36
- ```
37
- Comparison:
38
- panko: 447.0 i/s
39
- jserializer: 168.9 i/s - 2.65x slower
40
- alba_inline: 149.4 i/s - 2.99x slower
41
- alba: 146.5 i/s - 3.05x slower
42
- turbostreamer: 138.7 i/s - 3.22x slower
43
- rails: 105.6 i/s - 4.23x slower
44
- fast_serializer: 97.6 i/s - 4.58x slower
45
- blueprinter: 66.7 i/s - 6.70x slower
46
- representable: 50.6 i/s - 8.83x slower
47
- simple_ams: 35.5 i/s - 12.57x slower
48
- ams: 14.8 i/s - 30.25x slower
49
- ```
50
-
51
- `benchmark-ips` without `Oj.optimize_rails`:
52
-
53
- ```
54
- Comparison:
55
- panko: 457.9 i/s
56
- jserializer: 165.9 i/s - 2.76x slower
57
- alba: 160.1 i/s - 2.86x slower
58
- alba_inline: 158.5 i/s - 2.89x slower
59
- turbostreamer: 141.7 i/s - 3.23x slower
60
- fast_serializer: 96.2 i/s - 4.76x slower
61
- rails: 87.2 i/s - 5.25x slower
62
- blueprinter: 67.4 i/s - 6.80x slower
63
- representable: 43.4 i/s - 10.55x slower
64
- simple_ams: 34.7 i/s - 13.20x slower
65
- ams: 14.2 i/s - 32.28x slower
66
- ```
67
-
68
- `benchmark-ips` with `Oj.optimize_rail` and YJIT:
69
-
70
- ```
71
- Comparison:
72
- panko: 676.6 i/s
73
- jserializer: 285.3 i/s - 2.37x slower
74
- turbostreamer: 264.2 i/s - 2.56x slower
75
- alba: 258.9 i/s - 2.61x slower
76
- fast_serializer: 179.0 i/s - 3.78x slower
77
- rails: 150.7 i/s - 4.49x slower
78
- alba_inline: 131.5 i/s - 5.15x slower
79
- blueprinter: 110.0 i/s - 6.15x slower
80
- representable: 73.5 i/s - 9.21x slower
81
- simple_ams: 62.8 i/s - 10.77x slower
82
- ams: 20.4 i/s - 33.10x slower
83
- ```
84
-
85
- `benchmark-ips` with YJIT and without `Oj.optimize_rail`:
86
-
87
- ```
88
- Comparison:
89
- panko: 701.9 i/s
90
- alba: 311.1 i/s - 2.26x slower
91
- jserializer: 281.6 i/s - 2.49x slower
92
- turbostreamer: 240.4 i/s - 2.92x slower
93
- fast_serializer: 180.5 i/s - 3.89x slower
94
- alba_inline: 135.6 i/s - 5.18x slower
95
- rails: 131.4 i/s - 5.34x slower
96
- blueprinter: 110.7 i/s - 6.34x slower
97
- representable: 70.5 i/s - 9.96x slower
98
- simple_ams: 57.3 i/s - 12.24x slower
99
- ams: 20.3 i/s - 34.51x slower
100
- ```
101
-
102
- `benchmark-memory`:
103
-
104
- ```
105
- Comparison:
106
- panko: 259178 allocated
107
- turbostreamer: 817800 allocated - 3.16x more
108
- jserializer: 826425 allocated - 3.19x more
109
- alba: 846465 allocated - 3.27x more
110
- alba_inline: 867361 allocated - 3.35x more
111
- fast_serializer: 1474345 allocated - 5.69x more
112
- rails: 2265905 allocated - 8.74x more
113
- blueprinter: 2469905 allocated - 9.53x more
114
- representable: 4994281 allocated - 19.27x more
115
- ams: 5233265 allocated - 20.19x more
116
- simple_ams: 9506817 allocated - 36.68x more
117
- ```
118
-
119
- Conclusion: panko is extremely fast but it's a C extension gem. As pure Ruby gems, Alba, `turbostreamer` and `jserializer` are notably faster than others, but Alba is slightly slower than other two. With `Oj.optimize_rails`, `jbuilder` and Rails standard serialization are also fast.
@@ -1,275 +0,0 @@
1
- # Benchmark script to run varieties of JSON serializers
2
- # Fetch Alba from local, otherwise fetch latest from RubyGems
3
-
4
- require_relative 'prep'
5
-
6
- # --- Alba serializers ---
7
-
8
- require "alba"
9
-
10
- class AlbaCommentResource
11
- include ::Alba::Resource
12
- attributes :id, :body
13
- end
14
-
15
- class AlbaPostResource
16
- include ::Alba::Resource
17
- attributes :id, :body
18
- attribute :commenter_names do |post|
19
- post.commenters.pluck(:name)
20
- end
21
- many :comments, resource: AlbaCommentResource
22
- end
23
-
24
- # --- ActiveModelSerializer serializers ---
25
-
26
- require "active_model_serializers"
27
-
28
- ActiveModelSerializers.logger = Logger.new(nil)
29
-
30
- class AMSCommentSerializer < ActiveModel::Serializer
31
- attributes :id, :body
32
- end
33
-
34
- class AMSPostSerializer < ActiveModel::Serializer
35
- attributes :id, :body
36
- attribute :commenter_names
37
- has_many :comments, serializer: AMSCommentSerializer
38
-
39
- def commenter_names
40
- object.commenters.pluck(:name)
41
- end
42
- end
43
-
44
- # --- Blueprint serializers ---
45
-
46
- require "blueprinter"
47
-
48
- class CommentBlueprint < Blueprinter::Base
49
- fields :id, :body
50
- end
51
-
52
- class PostBlueprint < Blueprinter::Base
53
- fields :id, :body, :commenter_names
54
- association :comments, blueprint: CommentBlueprint
55
-
56
- def commenter_names
57
- commenters.pluck(:name)
58
- end
59
- end
60
-
61
- # --- Fast Serializer Ruby
62
-
63
- require "fast_serializer"
64
-
65
- class FastSerializerCommentResource
66
- include ::FastSerializer::Schema::Mixin
67
- attributes :id, :body
68
- end
69
-
70
- class FastSerializerPostResource
71
- include ::FastSerializer::Schema::Mixin
72
-
73
- attributes :id, :body
74
-
75
- attribute :commenter_names do
76
- object.commenters.pluck(:name)
77
- end
78
-
79
- has_many :comments, serializer: FastSerializerCommentResource
80
- end
81
-
82
- # --- Jserializer serializers ---
83
-
84
- require 'jserializer'
85
-
86
- class JserializerCommentSerializer < Jserializer::Base
87
- attributes :id, :body
88
- end
89
-
90
- class JserializerPostSerializer < Jserializer::Base
91
- attributes :id, :body, :commenter_names
92
- has_many :comments, serializer: JserializerCommentSerializer
93
- def commenter_names
94
- object.commenters.pluck(:name)
95
- end
96
- end
97
-
98
- # --- Panko serializers ---
99
- #
100
-
101
- require "panko_serializer"
102
-
103
- class PankoCommentSerializer < Panko::Serializer
104
- attributes :id, :body
105
- end
106
-
107
-
108
- class PankoPostSerializer < Panko::Serializer
109
- attributes :id, :body, :commenter_names
110
-
111
- has_many :comments, serializer: PankoCommentSerializer
112
-
113
- def commenter_names
114
- object.commenters.pluck(:name)
115
- end
116
- end
117
-
118
- # --- Representable serializers ---
119
-
120
- require "representable"
121
-
122
- class CommentRepresenter < Representable::Decorator
123
- include Representable::JSON
124
-
125
- property :id
126
- property :body
127
- end
128
-
129
- class PostsRepresenter < Representable::Decorator
130
- include Representable::JSON::Collection
131
-
132
- items class: Post do
133
- property :id
134
- property :body
135
- property :commenter_names
136
- collection :comments
137
- end
138
-
139
- def commenter_names
140
- commenters.pluck(:name)
141
- end
142
- end
143
-
144
- # --- SimpleAMS serializers ---
145
-
146
- require "simple_ams"
147
-
148
- class SimpleAMSCommentSerializer
149
- include SimpleAMS::DSL
150
-
151
- attributes :id, :body
152
- end
153
-
154
- class SimpleAMSPostSerializer
155
- include SimpleAMS::DSL
156
-
157
- attributes :id, :body
158
- attribute :commenter_names
159
- has_many :comments, serializer: SimpleAMSCommentSerializer
160
-
161
- def commenter_names
162
- object.commenters.pluck(:name)
163
- end
164
- end
165
-
166
- require 'turbostreamer'
167
- TurboStreamer.set_default_encoder(:json, :oj)
168
-
169
- class TurbostreamerSerializer
170
- def initialize(posts)
171
- @posts = posts
172
- end
173
-
174
- def to_json
175
- TurboStreamer.encode do |json|
176
- json.array! @posts do |post|
177
- json.object! do
178
- json.extract! post, :id, :body, :commenter_names
179
-
180
- json.comments post.comments do |comment|
181
- json.object! do
182
- json.extract! comment, :id, :body
183
- end
184
- end
185
- end
186
- end
187
- end
188
- end
189
- end
190
-
191
- # --- Test data creation ---
192
-
193
- 100.times do |i|
194
- post = Post.create!(body: "post#{i}")
195
- user1 = User.create!(name: "John#{i}")
196
- user2 = User.create!(name: "Jane#{i}")
197
- 10.times do |n|
198
- post.comments.create!(commenter: user1, body: "Comment1_#{i}_#{n}")
199
- post.comments.create!(commenter: user2, body: "Comment2_#{i}_#{n}")
200
- end
201
- end
202
-
203
- posts = Post.all.includes(:comments, :commenters)
204
-
205
- # --- Store the serializers in procs ---
206
-
207
- alba = Proc.new { AlbaPostResource.new(posts).serialize }
208
- alba_inline = Proc.new do
209
- Alba.serialize(posts) do
210
- attributes :id, :body
211
- attribute :commenter_names do |post|
212
- post.commenters.pluck(:name)
213
- end
214
- many :comments do
215
- attributes :id, :body
216
- end
217
- end
218
- end
219
- ams = Proc.new { ActiveModelSerializers::SerializableResource.new(posts, {each_serializer: AMSPostSerializer}).to_json }
220
- blueprinter = Proc.new { PostBlueprint.render(posts) }
221
- fast_serializer = Proc.new { FastSerializerPostResource.new(posts).to_json }
222
- jserializer = Proc.new { JserializerPostSerializer.new(posts, is_collection: true).to_json }
223
- panko = proc { Panko::ArraySerializer.new(posts, each_serializer: PankoPostSerializer).to_json }
224
- rails = Proc.new do
225
- ActiveSupport::JSON.encode(posts.map{ |post| post.serializable_hash(include: :comments) })
226
- end
227
- representable = Proc.new { PostsRepresenter.new(posts).to_json }
228
- simple_ams = Proc.new { SimpleAMS::Renderer::Collection.new(posts, serializer: SimpleAMSPostSerializer).to_json }
229
- turbostreamer = Proc.new { TurbostreamerSerializer.new(posts).to_json }
230
-
231
- # --- Execute the serializers to check their output ---
232
- GC.disable
233
- puts "Checking outputs..."
234
- correct = alba.call
235
- parsed_correct = JSON.parse(correct)
236
- {
237
- alba_inline: alba_inline,
238
- ams: ams,
239
- blueprinter: blueprinter,
240
- fast_serializer: fast_serializer,
241
- jserializer: jserializer,
242
- panko: panko,
243
- rails: rails,
244
- representable: representable,
245
- simple_ams: simple_ams,
246
- turbostreamer: turbostreamer
247
- }.each do |name, serializer|
248
- result = serializer.call
249
- parsed_result = JSON.parse(result)
250
- puts "#{name} yields wrong output: #{parsed_result}" unless parsed_result == parsed_correct
251
- end
252
-
253
- # --- Run the benchmarks ---
254
-
255
- benchmark_body = lambda do |x|
256
- x.report(:alba, &alba)
257
- x.report(:alba_inline, &alba_inline)
258
- x.report(:ams, &ams)
259
- x.report(:blueprinter, &blueprinter)
260
- x.report(:fast_serializer, &fast_serializer)
261
- x.report(:jserializer, &jserializer)
262
- x.report(:panko, &panko)
263
- x.report(:rails, &rails)
264
- x.report(:representable, &representable)
265
- x.report(:simple_ams, &simple_ams)
266
- x.report(:turbostreamer, &turbostreamer)
267
-
268
- x.compare!
269
- end
270
-
271
- require 'benchmark/ips'
272
- Benchmark.ips(&benchmark_body)
273
-
274
- require 'benchmark/memory'
275
- Benchmark.memory(&benchmark_body)
data/benchmark/prep.rb DELETED
@@ -1,56 +0,0 @@
1
- # --- Test data model setup ---
2
-
3
- RubyVM::YJIT.enable if ENV["YJIT"]
4
- require "csv"
5
- require "pg"
6
- require "active_record"
7
- require "active_record/connection_adapters/postgresql_adapter"
8
- require "logger"
9
- require "oj"
10
- require "sqlite3"
11
- Oj.optimize_rails unless ENV['NO_OJ_OPTIMIZE_RAILS']
12
-
13
- ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
14
- # ActiveRecord::Base.logger = Logger.new($stdout)
15
-
16
- ActiveRecord::Schema.define do
17
- create_table :posts, force: true do |t|
18
- t.string :body
19
- end
20
-
21
- create_table :comments, force: true do |t|
22
- t.integer :post_id
23
- t.string :body
24
- t.integer :commenter_id
25
- end
26
-
27
- create_table :users, force: true do |t|
28
- t.string :name
29
- end
30
- end
31
-
32
- class Post < ActiveRecord::Base
33
- has_many :comments
34
- has_many :commenters, through: :comments, class_name: 'User', source: :commenter
35
-
36
- def attributes
37
- {id: nil, body: nil, commenter_names: commenter_names}
38
- end
39
-
40
- def commenter_names
41
- commenters.pluck(:name)
42
- end
43
- end
44
-
45
- class Comment < ActiveRecord::Base
46
- belongs_to :post
47
- belongs_to :commenter, class_name: 'User'
48
-
49
- def attributes
50
- {id: nil, body: nil}
51
- end
52
- end
53
-
54
- class User < ActiveRecord::Base
55
- has_many :comments
56
- end