alba 0.8.0 → 0.11.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +29 -1
- data/.travis.yml +2 -0
- data/.yardopts +2 -0
- data/Gemfile +10 -8
- data/Gemfile.lock +24 -8
- data/README.md +107 -3
- data/alba.gemspec +1 -1
- data/lib/alba.rb +61 -7
- data/lib/alba/association.rb +31 -0
- data/lib/alba/many.rb +11 -20
- data/lib/alba/one.rb +11 -20
- data/lib/alba/resource.rb +106 -42
- data/lib/alba/serializer.rb +40 -30
- data/lib/alba/version.rb +1 -1
- data/sider.yml +1 -0
- metadata +5 -6
- data/lib/alba/attribute.rb +0 -19
- data/lib/alba/resources/default_resource.rb +0 -9
- data/lib/alba/serializers/default_serializer.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9aae01860e12e1e5fd71f066e8772fb3a80b4aec6e99cf9dd55ad8652896ea24
|
4
|
+
data.tar.gz: c9930853dcaaa5ea2ff28e954c3b0d7e2ba559efc64b82e7da6b0c28968f55ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7882b0950c339f87fc75b19e79bcdc2481a296331e9be47a646db997fa4db5639af31c49d529063d099cd5df405357d245397af23b0038c568e32feb86bd5718
|
7
|
+
data.tar.gz: 2bc8c03f0ca5049a23b7dd5c2f6582051f98296a0ba9e0a436e08e9a426a53896e7ac94af276cc3a82e5a0a253f30d381db5384e483c77918ff3d5ef9165a1b7
|
data/.rubocop.yml
CHANGED
@@ -12,12 +12,40 @@ AllCops:
|
|
12
12
|
- 'Rakefile'
|
13
13
|
- 'alba.gemspec'
|
14
14
|
NewCops: enable
|
15
|
+
EnabledByDefault: true
|
16
|
+
|
17
|
+
# Oneline comment is not valid so until it gets valid, we disable it
|
18
|
+
Bundler/GemComment:
|
19
|
+
Enabled: false
|
15
20
|
|
16
21
|
Layout/SpaceInsideHashLiteralBraces:
|
17
22
|
EnforcedStyle: no_space
|
18
23
|
|
24
|
+
Layout/MultilineAssignmentLayout:
|
25
|
+
EnforcedStyle: same_line
|
26
|
+
|
27
|
+
Lint/ConstantResolution:
|
28
|
+
Enabled: false
|
29
|
+
|
30
|
+
Metrics/ClassLength:
|
31
|
+
Exclude:
|
32
|
+
- 'test/alba_test.rb'
|
33
|
+
|
19
34
|
Metrics/MethodLength:
|
20
|
-
Max:
|
35
|
+
Max: 15
|
36
|
+
|
37
|
+
Style/ConstantVisibility:
|
38
|
+
Exclude:
|
39
|
+
- 'lib/alba/version.rb'
|
40
|
+
|
41
|
+
Style/Copyright:
|
42
|
+
Enabled: false
|
21
43
|
|
22
44
|
Style/FrozenStringLiteralComment:
|
23
45
|
Enabled: false
|
46
|
+
|
47
|
+
Style/InlineComment:
|
48
|
+
Enabled: false
|
49
|
+
|
50
|
+
Style/MethodCallWithArgsParentheses:
|
51
|
+
Enabled: false
|
data/.travis.yml
CHANGED
data/.yardopts
ADDED
data/Gemfile
CHANGED
@@ -3,11 +3,13 @@ source 'https://rubygems.org'
|
|
3
3
|
# Specify your gem's dependencies in alba.gemspec
|
4
4
|
gemspec
|
5
5
|
|
6
|
-
gem '
|
7
|
-
gem '
|
8
|
-
gem '
|
9
|
-
gem '
|
10
|
-
gem '
|
11
|
-
gem 'rubocop
|
12
|
-
gem 'rubocop-
|
13
|
-
gem 'rubocop-
|
6
|
+
gem 'activesupport', require: false # For backend
|
7
|
+
gem 'coveralls', require: false # For test coverage
|
8
|
+
gem 'minitest', '~> 5.14' # For test
|
9
|
+
gem 'oj', '~> 3.10', platform: :ruby, require: false # For backend
|
10
|
+
gem 'rake', '~> 13.0' # For test and automation
|
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
|
14
|
+
gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
|
15
|
+
gem 'yard', require: false
|
data/Gemfile.lock
CHANGED
@@ -1,12 +1,19 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
alba (0.
|
4
|
+
alba (0.11.1)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
+
activesupport (6.0.3.2)
|
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)
|
9
15
|
ast (2.4.1)
|
16
|
+
concurrent-ruby (1.1.6)
|
10
17
|
coveralls (0.8.23)
|
11
18
|
json (>= 1.8, < 3)
|
12
19
|
simplecov (~> 0.16.1)
|
@@ -14,9 +21,11 @@ GEM
|
|
14
21
|
thor (>= 0.19.4, < 2.0)
|
15
22
|
tins (~> 1.6)
|
16
23
|
docile (1.3.2)
|
24
|
+
i18n (1.8.5)
|
25
|
+
concurrent-ruby (~> 1.0)
|
17
26
|
json (2.3.1)
|
18
|
-
minitest (5.14.
|
19
|
-
oj (3.10.
|
27
|
+
minitest (5.14.2)
|
28
|
+
oj (3.10.13)
|
20
29
|
parallel (1.19.2)
|
21
30
|
parser (2.7.1.4)
|
22
31
|
ast (~> 2.4.1)
|
@@ -24,17 +33,17 @@ GEM
|
|
24
33
|
rake (13.0.1)
|
25
34
|
regexp_parser (1.7.1)
|
26
35
|
rexml (3.2.4)
|
27
|
-
rubocop (0.
|
36
|
+
rubocop (0.90.0)
|
28
37
|
parallel (~> 1.10)
|
29
38
|
parser (>= 2.7.1.1)
|
30
39
|
rainbow (>= 2.2.2, < 4.0)
|
31
40
|
regexp_parser (>= 1.7)
|
32
41
|
rexml
|
33
|
-
rubocop-ast (>= 0.
|
42
|
+
rubocop-ast (>= 0.3.0, < 1.0)
|
34
43
|
ruby-progressbar (~> 1.7)
|
35
44
|
unicode-display_width (>= 1.4.0, < 2.0)
|
36
|
-
rubocop-ast (0.
|
37
|
-
parser (>= 2.7.
|
45
|
+
rubocop-ast (0.3.0)
|
46
|
+
parser (>= 2.7.1.4)
|
38
47
|
rubocop-minitest (0.10.1)
|
39
48
|
rubocop (>= 0.87)
|
40
49
|
rubocop-performance (1.7.1)
|
@@ -51,23 +60,30 @@ GEM
|
|
51
60
|
term-ansicolor (1.7.1)
|
52
61
|
tins (~> 1.0)
|
53
62
|
thor (1.0.1)
|
63
|
+
thread_safe (0.3.6)
|
54
64
|
tins (1.25.0)
|
55
65
|
sync
|
66
|
+
tzinfo (1.2.7)
|
67
|
+
thread_safe (~> 0.1)
|
56
68
|
unicode-display_width (1.7.0)
|
69
|
+
yard (0.9.25)
|
70
|
+
zeitwerk (2.4.0)
|
57
71
|
|
58
72
|
PLATFORMS
|
59
73
|
ruby
|
60
74
|
|
61
75
|
DEPENDENCIES
|
76
|
+
activesupport
|
62
77
|
alba!
|
63
78
|
coveralls
|
64
|
-
minitest (~> 5.
|
79
|
+
minitest (~> 5.14)
|
65
80
|
oj (~> 3.10)
|
66
81
|
rake (~> 13.0)
|
67
82
|
rubocop (>= 0.79.0)
|
68
83
|
rubocop-minitest (~> 0.10.1)
|
69
84
|
rubocop-performance (~> 1.7.1)
|
70
85
|
rubocop-sensible (~> 0.3.0)
|
86
|
+
yard
|
71
87
|
|
72
88
|
BUNDLED WITH
|
73
89
|
2.1.4
|
data/README.md
CHANGED
@@ -2,11 +2,29 @@
|
|
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
|
|
12
|
+
## Why yet another JSON serializer?
|
13
|
+
|
14
|
+
We know that there are several other JSON serializers for Ruby around, but none of them made us satisfied.
|
15
|
+
|
16
|
+
Alba has some advantages over other JSON serializers which we've wanted to have.
|
17
|
+
|
18
|
+
### Easy to understand
|
19
|
+
|
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.
|
21
|
+
|
22
|
+
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!
|
23
|
+
|
24
|
+
### Performance
|
25
|
+
|
26
|
+
Alba is faster than most of the alternatives. We have a [benchmark](https://gist.github.com/okuramasafumi/4e375525bd3a28e4ca812d2a3b3e5829).
|
27
|
+
|
10
28
|
## Installation
|
11
29
|
|
12
30
|
Add this line to your application's Gemfile:
|
@@ -23,8 +41,56 @@ Or install it yourself as:
|
|
23
41
|
|
24
42
|
$ gem install alba
|
25
43
|
|
44
|
+
## Supported Ruby versions
|
45
|
+
|
46
|
+
Alba supports CRuby 2.5.7 and higher and latest TruffleRuby.
|
47
|
+
|
48
|
+
## Documentation
|
49
|
+
|
50
|
+
You can find the documentation on [RubyDoc](https://rubydoc.info/gems/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
|
+
* No runtime dependencies
|
63
|
+
|
64
|
+
## Anti features
|
65
|
+
|
66
|
+
* Sorting keys
|
67
|
+
* Class level support of parameters
|
68
|
+
* Supporting all existing JSON encoder/decoder
|
69
|
+
* Cache
|
70
|
+
* [JSON:API](https://jsonapi.org) support
|
71
|
+
* Association name inflection
|
72
|
+
* And many others
|
73
|
+
|
26
74
|
## Usage
|
27
75
|
|
76
|
+
### Configuration
|
77
|
+
|
78
|
+
Alba's configuration is fairly simple.
|
79
|
+
|
80
|
+
#### Backend
|
81
|
+
|
82
|
+
Backend is the actual part serializing an object into JSON. Alba supports these backends.
|
83
|
+
|
84
|
+
* Oj, the fastest. Gem installation required.
|
85
|
+
* active_support, mostly for Rails. Gem installation required.
|
86
|
+
* default or json, with no external dependencies.
|
87
|
+
|
88
|
+
You can set a backend like this:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
Alba.backend = :oj
|
92
|
+
```
|
93
|
+
|
28
94
|
### Simple serialization with key
|
29
95
|
|
30
96
|
```ruby
|
@@ -91,7 +157,7 @@ class ArticleResource
|
|
91
157
|
attributes :title
|
92
158
|
end
|
93
159
|
|
94
|
-
class
|
160
|
+
class UserResource
|
95
161
|
include Alba::Resource
|
96
162
|
|
97
163
|
attributes :id
|
@@ -105,7 +171,7 @@ user.articles << article1
|
|
105
171
|
article2 = Article.new(2, 'Super nice', 'Really nice!')
|
106
172
|
user.articles << article2
|
107
173
|
|
108
|
-
|
174
|
+
UserResource.new(user).serialize
|
109
175
|
# => '{"id":1,"articles":[{"title":"Hello World!"},{"title":"Super nice"}]}'
|
110
176
|
```
|
111
177
|
|
@@ -125,11 +191,49 @@ end
|
|
125
191
|
|
126
192
|
Although this might be useful sometimes, it's generally recommended to define a class for both Resource and Serializer.
|
127
193
|
|
194
|
+
### Inheritance and Ignorance
|
195
|
+
|
196
|
+
You can `exclude` or `ignore` certain attributes using `ignoring`.
|
197
|
+
|
198
|
+
```ruby
|
199
|
+
class Foo
|
200
|
+
attr_accessor :id, :name, :body
|
201
|
+
|
202
|
+
def initialize(id, name, body)
|
203
|
+
@id = id
|
204
|
+
@name = name
|
205
|
+
@body = body
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
class GenericFooResource
|
210
|
+
include Alba::Resource
|
211
|
+
|
212
|
+
attributes :id, :name, :body
|
213
|
+
end
|
214
|
+
|
215
|
+
class RestrictedFooResouce < GenericFooResource
|
216
|
+
ignoring :id, :body
|
217
|
+
end
|
218
|
+
|
219
|
+
RestrictedFooResouce.new(foo).serialize
|
220
|
+
# => '{"name":"my foo"}'
|
221
|
+
end
|
222
|
+
```
|
223
|
+
|
128
224
|
## Comparison
|
129
225
|
|
130
226
|
Alba is faster than alternatives.
|
131
227
|
For a performance benchmark, see https://gist.github.com/okuramasafumi/4e375525bd3a28e4ca812d2a3b3e5829.
|
132
228
|
|
229
|
+
## Rails
|
230
|
+
|
231
|
+
When you use Alba in Rails, you can create an initializer file with the line below for compatibility with Rails JSON encoder.
|
232
|
+
|
233
|
+
```ruby
|
234
|
+
Alba.backend = :active_support
|
235
|
+
```
|
236
|
+
|
133
237
|
## Why named "Alba"?
|
134
238
|
|
135
239
|
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.
|
@@ -142,7 +246,7 @@ Alba has three component, `Serializer`, `Resource` and `Value` (`Value` is conce
|
|
142
246
|
|
143
247
|
`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`.
|
144
248
|
|
145
|
-
`
|
249
|
+
`One` and `Many` are the special object fetching other resources and converting them into Hash.
|
146
250
|
|
147
251
|
The main `Alba` module holds config values and one convenience method, `.serialize`.
|
148
252
|
|
data/alba.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.description = "Alba is designed to be a simple, easy to use and fast alternative to existing JSON serializers. Its performance is better than almost all gems which do similar things. The internal is so simple that it's easy to hack and maintain."
|
11
11
|
spec.homepage = 'https://github.com/okuramasafumi/alba'
|
12
12
|
spec.license = 'MIT'
|
13
|
-
spec.required_ruby_version = Gem::Requirement.new('>= 2.5.
|
13
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.5.7')
|
14
14
|
|
15
15
|
spec.metadata['homepage_uri'] = spec.homepage
|
16
16
|
spec.metadata['source_code_uri'] = 'https://github.com/okuramasafumi/alba'
|
data/lib/alba.rb
CHANGED
@@ -1,21 +1,36 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
require 'alba/resource'
|
5
|
-
require 'alba/resources/default_resource'
|
1
|
+
require_relative 'alba/version'
|
2
|
+
require_relative 'alba/serializer'
|
3
|
+
require_relative 'alba/resource'
|
6
4
|
|
7
5
|
# Core module
|
8
6
|
module Alba
|
7
|
+
# Base class for Errors
|
9
8
|
class Error < StandardError; end
|
9
|
+
# Error class for backend which is not supported
|
10
|
+
class UnsupportedBackend < Error; end
|
10
11
|
|
11
12
|
class << self
|
12
|
-
attr_reader :backend
|
13
|
+
attr_reader :backend, :encoder
|
13
14
|
attr_accessor :default_serializer
|
14
15
|
|
16
|
+
# Set the backend, which actually serializes object into JSON
|
17
|
+
#
|
18
|
+
# @param backend [#to_sym, nil] the name of the backend
|
19
|
+
# Possible values are `oj`, `active_support`, `default`, `json` and nil
|
20
|
+
# @return [Proc] the proc to encode object into JSON
|
21
|
+
# @raise [Alba::UnsupportedBackend] if backend is not supported
|
15
22
|
def backend=(backend)
|
16
23
|
@backend = backend&.to_sym
|
24
|
+
set_encoder
|
17
25
|
end
|
18
26
|
|
27
|
+
# Serialize the object with inline definitions
|
28
|
+
#
|
29
|
+
# @param object [Object] the object to be serialized
|
30
|
+
# @param with [nil, Proc, Alba::Serializer] selializer
|
31
|
+
# @param block [Block] resource block
|
32
|
+
# @return [String] serialized JSON string
|
33
|
+
# @raise [ArgumentError] if block is absent or `with` argument's type is wrong
|
19
34
|
def serialize(object, with: nil, &block)
|
20
35
|
raise ArgumentError, 'Block required' unless block
|
21
36
|
|
@@ -27,8 +42,47 @@ module Alba
|
|
27
42
|
|
28
43
|
private
|
29
44
|
|
45
|
+
def set_encoder
|
46
|
+
@encoder = case @backend
|
47
|
+
when :oj
|
48
|
+
try_oj
|
49
|
+
when :active_support
|
50
|
+
try_active_support
|
51
|
+
when nil, :default, :json
|
52
|
+
default_encoder
|
53
|
+
else
|
54
|
+
raise Alba::UnsupportedBackend, "Unsupported backend, #{backend}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def try_oj
|
59
|
+
require 'oj'
|
60
|
+
->(hash) { Oj.dump(hash, mode: :strict) }
|
61
|
+
rescue LoadError
|
62
|
+
default_encoder
|
63
|
+
end
|
64
|
+
|
65
|
+
def try_active_support
|
66
|
+
require 'active_support/json'
|
67
|
+
->(hash) { ActiveSupport::JSON.encode(hash) }
|
68
|
+
rescue LoadError
|
69
|
+
default_encoder
|
70
|
+
end
|
71
|
+
|
72
|
+
def default_encoder
|
73
|
+
lambda do |hash|
|
74
|
+
require 'json'
|
75
|
+
JSON.dump(hash)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
30
79
|
def resource_class
|
31
|
-
|
80
|
+
@resource_class ||= begin
|
81
|
+
klass = Class.new
|
82
|
+
klass.include(Alba::Resource)
|
83
|
+
end
|
32
84
|
end
|
33
85
|
end
|
86
|
+
|
87
|
+
@encoder = default_encoder
|
34
88
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Alba
|
2
|
+
# Base class for `One` and `Many`
|
3
|
+
# Child class should implement `to_hash` method
|
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
|
9
|
+
def initialize(name:, condition: nil, resource: nil, &block)
|
10
|
+
@name = name
|
11
|
+
@condition = condition
|
12
|
+
@block = block
|
13
|
+
@resource = resource || resource_class
|
14
|
+
raise ArgumentError, 'resource or block is required' if @resource.nil? && @block.nil?
|
15
|
+
end
|
16
|
+
|
17
|
+
# @abstract
|
18
|
+
def to_hash
|
19
|
+
:not_implemented
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def resource_class
|
25
|
+
klass = Class.new
|
26
|
+
klass.include(Alba::Resource)
|
27
|
+
klass.class_eval(&@block)
|
28
|
+
klass
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/alba/many.rb
CHANGED
@@ -1,26 +1,17 @@
|
|
1
|
+
require_relative 'association'
|
2
|
+
|
1
3
|
module Alba
|
2
4
|
# Representing many association
|
3
|
-
class Many
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def to_hash(target)
|
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>]
|
11
|
+
def to_hash(target, params: {})
|
12
12
|
objects = target.public_send(@name)
|
13
|
-
@
|
14
|
-
objects.map { |o| @resource.new(o).to_hash }
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
def resource_class
|
20
|
-
klass = Class.new
|
21
|
-
klass.include(::Alba::Resource)
|
22
|
-
klass.class_exec(&@block)
|
23
|
-
klass
|
13
|
+
objects = @condition.call(objects, params) if @condition
|
14
|
+
objects.map { |o| @resource.new(o, params: params).to_hash }
|
24
15
|
end
|
25
16
|
end
|
26
17
|
end
|
data/lib/alba/one.rb
CHANGED
@@ -1,26 +1,17 @@
|
|
1
|
+
require_relative 'association'
|
2
|
+
|
1
3
|
module Alba
|
2
4
|
# Representing one association
|
3
|
-
class One
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def to_hash(target)
|
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]
|
11
|
+
def to_hash(target, params: {})
|
12
12
|
object = target.public_send(@name)
|
13
|
-
@
|
14
|
-
@resource.new(object).to_hash
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
def resource_class
|
20
|
-
klass = Class.new
|
21
|
-
klass.include(::Alba::Resource)
|
22
|
-
klass.class_exec(&@block)
|
23
|
-
klass
|
13
|
+
object = @condition.call(object, params) if @condition
|
14
|
+
@resource.new(object, params: params).to_hash
|
24
15
|
end
|
25
16
|
end
|
26
17
|
end
|
data/lib/alba/resource.rb
CHANGED
@@ -1,24 +1,22 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
require 'alba/many'
|
5
|
-
require 'alba/serializers/default_serializer'
|
1
|
+
require_relative 'serializer'
|
2
|
+
require_relative 'one'
|
3
|
+
require_relative 'many'
|
6
4
|
|
7
5
|
module Alba
|
8
6
|
# This module represents what should be serialized
|
9
7
|
module Resource
|
10
|
-
|
8
|
+
# @!parse include InstanceMethods
|
9
|
+
# @!parse extend ClassMethods
|
10
|
+
DSLS = {_attributes: {}, _serializer: nil, _key: nil}.freeze
|
11
|
+
private_constant :DSLS
|
12
|
+
|
13
|
+
# @private
|
11
14
|
def self.included(base)
|
15
|
+
super
|
12
16
|
base.class_eval do
|
13
17
|
# Initialize
|
14
|
-
DSLS.each do |name|
|
15
|
-
initial
|
16
|
-
when :_attributes
|
17
|
-
{}
|
18
|
-
when :_serializer, :_name
|
19
|
-
nil
|
20
|
-
end
|
21
|
-
instance_variable_set("@#{name}", initial) unless instance_variable_defined?("@#{name}")
|
18
|
+
DSLS.each do |name, initial|
|
19
|
+
instance_variable_set("@#{name}", initial.dup) unless instance_variable_defined?("@#{name}")
|
22
20
|
end
|
23
21
|
end
|
24
22
|
base.include InstanceMethods
|
@@ -27,17 +25,24 @@ module Alba
|
|
27
25
|
|
28
26
|
# Instance methods
|
29
27
|
module InstanceMethods
|
30
|
-
attr_reader :
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
28
|
+
attr_reader :object, :_key, :params
|
29
|
+
|
30
|
+
# @param object [Object] the object to be serialized
|
31
|
+
# @param params [Hash] user-given Hash for arbitrary data
|
32
|
+
def initialize(object, params: {})
|
33
|
+
@object = object
|
34
|
+
@params = params.freeze
|
35
|
+
DSLS.each_key { |name| instance_variable_set("@#{name}", self.class.public_send(name)) }
|
35
36
|
end
|
36
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
|
37
42
|
def serialize(with: nil)
|
38
43
|
serializer = case with
|
39
44
|
when nil
|
40
|
-
@_serializer ||
|
45
|
+
@_serializer || empty_serializer
|
41
46
|
when ->(obj) { obj.is_a?(Class) && obj <= Alba::Serializer }
|
42
47
|
with
|
43
48
|
when Proc
|
@@ -48,71 +53,130 @@ module Alba
|
|
48
53
|
serializer.new(self).serialize
|
49
54
|
end
|
50
55
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
57
|
-
serializable_hash = if collection?
|
58
|
-
@_object.map(&get_attribute)
|
59
|
-
else
|
60
|
-
get_attribute.call(@_object)
|
61
|
-
end
|
62
|
-
with_key && @_key ? {@_key => serializable_hash} : serializable_hash
|
56
|
+
# A Hash for serialization
|
57
|
+
#
|
58
|
+
# @return [Hash]
|
59
|
+
def serializable_hash
|
60
|
+
collection? ? @object.map(&converter) : converter.call(@object)
|
63
61
|
end
|
64
62
|
alias to_hash serializable_hash
|
65
63
|
|
64
|
+
# @return [Symbol]
|
66
65
|
def key
|
67
66
|
@_key || self.class.name.delete_suffix('Resource').downcase.gsub(/:{2}/, '_').to_sym
|
68
67
|
end
|
69
68
|
|
70
69
|
private
|
71
70
|
|
71
|
+
def converter
|
72
|
+
lambda do |resource|
|
73
|
+
@_attributes.transform_values do |attribute|
|
74
|
+
case attribute
|
75
|
+
when Symbol
|
76
|
+
resource.public_send attribute
|
77
|
+
when Proc
|
78
|
+
instance_exec(resource, &attribute)
|
79
|
+
when Alba::One, Alba::Many
|
80
|
+
attribute.to_hash(resource, params: params)
|
81
|
+
else
|
82
|
+
raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def empty_serializer
|
89
|
+
klass = Class.new
|
90
|
+
klass.include Alba::Serializer
|
91
|
+
klass
|
92
|
+
end
|
93
|
+
|
72
94
|
def inline_extended_serializer(with)
|
73
|
-
klass =
|
95
|
+
klass = empty_serializer
|
74
96
|
klass.class_eval(&with)
|
75
97
|
klass
|
76
98
|
end
|
77
99
|
|
78
100
|
def collection?
|
79
|
-
@
|
101
|
+
@object.is_a?(Enumerable)
|
80
102
|
end
|
81
103
|
end
|
82
104
|
|
83
105
|
# Class methods
|
84
106
|
module ClassMethods
|
85
|
-
attr_reader(*DSLS)
|
107
|
+
attr_reader(*DSLS.keys)
|
86
108
|
|
109
|
+
# @private
|
87
110
|
def inherited(subclass)
|
88
|
-
|
111
|
+
super
|
112
|
+
DSLS.each_key { |name| subclass.instance_variable_set("@#{name}", instance_variable_get("@#{name}").clone) }
|
89
113
|
end
|
90
114
|
|
115
|
+
# Set multiple attributes at once
|
116
|
+
#
|
117
|
+
# @param attrs [Array<String, Symbol>]
|
91
118
|
def attributes(*attrs)
|
92
|
-
attrs.each { |attr_name| @_attributes[attr_name] =
|
119
|
+
attrs.each { |attr_name| @_attributes[attr_name.to_sym] = attr_name.to_sym }
|
93
120
|
end
|
94
121
|
|
122
|
+
# Set an attribute with the given block
|
123
|
+
#
|
124
|
+
# @param name [String, Symbol] key name
|
125
|
+
# @param block [Block] the block called during serialization
|
126
|
+
# @raise [ArgumentError] if block is absent
|
95
127
|
def attribute(name, &block)
|
96
128
|
raise ArgumentError, 'No block given in attribute method' unless block
|
97
129
|
|
98
|
-
@_attributes[name] =
|
130
|
+
@_attributes[name.to_sym] = block
|
99
131
|
end
|
100
132
|
|
101
|
-
|
102
|
-
|
133
|
+
# Set One association
|
134
|
+
#
|
135
|
+
# @param name [String, Symbol]
|
136
|
+
# @param condition [Proc]
|
137
|
+
# @param resource [Class<Alba::Resource>]
|
138
|
+
# @param key [String, Symbol] used as key when given
|
139
|
+
# @param block [Block]
|
140
|
+
# @see Alba::One#initialize
|
141
|
+
def one(name, condition = nil, resource: nil, key: nil, &block)
|
142
|
+
@_attributes[key&.to_sym || name.to_sym] = One.new(name: name, condition: condition, resource: resource, &block)
|
103
143
|
end
|
104
144
|
|
105
|
-
|
106
|
-
|
145
|
+
# Set Many association
|
146
|
+
#
|
147
|
+
# @param name [String, Symbol]
|
148
|
+
# @param condition [Proc]
|
149
|
+
# @param resource [Class<Alba::Resource>]
|
150
|
+
# @param key [String, Symbol] used as key when given
|
151
|
+
# @param block [Block]
|
152
|
+
# @see Alba::Many#initialize
|
153
|
+
def many(name, condition = nil, resource: nil, key: nil, &block)
|
154
|
+
@_attributes[key&.to_sym || name.to_sym] = Many.new(name: name, condition: condition, resource: resource, &block)
|
107
155
|
end
|
108
156
|
|
157
|
+
# Set serializer for the resource
|
158
|
+
#
|
159
|
+
# @param name [Alba::Serializer]
|
109
160
|
def serializer(name)
|
110
161
|
@_serializer = name <= Alba::Serializer ? name : nil
|
111
162
|
end
|
112
163
|
|
164
|
+
# Set key
|
165
|
+
#
|
166
|
+
# @param key [String, Symbol]
|
113
167
|
def key(key)
|
114
168
|
@_key = key.to_sym
|
115
169
|
end
|
170
|
+
|
171
|
+
# Delete attributes
|
172
|
+
# Use this DSL in child class to ignore certain attributes
|
173
|
+
#
|
174
|
+
# @param attributes [Array<String, Symbol>]
|
175
|
+
def ignoring(*attributes)
|
176
|
+
attributes.each do |attr_name|
|
177
|
+
@_attributes.delete(attr_name.to_sym)
|
178
|
+
end
|
179
|
+
end
|
116
180
|
end
|
117
181
|
end
|
118
182
|
end
|
data/lib/alba/serializer.rb
CHANGED
@@ -1,45 +1,48 @@
|
|
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)
|
9
|
+
super
|
10
|
+
base.class_eval do
|
11
|
+
@_opts = {} unless instance_variable_defined?('@_opts')
|
12
|
+
@_metadata = {} unless instance_variable_defined?('@_metadata')
|
13
|
+
end
|
5
14
|
base.include InstanceMethods
|
6
15
|
base.extend ClassMethods
|
7
16
|
end
|
8
17
|
|
9
18
|
# Instance methods
|
10
19
|
module InstanceMethods
|
20
|
+
# @param resource [Alba::Resource]
|
11
21
|
def initialize(resource)
|
12
|
-
@
|
13
|
-
@
|
14
|
-
@_metadata = @_metadata.transform_values { |block| block.call(resource._object) }
|
15
|
-
key = case @_opts[:key]
|
16
|
-
when true
|
17
|
-
resource.key
|
18
|
-
else
|
19
|
-
@_opts[:key]
|
20
|
-
end
|
21
|
-
@hash = resource.serializable_hash(with_key: false)
|
22
|
+
@resource = resource
|
23
|
+
@hash = resource.serializable_hash
|
22
24
|
@hash = {key.to_sym => @hash} if key
|
23
25
|
# @hash is either Hash or Array
|
24
|
-
@hash.is_a?(Hash) ? @hash.merge!(
|
26
|
+
@hash.is_a?(Hash) ? @hash.merge!(metadata.to_h) : @hash << metadata
|
25
27
|
end
|
26
28
|
|
29
|
+
# Use real encoder to actually serialize to JSON
|
30
|
+
#
|
31
|
+
# @return [String] JSON string
|
27
32
|
def serialize
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
fallback
|
42
|
-
end.call
|
33
|
+
Alba.encoder.call(@hash)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def key
|
39
|
+
opts = self.class._opts
|
40
|
+
opts[:key] == true ? @resource.key : opts[:key]
|
41
|
+
end
|
42
|
+
|
43
|
+
def metadata
|
44
|
+
metadata = self.class._metadata
|
45
|
+
metadata.transform_values { |block| block.call(@resource.object) }
|
43
46
|
end
|
44
47
|
end
|
45
48
|
|
@@ -47,18 +50,25 @@ module Alba
|
|
47
50
|
module ClassMethods
|
48
51
|
attr_reader :_opts, :_metadata
|
49
52
|
|
53
|
+
# @private
|
50
54
|
def inherited(subclass)
|
51
|
-
|
55
|
+
super
|
56
|
+
%w[_opts _metadata].each { |name| subclass.instance_variable_set("@#{name}", public_send(name).clone) }
|
52
57
|
end
|
53
58
|
|
59
|
+
# Set options, currently key only
|
60
|
+
#
|
61
|
+
# @param key [Boolean, Symbol]
|
54
62
|
def set(key: false)
|
55
|
-
@_opts ||= {}
|
56
63
|
@_opts[:key] = key
|
57
64
|
end
|
58
65
|
|
66
|
+
# Set metadata
|
67
|
+
#
|
68
|
+
# @param name [String, Symbol] key for the metadata
|
69
|
+
# @param block [Block] the content of the metadata
|
59
70
|
def metadata(name, &block)
|
60
|
-
@_metadata
|
61
|
-
@_metadata[name] = block
|
71
|
+
@_metadata[name.to_sym] = block
|
62
72
|
end
|
63
73
|
end
|
64
74
|
end
|
data/lib/alba/version.rb
CHANGED
data/sider.yml
CHANGED
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.
|
4
|
+
version: 0.11.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-
|
11
|
+
date: 2020-09-02 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
|
@@ -22,6 +22,7 @@ files:
|
|
22
22
|
- ".gitignore"
|
23
23
|
- ".rubocop.yml"
|
24
24
|
- ".travis.yml"
|
25
|
+
- ".yardopts"
|
25
26
|
- CODE_OF_CONDUCT.md
|
26
27
|
- Gemfile
|
27
28
|
- Gemfile.lock
|
@@ -32,13 +33,11 @@ files:
|
|
32
33
|
- bin/console
|
33
34
|
- bin/setup
|
34
35
|
- lib/alba.rb
|
35
|
-
- lib/alba/
|
36
|
+
- lib/alba/association.rb
|
36
37
|
- lib/alba/many.rb
|
37
38
|
- lib/alba/one.rb
|
38
39
|
- lib/alba/resource.rb
|
39
|
-
- lib/alba/resources/default_resource.rb
|
40
40
|
- lib/alba/serializer.rb
|
41
|
-
- lib/alba/serializers/default_serializer.rb
|
42
41
|
- lib/alba/version.rb
|
43
42
|
- sider.yml
|
44
43
|
homepage: https://github.com/okuramasafumi/alba
|
@@ -56,7 +55,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
56
55
|
requirements:
|
57
56
|
- - ">="
|
58
57
|
- !ruby/object:Gem::Version
|
59
|
-
version: 2.5.
|
58
|
+
version: 2.5.7
|
60
59
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
60
|
requirements:
|
62
61
|
- - ">="
|
data/lib/alba/attribute.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
module Alba
|
2
|
-
# This class represents an attribute, which is serialized
|
3
|
-
# by either sending message or calling a Proc.
|
4
|
-
class Attribute
|
5
|
-
def initialize(name:, method:)
|
6
|
-
@name = name
|
7
|
-
@method = method
|
8
|
-
end
|
9
|
-
|
10
|
-
def to_hash(target)
|
11
|
-
case @method
|
12
|
-
when Symbol, String
|
13
|
-
target.public_send(@method)
|
14
|
-
when Proc
|
15
|
-
@method.arity.zero? ? target.instance_exec(&@method) : @method.call(target)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
@@ -1,11 +0,0 @@
|
|
1
|
-
require 'alba/serializer'
|
2
|
-
|
3
|
-
module Alba
|
4
|
-
module Serializers
|
5
|
-
# DefaultSerializer class is used when a user doesn't specify serializer opt.
|
6
|
-
# It's basically an alias of Alba::Serializer, but since it's a module this class simply include it.
|
7
|
-
class DefaultSerializer
|
8
|
-
include Alba::Serializer
|
9
|
-
end
|
10
|
-
end
|
11
|
-
end
|