alba 0.11.0 → 1.0.0

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: f9adc72b171ef6c74c4198ddad4cc8f629c416c62c6b8d8a9698efdbe9dccc44
4
- data.tar.gz: af33f05822bfee2bdfe455d4b7a9e75b0e2486c692297e24ab28768c0a839b24
3
+ metadata.gz: c3999a63b9564e6bf0a7b00dfeb64a19d6d076c94047f7ee869a4ac4a38c6646
4
+ data.tar.gz: dd7244bf63111e4795639a62dac91a744e51adb38f6a88d851c9cc6de3043dc0
5
5
  SHA512:
6
- metadata.gz: 2b289f590bf7b4cc3682b13f7841e9408f61ba7d49c0521b05649fb658745cd76c91032f3c2ee83224a1407f17686dc01eb383d90219877ac6f077b05095ce4c
7
- data.tar.gz: f842f9775b8355ed1b0dd88ea14aaecd57699fc578a11c38bf3a3d12ad04bbf01dbe0867bb30efc4cc73f1e7766d6e0e0e750741c6130dd33562434bfea6ba8b
6
+ metadata.gz: f93aaadf9b8fae4b9ae334f87647c3b0f1645147fd237b62f12451f4306f5cb6699ed25c935b563b68ab45c965bb1cfb8f2c73e08b23899a477384c37124d325
7
+ data.tar.gz: adb456a4796782e725bffa5720d1b5e6f831c1e0ec215721a814b331890f4a1d0ebe592e7e906239138729dd81586ad6e1c850ece61c5495ce829551feada9a1
@@ -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/.gitignore CHANGED
@@ -6,3 +6,5 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+
10
+ Gemfile.lock
data/.rubocop.yml CHANGED
@@ -6,21 +6,21 @@ 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
18
+ TargetRubyVersion: 2.5
16
19
 
17
20
  # Oneline comment is not valid so until it gets valid, we disable it
18
21
  Bundler/GemComment:
19
22
  Enabled: false
20
23
 
21
- Layout/ClassStructure:
22
- Enabled: true
23
-
24
24
  Layout/SpaceInsideHashLiteralBraces:
25
25
  EnforcedStyle: no_space
26
26
 
@@ -37,13 +37,20 @@ Metrics/ClassLength:
37
37
  Metrics/MethodLength:
38
38
  Max: 15
39
39
 
40
+ # Resource class includes DSLs, which tend to accept long list of parameters
41
+ Metrics/ParameterLists:
42
+ Exclude:
43
+ - 'lib/alba/resource.rb'
44
+
40
45
  Style/ConstantVisibility:
41
- Enabled: false
46
+ Exclude:
47
+ - 'lib/alba/version.rb'
42
48
 
43
49
  Style/Copyright:
44
50
  Enabled: false
45
51
 
46
- Style/DocumentationMethod:
52
+ # I know what I do :)
53
+ Style/DisableCopsWithinSourceCodeDirective:
47
54
  Enabled: false
48
55
 
49
56
  Style/FrozenStringLiteralComment:
