alba 0.10.2 → 0.13.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c5b12eead5b50b0e426952a2318fa85d852326a078fa5e285dc7a402ee19688d
4
- data.tar.gz: 89dc5fb1295528255ba028e2dfd8e30a2c136ff8a2ac9b9fb65b869a259f9435
3
+ metadata.gz: 993f4b887c19f4e1149ddb850cd07a8cb8c2042cde0fb3974b1380537707a55d
4
+ data.tar.gz: acf77a0aa35f7fe23fc3f98843064d2800e0b4e0739e587dd29aab65f06269ad
5
5
  SHA512:
6
- metadata.gz: c7d13ca3b2c592e97501cfb79de3aba84eb9ffeb107099bccde9a55a81b6bd36c93e9acda83d237b1e36de8dc34b3bb5aabbad68c7dd09cb998a5482d11f86f3
7
- data.tar.gz: 8e9ab49f10f5f09669ffc27135d9fe5fe57da8b5d9e57cd7d250d661d7f5cc0f9453992c78d8db3fbd8c08ed22b4b058266faf63977f95668481151a42434d97
6
+ metadata.gz: 9b8e52dcaf4ca33cba0685c2a0a06c16df69cd04b6d399c092e920548f5baa6a21aeab8097f90e92ab35ac9c260d83858c19462a9ec2a689d97c6999daef5e3a
7
+ data.tar.gz: e525a66c30477cab6741436128398a6f0309a0f786040fe946a9f2783d08f619df00d85157d04c5876cc579e19cceabf22dcb3556addf6ad061d44d1f0cfa888
@@ -0,0 +1,25 @@
1
+ name: Ruby
2
+
3
+ on: [push,pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ strategy:
8
+ fail-fast: false
9
+ matrix:
10
+ os: [ubuntu-latest, windows-latest, macos-latest]
11
+ ruby: [2.5, 2.6, 2.7, 3.0, head, truffleruby]
12
+ exclude:
13
+ - os: windows-latest
14
+ ruby: truffleruby
15
+ runs-on: ${{ matrix.os }}
16
+ steps:
17
+ - uses: actions/checkout@v2
18
+ - name: Set up Ruby
19
+ uses: ruby/setup-ruby@v1
20
+ with:
21
+ ruby-version: ${{ matrix.ruby }}
22
+ bundler-cache: true
23
+ - name: Run the default task
24
+ run: |
25
+ bundle exec rake
data/.rubocop.yml CHANGED
@@ -6,11 +6,13 @@ inherit_gem:
6
6
  require:
7
7
  - rubocop-minitest
8
8
  - rubocop-performance
9
+ - rubocop-rake
9
10
 
10
11
  AllCops:
11
12
  Exclude:
12
13
  - 'Rakefile'
13
14
  - 'alba.gemspec'
15
+ - 'benchmark/**/*.rb'
14
16
  NewCops: enable
15
17
  EnabledByDefault: true
16
18
 
@@ -18,9 +20,6 @@ AllCops:
18
20
  Bundler/GemComment:
19
21
  Enabled: false
20
22
 
21
- Layout/ClassStructure:
22
- Enabled: true
23
-
24
23
  Layout/SpaceInsideHashLiteralBraces:
25
24
  EnforcedStyle: no_space
26
25
 
@@ -38,12 +37,14 @@ Metrics/MethodLength:
38
37
  Max: 15
39
38
 
40
39
  Style/ConstantVisibility:
41
- Enabled: false
40
+ Exclude:
41
+ - 'lib/alba/version.rb'
42
42
 
43
43
  Style/Copyright:
44
44
  Enabled: false
45
45
 
46
- Style/DocumentationMethod:
46
+ # I know what I do :)
47
+ Style/DisableCopsWithinSourceCodeDirective:
47
48
  Enabled: false
48
49
 
49
50
  Style/FrozenStringLiteralComment:
data/.yardopts ADDED
@@ -0,0 +1,2 @@
1
+ --no-private
2
+ --exclude lib/alba/version.rb
data/Gemfile CHANGED
@@ -5,10 +5,12 @@ gemspec
5
5
 
6
6
  gem 'activesupport', require: false # For backend
7
7
  gem 'coveralls', require: false # For test coverage
8
- gem 'minitest', '~> 5.0' # For test
9
- gem 'oj', '~> 3.10', platform: :ruby, require: false # For backend
8
+ gem 'minitest', '~> 5.14' # For test
9
+ gem 'oj', '~> 3.11', platform: :ruby, require: false # For backend
10
10
  gem 'rake', '~> 13.0' # For test and automation
11
11
  gem 'rubocop', '>= 0.79.0', require: false # For lint
12
- gem 'rubocop-minitest', '~> 0.10.1', require: false # For lint
13
- gem 'rubocop-performance', '~> 1.7.1', require: false # For lint
12
+ gem 'rubocop-minitest', '~> 0.10.3', require: false # For lint
13
+ gem 'rubocop-performance', '~> 1.10.1', require: false # For lint
14
+ gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
14
15
  gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
16
+ gem 'yard', require: false
data/Gemfile.lock CHANGED
@@ -1,20 +1,19 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- alba (0.10.2)
4
+ alba (0.13.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- activesupport (6.0.3.2)
9
+ activesupport (6.1.3)
10
10
  concurrent-ruby (~> 1.0, >= 1.0.2)
11
- i18n (>= 0.7, < 2)
12
- minitest (~> 5.1)
13
- tzinfo (~> 1.1)
14
- zeitwerk (~> 2.2, >= 2.2.2)
15
- ast (2.4.1)
16
- bigdecimal (2.0.0)
17
- concurrent-ruby (1.1.6)
11
+ i18n (>= 1.6, < 2)
12
+ minitest (>= 5.1)
13
+ tzinfo (~> 2.0)
14
+ zeitwerk (~> 2.3)
15
+ ast (2.4.2)
16
+ concurrent-ruby (1.1.8)
18
17
  coveralls (0.8.23)
19
18
  json (>= 1.8, < 3)
20
19
  simplecov (~> 0.16.1)
@@ -22,37 +21,39 @@ GEM
22
21
  thor (>= 0.19.4, < 2.0)
23
22
  tins (~> 1.6)
24
23
  docile (1.3.2)
25
- i18n (1.8.5)
24
+ i18n (1.8.9)
26
25
  concurrent-ruby (~> 1.0)
27
26
  json (2.3.1)
28
- minitest (5.14.1)
29
- oj (3.10.12)
30
- bigdecimal (>= 1.0, < 3)
31
- parallel (1.19.2)
32
- parser (2.7.1.4)
27
+ minitest (5.14.3)
28
+ oj (3.11.2)
29
+ parallel (1.20.1)
30
+ parser (3.0.0.0)
33
31
  ast (~> 2.4.1)
34
32
  rainbow (3.0.0)
35
- rake (13.0.1)
36
- regexp_parser (1.7.1)
33
+ rake (13.0.3)
34
+ regexp_parser (2.1.1)
37
35
  rexml (3.2.4)
38
- rubocop (0.89.1)
36
+ rubocop (1.11.0)
39
37
  parallel (~> 1.10)
40
- parser (>= 2.7.1.1)
38
+ parser (>= 3.0.0.0)
41
39
  rainbow (>= 2.2.2, < 4.0)
42
- regexp_parser (>= 1.7)
40
+ regexp_parser (>= 1.8, < 3.0)
43
41
  rexml
44
- rubocop-ast (>= 0.3.0, < 1.0)
42
+ rubocop-ast (>= 1.2.0, < 2.0)
45
43
  ruby-progressbar (~> 1.7)
46
- unicode-display_width (>= 1.4.0, < 2.0)
47
- rubocop-ast (0.3.0)
48
- parser (>= 2.7.1.4)
49
- rubocop-minitest (0.10.1)
50
- rubocop (>= 0.87)
51
- rubocop-performance (1.7.1)
52
- rubocop (>= 0.82.0)
44
+ unicode-display_width (>= 1.4.0, < 3.0)
45
+ rubocop-ast (1.4.1)
46
+ parser (>= 2.7.1.5)
47
+ rubocop-minitest (0.10.3)
48
+ rubocop (>= 0.87, < 2.0)
49
+ rubocop-performance (1.10.1)
50
+ rubocop (>= 0.90.0, < 2.0)
51
+ rubocop-ast (>= 0.4.0)
52
+ rubocop-rake (0.5.1)
53
+ rubocop
53
54
  rubocop-sensible (0.3.0)
54
55
  rubocop (>= 0.60.0)
55
- ruby-progressbar (1.10.1)
56
+ ruby-progressbar (1.11.0)
56
57
  simplecov (0.16.1)
57
58
  docile (~> 1.1)
58
59
  json (>= 1.8, < 3)
@@ -62,13 +63,13 @@ GEM
62
63
  term-ansicolor (1.7.1)
63
64
  tins (~> 1.0)
64
65
  thor (1.0.1)
65
- thread_safe (0.3.6)
66
66
  tins (1.25.0)
67
67
  sync
68
- tzinfo (1.2.7)
69
- thread_safe (~> 0.1)
70
- unicode-display_width (1.7.0)
71
- zeitwerk (2.4.0)
68
+ tzinfo (2.0.4)
69
+ concurrent-ruby (~> 1.0)
70
+ unicode-display_width (2.0.0)
71
+ yard (0.9.26)
72
+ zeitwerk (2.4.2)
72
73
 
73
74
  PLATFORMS
74
75
  ruby
@@ -77,13 +78,15 @@ DEPENDENCIES
77
78
  activesupport
78
79
  alba!
79
80
  coveralls
80
- minitest (~> 5.0)
81
- oj (~> 3.10)
81
+ minitest (~> 5.14)
82
+ oj (~> 3.11)
82
83
  rake (~> 13.0)
83
84
  rubocop (>= 0.79.0)
84
- rubocop-minitest (~> 0.10.1)
85
- rubocop-performance (~> 1.7.1)
85
+ rubocop-minitest (~> 0.10.3)
86
+ rubocop-performance (~> 1.10.1)
87
+ rubocop-rake (>= 0.5.1)
86
88
  rubocop-sensible (~> 0.3.0)
89
+ yard
87
90
 
88
91
  BUNDLED WITH
89
- 2.1.4
92
+ 2.2.6
data/README.md CHANGED
@@ -2,26 +2,28 @@
2
2
  [![Build Status](https://travis-ci.com/okuramasafumi/alba.svg?branch=master)](https://travis-ci.com/okuramasafumi/alba)
3
3
  [![Coverage Status](https://coveralls.io/repos/github/okuramasafumi/alba/badge.svg?branch=master)](https://coveralls.io/github/okuramasafumi/alba?branch=master)
4
4
  [![Maintainability](https://api.codeclimate.com/v1/badges/fdab4cc0de0b9addcfe8/maintainability)](https://codeclimate.com/github/okuramasafumi/alba/maintainability)
5
+ ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/okuramasafumi/alba)
6
+ ![GitHub](https://img.shields.io/github/license/okuramasafumi/alba)
5
7
 
6
8
  # Alba
7
9
 
8
10
  `Alba` is the fastest JSON serializer for Ruby.
9
11
 
10
- # Why yet another JSON serializer?
12
+ ## Why yet another JSON serializer?
11
13
 
12
14
  We know that there are several other JSON serializers for Ruby around, but none of them made us satisfied.
13
15
 
14
16
  Alba has some advantages over other JSON serializers which we've wanted to have.
15
17
 
16
- ## Easy to understand
18
+ ### Easy to understand
17
19
 
18
20
  DSL is great. It makes the coding experience natural and intuitive. However, remembering lots of DSL requires us a lot of effort. Unfortunately, most of the existing libraries have implemented their features via DSL and it's not easy to understand how they behave entirely. Alba's core DSL are only four (`attributes`, `attribute`, `one` and `many`) so it's easy to understand how to use.
19
21
 
20
- Alba is also understandable internally. The codebase is much smaller than the alternatives. In fact, it's less than 300 lines of code. Look at the code on [GitHub](https://github.com/okuramasafumi/alba/tree/master/lib) and you'll be surprised how simple it is!
22
+ Alba is also understandable internally. The codebase is much smaller than the alternatives. In fact, it's about 330 lines of code. Look at the code on [GitHub](https://github.com/okuramasafumi/alba/tree/master/lib) and you'll be surprised how simple it is!
21
23
 
22
- ## Performance
24
+ ### Performance
23
25
 
24
- Alba is faster than most of the alternatives. We have a [benchmark](https://gist.github.com/okuramasafumi/4e375525bd3a28e4ca812d2a3b3e5829).
26
+ Alba is faster than most of the alternatives. We have a [benchmark](https://github.com/okuramasafumi/alba/tree/master/benchmark).
25
27
 
26
28
  ## Installation
27
29
 
@@ -43,6 +45,33 @@ Or install it yourself as:
43
45
 
44
46
  Alba supports CRuby 2.5.7 and higher and latest TruffleRuby.
45
47
 
48
+ ## Documentation
49
+
50
+ You can find the documentation on [RubyDoc](https://rubydoc.info/github/okuramasafumi/alba).
51
+
52
+ ## Features
53
+
54
+ * Resource-based serialization
55
+ * Arbitrary attribute definition
56
+ * One and many association with the ability to define them inline
57
+ * Adding condition and filter to association
58
+ * Parameters can be injected and used in attributes and associations
59
+ * Setting root key separately in Serializer
60
+ * Adding metadata
61
+ * Selectable backend
62
+ * Key transformation
63
+ * No runtime dependencies
64
+
65
+ ## Anti features
66
+
67
+ * Sorting keys
68
+ * Class level support of parameters
69
+ * Supporting all existing JSON encoder/decoder
70
+ * Cache
71
+ * [JSON:API](https://jsonapi.org) support
72
+ * Association name inflection
73
+ * And many others
74
+
46
75
  ## Usage
47
76
 
48
77
  ### Configuration
@@ -129,7 +158,7 @@ class ArticleResource
129
158
  attributes :title
130
159
  end
131
160
 
132
- class UserResource1
161
+ class UserResource
133
162
  include Alba::Resource
134
163
 
135
164
  attributes :id
@@ -143,7 +172,7 @@ user.articles << article1
143
172
  article2 = Article.new(2, 'Super nice', 'Really nice!')
144
173
  user.articles << article2
145
174
 
146
- UserResource1.new(user).serialize
175
+ UserResource.new(user).serialize
147
176
  # => '{"id":1,"articles":[{"title":"Hello World!"},{"title":"Super nice"}]}'
148
177
  ```
149
178
 
@@ -193,6 +222,76 @@ RestrictedFooResouce.new(foo).serialize
193
222
  end
194
223
  ```
195
224
 
225
+ ### Attribute key transformation
226
+
227
+ ** Note: You need to install `active_support` gem to use `transform_keys` DSL.
228
+
229
+ With `active_support` installed, you can transform attribute keys.
230
+
231
+ ```ruby
232
+ class User
233
+ attr_reader :id, :first_name, :last_name
234
+
235
+ def initialize(id, first_name, last_name)
236
+ @id = id
237
+ @first_name = first_name
238
+ @last_name = last_name
239
+ end
240
+ end
241
+
242
+ class UserResource
243
+ include Alba::Resource
244
+
245
+ attributes :id, :first_name, :last_name
246
+
247
+ transform_keys :lower_camel
248
+ end
249
+
250
+ user = User.new(1, 'Masafumi', 'Okura')
251
+ UserResourceCamel.new(user).serialize
252
+ # => '{"id":1,"firstName":"Masafumi","lastName":"Okura"}'
253
+ ```
254
+
255
+ Supported transformation types are :camel, :lower_camel and :dash.
256
+
257
+ ### Filtering attributes
258
+
259
+ You can filter attributes by overriding `Alba::Resource#converter` method, but it's a bit tricky.
260
+
261
+ ```ruby
262
+ class User
263
+ attr_accessor :id, :name, :email, :created_at, :updated_at
264
+
265
+ def initialize(id, name, email)
266
+ @id = id
267
+ @name = name
268
+ @email = email
269
+ end
270
+ end
271
+
272
+ class UserResource
273
+ include Alba::Resource
274
+
275
+ attributes :id, :name, :email
276
+
277
+ private
278
+
279
+ # Here using `Proc#>>` method to compose a proc from `super`
280
+ def converter
281
+ super >> proc { |hash| hash.compact }
282
+ end
283
+ end
284
+
285
+ user = User.new(1, nil, nil)
286
+ UserResource.new(user).serialize # => '{"id":1}'
287
+
288
+
289
+ ```
290
+
291
+ The key part is the use of `Proc#>>` since `Alba::Resource#converter` returns a `Proc` which contains the basic logic and it's impossible to change its behavior by just overriding the method.
292
+
293
+ It's not recommended to swap the whole conversion logic. It's recommended to always call `super` when you override `converter`.
294
+
196
295
  ## Comparison
197
296
 
198
297
  Alba is faster than alternatives.
@@ -0,0 +1,198 @@
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
data/lib/alba.rb CHANGED
@@ -1,21 +1,37 @@
1
- require 'alba/version'
2
- require 'alba/serializer'
3
- require 'alba/resource'
1
+ require_relative 'alba/version'
2
+ require_relative 'alba/serializer'
3
+ require_relative 'alba/resource'
4
4
 
5
5
  # Core module
6
6
  module Alba
7
+ # Base class for Errors
7
8
  class Error < StandardError; end
9
+
10
+ # Error class for backend which is not supported
8
11
  class UnsupportedBackend < Error; end
9
12
 
10
13
  class << self
11
14
  attr_reader :backend, :encoder
12
15
  attr_accessor :default_serializer
13
16
 
17
+ # Set the backend, which actually serializes object into JSON
18
+ #
19
+ # @param backend [#to_sym, nil] the name of the backend
20
+ # Possible values are `oj`, `active_support`, `default`, `json` and nil
21
+ # @return [Proc] the proc to encode object into JSON
22
+ # @raise [Alba::UnsupportedBackend] if backend is not supported
14
23
  def backend=(backend)
15
24
  @backend = backend&.to_sym
16
25
  set_encoder
17
26
  end
18
27
 
28
+ # Serialize the object with inline definitions
29
+ #
30
+ # @param object [Object] the object to be serialized
31
+ # @param with [nil, Proc, Alba::Serializer] selializer
32
+ # @param block [Block] resource block
33
+ # @return [String] serialized JSON string
34
+ # @raise [ArgumentError] if block is absent or `with` argument's type is wrong
19
35
  def serialize(object, with: nil, &block)
20
36
  raise ArgumentError, 'Block required' unless block
21
37
 
@@ -63,9 +79,9 @@ module Alba
63
79
 
64
80
  def resource_class
65
81
  @resource_class ||= begin
66
- klass = Class.new
67
- klass.include(Alba::Resource)
68
- end
82
+ klass = Class.new
83
+ klass.include(Alba::Resource)
84
+ end
69
85
  end
70
86
  end
71
87
 
@@ -2,6 +2,10 @@ module Alba
2
2
  # Base class for `One` and `Many`
3
3
  # Child class should implement `to_hash` method
4
4
  class Association
5
+ # @param name [Symbol] name of the method to fetch association
6
+ # @param condition [Proc] a proc filtering data
7
+ # @param resource [Class<Alba::Resource>] a resource class for the association
8
+ # @param block [Block] used to define resource when resource arg is absent
5
9
  def initialize(name:, condition: nil, resource: nil, &block)
6
10
  @name = name
7
11
  @condition = condition
@@ -10,6 +14,7 @@ module Alba
10
14
  raise ArgumentError, 'resource or block is required' if @resource.nil? && @block.nil?
11
15
  end
12
16
 
17
+ # @abstract
13
18
  def to_hash
14
19
  :not_implemented
15
20
  end
@@ -0,0 +1,31 @@
1
+ module Alba
2
+ # Transform keys using `ActiveSupport::Inflector`
3
+ module KeyTransformer
4
+ begin
5
+ require 'active_support/inflector'
6
+ rescue LoadError
7
+ raise ::Alba::Error, 'To use transform_keys, please install `ActiveSupport` gem.'
8
+ end
9
+
10
+ module_function
11
+
12
+ # Transform key as given transform_type
13
+ #
14
+ # @params key [String] key to be transformed
15
+ # @params transform_type [Symbol] transform type
16
+ # @return [String] transformed key
17
+ # @raise [Alba::Error] when transform_type is not supported
18
+ def transform(key, transform_type)
19
+ case transform_type
20
+ when :camel
21
+ ActiveSupport::Inflector.camelize(key)
22
+ when :lower_camel
23
+ ActiveSupport::Inflector.camelize(key, false)
24
+ when :dash
25
+ ActiveSupport::Inflector.dasherize(key)
26
+ else
27
+ raise ::Alba::Error, "Unknown transform_type: #{transform_type}. Supported transform_type are :camel, :lower_camel and :dash."
28
+ end
29
+ end
30
+ end
31
+ end
data/lib/alba/many.rb CHANGED
@@ -1,11 +1,18 @@
1
- require 'alba/association'
1
+ require_relative 'association'
2
2
 
3
3
  module Alba
4
4
  # Representing many association
5
5
  class Many < Association
6
+ # Recursively converts objects into an Array of Hashes
7
+ #
8
+ # @param target [Object] the object having an association method
9
+ # @param params [Hash] user-given Hash for arbitrary data
10
+ # @return [Array<Hash>]
6
11
  def to_hash(target, params: {})
7
12
  objects = target.public_send(@name)
8
- objects = @condition.call(objects) if @condition
13
+ objects = @condition.call(objects, params) if @condition
14
+ return if objects.nil?
15
+
9
16
  objects.map { |o| @resource.new(o, params: params).to_hash }
10
17
  end
11
18
  end
data/lib/alba/one.rb CHANGED
@@ -1,11 +1,18 @@
1
- require 'alba/association'
1
+ require_relative 'association'
2
2
 
3
3
  module Alba
4
4
  # Representing one association
5
5
  class One < Association
6
+ # Recursively converts an object into a Hash
7
+ #
8
+ # @param target [Object] the object having an association method
9
+ # @param params [Hash] user-given Hash for arbitrary data
10
+ # @return [Hash]
6
11
  def to_hash(target, params: {})
7
12
  object = target.public_send(@name)
8
- object = @condition.call(object) if @condition
13
+ object = @condition.call(object, params) if @condition
14
+ return if object.nil?
15
+
9
16
  @resource.new(object, params: params).to_hash
10
17
  end
11
18
  end
data/lib/alba/resource.rb CHANGED
@@ -1,11 +1,16 @@
1
- require 'alba/serializer'
2
- require 'alba/one'
3
- require 'alba/many'
1
+ require_relative 'serializer'
2
+ require_relative 'one'
3
+ require_relative 'many'
4
4
 
5
5
  module Alba
6
6
  # This module represents what should be serialized
7
7
  module Resource
8
- DSLS = {_attributes: {}, _serializer: nil, _key: nil}.freeze
8
+ # @!parse include InstanceMethods
9
+ # @!parse extend ClassMethods
10
+ DSLS = {_attributes: {}, _serializer: nil, _key: nil, _transform_keys: nil}.freeze
11
+ private_constant :DSLS
12
+
13
+ # @private
9
14
  def self.included(base)
10
15
  super
11
16
  base.class_eval do
@@ -22,12 +27,18 @@ module Alba
22
27
  module InstanceMethods
23
28
  attr_reader :object, :_key, :params
24
29
 
30
+ # @param object [Object] the object to be serialized
31
+ # @param params [Hash] user-given Hash for arbitrary data
25
32
  def initialize(object, params: {})
26
33
  @object = object
27
- @params = params
34
+ @params = params.freeze
28
35
  DSLS.each_key { |name| instance_variable_set("@#{name}", self.class.public_send(name)) }
29
36
  end
30
37
 
38
+ # Get serializer with `with` argument and serialize self with it
39
+ #
40
+ # @param with [nil, Proc, Alba::Serializer] selializer
41
+ # @return [String] serialized JSON string
31
42
  def serialize(with: nil)
32
43
  serializer = case with
33
44
  when nil
@@ -42,31 +53,49 @@ module Alba
42
53
  serializer.new(self).serialize
43
54
  end
44
55
 
56
+ # A Hash for serialization
57
+ #
58
+ # @return [Hash]
45
59
  def serializable_hash
46
60
  collection? ? @object.map(&converter) : converter.call(@object)
47
61
  end
48
62
  alias to_hash serializable_hash
49
63
 
64
+ # @return [Symbol]
50
65
  def key
51
66
  @_key || self.class.name.delete_suffix('Resource').downcase.gsub(/:{2}/, '_').to_sym
52
67
  end
53
68
 
54
69
  private
55
70
 
71
+ # rubocop:disable Style/MethodCalledOnDoEndBlock
56
72
  def converter
57
73
  lambda do |resource|
58
- @_attributes.transform_values do |attribute|
59
- case attribute
60
- when Symbol
61
- resource.public_send attribute
62
- when Proc
63
- instance_exec(resource, &attribute)
64
- when Alba::One, Alba::Many
65
- attribute.to_hash(resource, params: params)
66
- else
67
- raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
68
- end
69
- end
74
+ @_attributes.map do |key, attribute|
75
+ [transform_key(key), fetch_attribute(resource, attribute)]
76
+ end.to_h
77
+ end
78
+ end
79
+ # rubocop:enable Style/MethodCalledOnDoEndBlock
80
+
81
+ # Override this method to supply custom key transform method
82
+ def transform_key(key)
83
+ return key unless @_transform_keys
84
+
85
+ require_relative 'key_transformer'
86
+ KeyTransformer.transform(key, @_transform_keys)
87
+ end
88
+
89
+ def fetch_attribute(resource, attribute)
90
+ case attribute
91
+ when Symbol
92
+ resource.public_send attribute
93
+ when Proc
94
+ instance_exec(resource, &attribute)
95
+ when Alba::One, Alba::Many
96
+ attribute.to_hash(resource, params: params)
97
+ else
98
+ raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
70
99
  end
71
100
  end
72
101
 
@@ -91,43 +120,86 @@ module Alba
91
120
  module ClassMethods
92
121
  attr_reader(*DSLS.keys)
93
122
 
123
+ # @private
94
124
  def inherited(subclass)
95
125
  super
96
126
  DSLS.each_key { |name| subclass.instance_variable_set("@#{name}", instance_variable_get("@#{name}").clone) }
97
127
  end
98
128
 
129
+ # Set multiple attributes at once
130
+ #
131
+ # @param attrs [Array<String, Symbol>]
99
132
  def attributes(*attrs)
100
133
  attrs.each { |attr_name| @_attributes[attr_name.to_sym] = attr_name.to_sym }
101
134
  end
102
135
 
136
+ # Set an attribute with the given block
137
+ #
138
+ # @param name [String, Symbol] key name
139
+ # @param block [Block] the block called during serialization
140
+ # @raise [ArgumentError] if block is absent
103
141
  def attribute(name, &block)
104
142
  raise ArgumentError, 'No block given in attribute method' unless block
105
143
 
106
144
  @_attributes[name.to_sym] = block
107
145
  end
108
146
 
147
+ # Set One association
148
+ #
149
+ # @param name [String, Symbol]
150
+ # @param condition [Proc]
151
+ # @param resource [Class<Alba::Resource>]
152
+ # @param key [String, Symbol] used as key when given
153
+ # @param block [Block]
154
+ # @see Alba::One#initialize
109
155
  def one(name, condition = nil, resource: nil, key: nil, &block)
110
156
  @_attributes[key&.to_sym || name.to_sym] = One.new(name: name, condition: condition, resource: resource, &block)
111
157
  end
158
+ alias has_one one
112
159
 
160
+ # Set Many association
161
+ #
162
+ # @param name [String, Symbol]
163
+ # @param condition [Proc]
164
+ # @param resource [Class<Alba::Resource>]
165
+ # @param key [String, Symbol] used as key when given
166
+ # @param block [Block]
167
+ # @see Alba::Many#initialize
113
168
  def many(name, condition = nil, resource: nil, key: nil, &block)
114
169
  @_attributes[key&.to_sym || name.to_sym] = Many.new(name: name, condition: condition, resource: resource, &block)
115
170
  end
171
+ alias has_many many
116
172
 
173
+ # Set serializer for the resource
174
+ #
175
+ # @param name [Alba::Serializer]
117
176
  def serializer(name)
118
177
  @_serializer = name <= Alba::Serializer ? name : nil
119
178
  end
120
179
 
180
+ # Set key
181
+ #
182
+ # @param key [String, Symbol]
121
183
  def key(key)
122
184
  @_key = key.to_sym
123
185
  end
124
186
 
187
+ # Delete attributes
125
188
  # Use this DSL in child class to ignore certain attributes
189
+ #
190
+ # @param attributes [Array<String, Symbol>]
126
191
  def ignoring(*attributes)
127
192
  attributes.each do |attr_name|
128
193
  @_attributes.delete(attr_name.to_sym)
129
194
  end
130
195
  end
196
+
197
+ # Transform keys as specified type
198
+ #
199
+ # @param type [String, Symbol]
200
+ def transform_keys(type)
201
+ @_transform_keys = type.to_sym
202
+ end
131
203
  end
132
204
  end
133
205
  end
@@ -1,26 +1,34 @@
1
1
  module Alba
2
2
  # This module represents how a resource should be serialized.
3
3
  module Serializer
4
+ # @!parse include InstanceMethods
5
+ # @!parse extend ClassMethods
6
+
7
+ # @private
4
8
  def self.included(base)
5
9
  super
6
- base.class_eval do
7
- @_opts = {} unless instance_variable_defined?('@_opts')
8
- @_metadata = {} unless instance_variable_defined?('@_metadata')
9
- end
10
+ base.instance_variable_set('@_opts', {}) unless base.instance_variable_defined?('@_opts')
11
+ base.instance_variable_set('@_metadata', {}) unless base.instance_variable_defined?('@_metadata')
10
12
  base.include InstanceMethods
11
13
  base.extend ClassMethods
12
14
  end
13
15
 
14
16
  # Instance methods
15
17
  module InstanceMethods
18
+ # @param resource [Alba::Resource]
16
19
  def initialize(resource)
17
20
  @resource = resource
18
21
  @hash = resource.serializable_hash
19
22
  @hash = {key.to_sym => @hash} if key
23
+ return if metadata.empty?
24
+
20
25
  # @hash is either Hash or Array
21
26
  @hash.is_a?(Hash) ? @hash.merge!(metadata.to_h) : @hash << metadata
22
27
  end
23
28
 
29
+ # Use real encoder to actually serialize to JSON
30
+ #
31
+ # @return [String] JSON string
24
32
  def serialize
25
33
  Alba.encoder.call(@hash)
26
34
  end
@@ -42,17 +50,25 @@ module Alba
42
50
  module ClassMethods
43
51
  attr_reader :_opts, :_metadata
44
52
 
53
+ # @private
45
54
  def inherited(subclass)
46
55
  super
47
56
  %w[_opts _metadata].each { |name| subclass.instance_variable_set("@#{name}", public_send(name).clone) }
48
57
  end
49
58
 
59
+ # Set options, currently key only
60
+ #
61
+ # @param key [Boolean, Symbol]
50
62
  def set(key: false)
51
63
  @_opts[:key] = key
52
64
  end
53
65
 
66
+ # Set metadata
67
+ #
68
+ # @param name [String, Symbol] key for the metadata
69
+ # @param block [Block] the content of the metadata
54
70
  def metadata(name, &block)
55
- @_metadata[name] = block
71
+ @_metadata[name.to_sym] = block
56
72
  end
57
73
  end
58
74
  end
data/lib/alba/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Alba
2
- VERSION = '0.10.2'.freeze
2
+ VERSION = '0.13.1'.freeze
3
3
  end
data/sider.yml CHANGED
@@ -7,6 +7,7 @@ linter:
7
7
  - "rubocop-minitest"
8
8
  - "rubocop-performance"
9
9
  - "rubocop-sensible"
10
+ - "rubocop-rake"
10
11
  safe: false
11
12
 
12
13
  # # https://help.sider.review/tools/ruby/reek
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alba
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.2
4
+ version: 0.13.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - OKURA Masafumi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-08-24 00:00:00.000000000 Z
11
+ date: 2021-03-24 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Alba is designed to be a simple, easy to use and fast alternative to
14
14
  existing JSON serializers. Its performance is better than almost all gems which
@@ -19,9 +19,10 @@ executables: []
19
19
  extensions: []
20
20
  extra_rdoc_files: []
21
21
  files:
22
+ - ".github/workflows/main.yml"
22
23
  - ".gitignore"
23
24
  - ".rubocop.yml"
24
- - ".travis.yml"
25
+ - ".yardopts"
25
26
  - CODE_OF_CONDUCT.md
26
27
  - Gemfile
27
28
  - Gemfile.lock
@@ -29,10 +30,12 @@ files:
29
30
  - README.md
30
31
  - Rakefile
31
32
  - alba.gemspec
33
+ - benchmark/local.rb
32
34
  - bin/console
33
35
  - bin/setup
34
36
  - lib/alba.rb
35
37
  - lib/alba/association.rb
38
+ - lib/alba/key_transformer.rb
36
39
  - lib/alba/many.rb
37
40
  - lib/alba/one.rb
38
41
  - lib/alba/resource.rb
@@ -61,7 +64,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
61
64
  - !ruby/object:Gem::Version
62
65
  version: '0'
63
66
  requirements: []
64
- rubygems_version: 3.1.4
67
+ rubygems_version: 3.2.11
65
68
  signing_key:
66
69
  specification_version: 4
67
70
  summary: Alba is the fastest JSON serializer for Ruby.
data/.travis.yml DELETED
@@ -1,9 +0,0 @@
1
- ---
2
- language: ruby
3
- cache: bundler
4
- rvm:
5
- - 2.5.8
6
- - 2.6.6
7
- - 2.7.1
8
- - truffleruby
9
- before_install: gem install bundler -v 2.1.4