data/.yardopts ADDED
@@ -0,0 +1,2 @@
1
+ --no-private
2
+ --exclude lib/alba/version.rb
data/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [1.0.0] - 2021-04-07
10
+
11
+ This is the first major release of Alba and it includes so many features. To see all the features you can have a look at [README](https://github.com/okuramasafumi/alba/blob/master/README.md#features).
data/Gemfile CHANGED
@@ -5,10 +5,13 @@ 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.11.0', 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 'ruby-prof', require: false # For performance profiling
17
+ gem 'yard', require: false
data/README.md CHANGED
@@ -2,26 +2,26 @@
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
+ ### Performance
21
23
 
22
- ## Performance
23
-
24
- Alba is faster than most of the alternatives. We have a [benchmark](https://gist.github.com/okuramasafumi/4e375525bd3a28e4ca812d2a3b3e5829).
24
+ Alba is faster than most of the alternatives. We have a [benchmark](https://github.com/okuramasafumi/alba/tree/master/benchmark).
25
25
 
26
26
  ## Installation
27
27
 
@@ -43,13 +43,41 @@ Or install it yourself as:
43
43
 
44
44
  Alba supports CRuby 2.5.7 and higher and latest TruffleRuby.
45
45
 
46
+ ## Documentation
47
+
48
+ You can find the documentation on [RubyDoc](https://rubydoc.info/github/okuramasafumi/alba).
49
+
50
+ ## Features
51
+
52
+ * Resource-based serialization
53
+ * Arbitrary attribute definition
54
+ * One and many association with the ability to define them inline
55
+ * Adding condition and filter to association
56
+ * Parameters can be injected and used in attributes and associations
57
+ * Conditional attributes and associations
58
+ * Selectable backend
59
+ * Key transformation
60
+ * Root key inference
61
+ * Error handling
62
+ * Resource name inflection based on association name
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
+ * And many others
73
+
46
74
  ## Usage
47
75
 
48
76
  ### Configuration
49
77
 
50
78
  Alba's configuration is fairly simple.
51
79
 
52
- #### Backend
80
+ #### Backend configuration
53
81
 
54
82
  Backend is the actual part serializing an object into JSON. Alba supports these backends.
55
83
 
@@ -63,6 +91,26 @@ You can set a backend like this:
63
91
  Alba.backend = :oj
64
92
  ```
65
93
 
94
+ #### Inference configuration
95
+
96
+ You can enable inference feature using `enable_inference!` method.
97
+
98
+ ```ruby
99
+ Alba.enable_inference!
100
+ ```
101
+
102
+ You must install `ActiveSupport` to enable inference.
103
+
104
+ #### Error handling configuration
105
+
106
+ You can configure error handling with `on_error` method.
107
+
108
+ ```ruby
109
+ Alba.on_error :ignore
110
+ ```
111
+
112
+ For the details, see [Error handling section](#error-handling)
113
+
66
114
  ### Simple serialization with key
67
115
 
68
116
  ```ruby
@@ -80,6 +128,8 @@ end
80
128
  class UserResource
81
129
  include Alba::Resource
82
130
 
131
+ key :user
132
+
83
133
  attributes :id, :name
84
134
 
85
135
  attribute :name_with_email do |resource|
@@ -87,12 +137,6 @@ class UserResource
87
137
  end
88
138
  end
89
139
 
90
- class SerializerWithKey
91
- include Alba::Serializer
92
-
93
- set key: :user
94
- end
95
-
96
140
  user = User.new(1, 'Masafumi OKURA', 'masafumi@example.com')
97
141
  UserResource.new(user).serialize
98
142
  # => "{\"id\":1,\"name\":\"Masafumi OKURA\",\"name_with_email\":\"Masafumi OKURA: masafumi@example.com\"}"
@@ -129,7 +173,7 @@ class ArticleResource
129
173
  attributes :title
130
174
  end
131
175
 
132
- class UserResource1
176
+ class UserResource
133
177
  include Alba::Resource
134
178
 
135
179
  attributes :id
@@ -143,7 +187,7 @@ user.articles << article1
143
187
  article2 = Article.new(2, 'Super nice', 'Really nice!')
144
188
  user.articles << article2
145
189
 
146
- UserResource1.new(user).serialize
190
+ UserResource.new(user).serialize
147
191
  # => '{"id":1,"articles":[{"title":"Hello World!"},{"title":"Super nice"}]}'
148
192
  ```
149
193
 
@@ -152,7 +196,7 @@ UserResource1.new(user).serialize
152
196
  `Alba.serialize` method is a shortcut to define everything inline.
153
197
 
154
198
  ```ruby
155
- Alba.serialize(user, with: proc { set key: :foo }) do
199
+ Alba.serialize(user, key: :foo) do
156
200
  attributes :id
157
201
  many :articles do
158
202
  attributes :title, :body
@@ -161,7 +205,7 @@ end
161
205
  # => '{"foo":{"id":1,"articles":[{"title":"Hello World!","body":"Hello World!!!"},{"title":"Super nice","body":"Really nice!"}]}}'
162
206
  ```
163
207
 
164
- Although this might be useful sometimes, it's generally recommended to define a class for both Resource and Serializer.
208
+ Although this might be useful sometimes, it's generally recommended to define a class for Resource.
165
209
 
166
210
  ### Inheritance and Ignorance
167
211
 
@@ -193,6 +237,209 @@ RestrictedFooResouce.new(foo).serialize
193
237
  end
194
238
  ```
195
239
 
240
+ ### Attribute key transformation
241
+
242
+ ** Note: You need to install `active_support` gem to use `transform_keys` DSL.
243
+
244
+ With `active_support` installed, you can transform attribute keys.
245
+
246
+ ```ruby
247
+ class User
248
+ attr_reader :id, :first_name, :last_name
249
+
250
+ def initialize(id, first_name, last_name)
251
+ @id = id
252
+ @first_name = first_name
253
+ @last_name = last_name
254
+ end
255
+ end
256
+
257
+ class UserResource
258
+ include Alba::Resource
259
+
260
+ attributes :id, :first_name, :last_name
261
+
262
+ transform_keys :lower_camel
263
+ end
264
+
265
+ user = User.new(1, 'Masafumi', 'Okura')
266
+ UserResourceCamel.new(user).serialize
267
+ # => '{"id":1,"firstName":"Masafumi","lastName":"Okura"}'
268
+ ```
269
+
270
+ Supported transformation types are :camel, :lower_camel and :dash.
271
+
272
+ ### Filtering attributes
273
+
274
+ You can filter attributes by overriding `Alba::Resource#converter` method, but it's a bit tricky.
275
+
276
+ ```ruby
277
+ class User
278
+ attr_accessor :id, :name, :email, :created_at, :updated_at
279
+
280
+ def initialize(id, name, email)
281
+ @id = id
282
+ @name = name
283
+ @email = email
284
+ end
285
+ end
286
+
287
+ class UserResource
288
+ include Alba::Resource
289
+
290
+ attributes :id, :name, :email
291
+
292
+ private
293
+
294
+ # Here using `Proc#>>` method to compose a proc from `super`
295
+ def converter
296
+ super >> proc { |hash| hash.compact }
297
+ end
298
+ end
299
+
300
+ user = User.new(1, nil, nil)
301
+ UserResource.new(user).serialize # => '{"id":1}'
302
+ ```
303
+
304
+ 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.
305
+
306
+ It's not recommended to swap the whole conversion logic. It's recommended to always call `super` when you override `converter`.
307
+
308
+ ### Conditional attributes
309
+
310
+ Filtering attributes with overriding `convert` works well for simple cases. However, It's cumbersome when we want to filter various attributes based on different conditions for keys.
311
+
312
+ In these cases, conditional attributes works well. We can pass `if` option to `attributes`, `attribute`, `one` and `many`. Below is an example for the same effect as [filtering attributes section](#filtering-attributes).
313
+
314
+ ```ruby
315
+ class User
316
+ attr_accessor :id, :name, :email, :created_at, :updated_at
317
+
318
+ def initialize(id, name, email)
319
+ @id = id
320
+ @name = name
321
+ @email = email
322
+ end
323
+ end
324
+
325
+ class UserResource
326
+ include Alba::Resource
327
+
328
+ attributes :id, :name, :email, if: proc { |user, attribute| !attribute.nil? }
329
+ end
330
+
331
+ user = User.new(1, nil, nil)
332
+ UserResource.new(user).serialize # => '{"id":1}'
333
+ ```
334
+
335
+ ### Inference
336
+
337
+ After `Alba.enable_inference!` called, Alba tries to infer root key and association resource name.
338
+
339
+ ```ruby
340
+ Alba.enable_inference!
341
+
342
+ class User
343
+ attr_reader :id
344
+ attr_accessor :articles
345
+
346
+ def initialize(id)
347
+ @id = id
348
+ @articles = []
349
+ end
350
+ end
351
+
352
+ class Article
353
+ attr_accessor :id, :title
354
+
355
+ def initialize(id, title)
356
+ @id = id
357
+ @title = title
358
+ end
359
+ end
360
+
361
+ class ArticleResource
362
+ include Alba::Resource
363
+
364
+ attributes :title
365
+ end
366
+
367
+ class UserResource
368
+ include Alba::Resource
369
+
370
+ key!
371
+
372
+ attributes :id
373
+
374
+ many :articles
375
+ end
376
+
377
+ user = User.new(1)
378
+ user.articles << Article.new(1, 'The title')
379
+
380
+ UserResource.new(user).serialize # => '{"user":{"id":1,"articles":[{"title":"The title"}]}}'
381
+ UserResource.new([user]).serialize # => '{"users":[{"id":1,"articles":[{"title":"The title"}]}]}'
382
+ ```
383
+
384
+ This resource automatically sets its root key to either "users" or "user", depending on the given object is collection or not.
385
+
386
+ Also, you don't have to specify which resource class to use with `many`. Alba infers it from association name.
387
+
388
+ Note that to enable this feature you must install `ActiveSupport` gem.
389
+
390
+ ### Error handling
391
+
392
+ You can set error handler globally or per resource using `on_error`.
393
+
394
+ ```ruby
395
+ class User
396
+ attr_accessor :id, :name
397
+
398
+ def initialize(id, name, email)
399
+ @id = id
400
+ @name = name
401
+ @email = email
402
+ end
403
+
404
+ def email
405
+ raise RuntimeError, 'Error!'
406
+ end
407
+ end
408
+
409
+ class UserResource
410
+ include Alba::Resource
411
+
412
+ attributes :id, :name, :email
413
+
414
+ on_error :ignore
415
+ end
416
+
417
+ user = User.new(1, 'Test', 'email@example.com')
418
+ UserResource.new(user).serialize # => '{"id":1,"name":"Test"}'
419
+ ```
420
+
421
+ This way you can exclude an entry when fetching an attribute gives an exception.
422
+
423
+ There are four possible arguments `on_error` method accepts.
424
+
425
+ * `:raise` re-raises an error. This is the default behavior.
426
+ * `:ignore` ignores the entry with the error.
427
+ * `:nullify` sets the attribute with the error to `nil`.
428
+ * Block gives you more control over what to be returned.
429
+
430
+ The block receives five arguments, `error`, `object`, `key`, `attribute` and `resource class` and must return a two-element array. Below is an example.
431
+
432
+ ```ruby
433
+ # Global error handling
434
+ Alba.on_error do |error, object, key, attribute, resource_class|
435
+ if resource_class == MyResource
436
+ ['error_fallback', object.error_fallback]
437
+ else
438
+ [key, error.message]
439
+ end
440
+ end
441
+ ```
442
+
196
443
  ## Comparison
197
444
 
198
445
  Alba is faster than alternatives.
@@ -210,18 +457,6 @@ Alba.backend = :active_support
210
457
 
211
458
  The name "Alba" comes from "albatross", a kind of birds. In Japanese, this bird is called "Aho-dori", which means "stupid bird". I find it funny because in fact albatrosses fly really fast. I hope Alba looks stupid but in fact it does its job quick.
212
459
 
213
- ## Alba internals
214
-
215
- Alba has three component, `Serializer`, `Resource` and `Value` (`Value` is conceptual and not implemented directly).
216
-
217
- `Serializer` is a component responsible for rendering JSON output with `Resource`. `Serializer` can add more data to `Resource` such as `metadata`. Users can define one single `Serializer` and reuse it for all `Resource`s. The main interface is `#serialize`.
218
-
219
- `Resource` is a component responsible for defining how an object (or a collection of objects) is converted into JSON. The difference between `Serializer` and `Resource` is that while `Serializer` can add arbitrary data into JSON, `Resource` can get data only from the object under it. The main interface is `#serializable_hash`.
220
-
221
- `One` and `Many` are the special object fetching other resources and converting them into Hash.
222
-
223
- The main `Alba` module holds config values and one convenience method, `.serialize`.
224
-
225
460
  ## Development
226
461
 
227
462
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.