alba 0.12.0 → 1.1.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.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +34 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +28 -2
- data/CHANGELOG.md +25 -0
- data/Gemfile +10 -4
- data/README.md +269 -37
- data/Rakefile +4 -1
- data/alba.gemspec +2 -2
- data/benchmark/local.rb +315 -0
- data/codecov.yml +5 -0
- data/gemfiles/all.gemfile +19 -0
- data/gemfiles/without_active_support.gemfile +17 -0
- data/gemfiles/without_oj.gemfile +17 -0
- data/lib/alba.rb +42 -19
- data/lib/alba/association.rb +22 -7
- data/lib/alba/key_transformer.rb +32 -0
- data/lib/alba/many.rb +8 -4
- data/lib/alba/one.rb +8 -4
- data/lib/alba/resource.rb +146 -57
- data/lib/alba/version.rb +1 -1
- metadata +13 -8
- data/.travis.yml +0 -10
- data/Gemfile.lock +0 -92
- data/lib/alba/serializer.rb +0 -77
data/lib/alba/association.rb
CHANGED
@@ -2,25 +2,40 @@ module Alba
|
|
2
2
|
# Base class for `One` and `Many`
|
3
3
|
# Child class should implement `to_hash` method
|
4
4
|
class Association
|
5
|
+
attr_reader :object
|
6
|
+
|
5
7
|
# @param name [Symbol] name of the method to fetch association
|
6
8
|
# @param condition [Proc] a proc filtering data
|
7
9
|
# @param resource [Class<Alba::Resource>] a resource class for the association
|
8
10
|
# @param block [Block] used to define resource when resource arg is absent
|
9
|
-
def initialize(name:, condition: nil, resource: nil, &block)
|
11
|
+
def initialize(name:, condition: nil, resource: nil, nesting: nil, &block)
|
10
12
|
@name = name
|
11
13
|
@condition = condition
|
12
14
|
@block = block
|
13
|
-
@resource = resource
|
14
|
-
|
15
|
-
end
|
15
|
+
@resource = resource
|
16
|
+
return if @resource
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
if @block
|
19
|
+
@resource = resource_class
|
20
|
+
elsif Alba.inferring
|
21
|
+
const_parent = nesting.nil? ? Object : Object.const_get(nesting)
|
22
|
+
@resource = const_parent.const_get("#{ActiveSupport::Inflector.classify(@name)}Resource")
|
23
|
+
else
|
24
|
+
raise ArgumentError, 'When Alba.inferring is false, either resource or block is required'
|
25
|
+
end
|
20
26
|
end
|
21
27
|
|
22
28
|
private
|
23
29
|
|
30
|
+
def constantize(resource)
|
31
|
+
case resource # rubocop:disable Style/MissingElse
|
32
|
+
when Class
|
33
|
+
resource
|
34
|
+
when Symbol, String
|
35
|
+
Object.const_get(resource)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
24
39
|
def resource_class
|
25
40
|
klass = Class.new
|
26
41
|
klass.include(Alba::Resource)
|
@@ -0,0 +1,32 @@
|
|
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
|
+
key = key.to_s
|
20
|
+
case transform_type
|
21
|
+
when :camel
|
22
|
+
ActiveSupport::Inflector.camelize(key)
|
23
|
+
when :lower_camel
|
24
|
+
ActiveSupport::Inflector.camelize(key, false)
|
25
|
+
when :dash
|
26
|
+
ActiveSupport::Inflector.dasherize(key)
|
27
|
+
else
|
28
|
+
raise ::Alba::Error, "Unknown transform_type: #{transform_type}. Supported transform_type are :camel, :lower_camel and :dash."
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/alba/many.rb
CHANGED
@@ -6,12 +6,16 @@ module Alba
|
|
6
6
|
# Recursively converts objects into an Array of Hashes
|
7
7
|
#
|
8
8
|
# @param target [Object] the object having an association method
|
9
|
+
# @param within [Hash] determines what associations to be serialized. If not set, it serializes all associations.
|
9
10
|
# @param params [Hash] user-given Hash for arbitrary data
|
10
11
|
# @return [Array<Hash>]
|
11
|
-
def to_hash(target, params: {})
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
def to_hash(target, within: nil, params: {})
|
13
|
+
@object = target.public_send(@name)
|
14
|
+
@object = @condition.call(@object, params) if @condition
|
15
|
+
return if @object.nil?
|
16
|
+
|
17
|
+
@resource = constantize(@resource)
|
18
|
+
@object.map { |o| @resource.new(o, params: params, within: within).to_hash }
|
15
19
|
end
|
16
20
|
end
|
17
21
|
end
|
data/lib/alba/one.rb
CHANGED
@@ -6,12 +6,16 @@ module Alba
|
|
6
6
|
# Recursively converts an object into a Hash
|
7
7
|
#
|
8
8
|
# @param target [Object] the object having an association method
|
9
|
+
# @param within [Hash] determines what associations to be serialized. If not set, it serializes all associations.
|
9
10
|
# @param params [Hash] user-given Hash for arbitrary data
|
10
11
|
# @return [Hash]
|
11
|
-
def to_hash(target, params: {})
|
12
|
-
object = target.public_send(@name)
|
13
|
-
object = @condition.call(object, params) if @condition
|
14
|
-
@
|
12
|
+
def to_hash(target, within: nil, params: {})
|
13
|
+
@object = target.public_send(@name)
|
14
|
+
@object = @condition.call(object, params) if @condition
|
15
|
+
return if @object.nil?
|
16
|
+
|
17
|
+
@resource = constantize(@resource)
|
18
|
+
@resource.new(object, params: params, within: within).to_hash
|
15
19
|
end
|
16
20
|
end
|
17
21
|
end
|
data/lib/alba/resource.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require_relative 'serializer'
|
2
1
|
require_relative 'one'
|
3
2
|
require_relative 'many'
|
4
3
|
|
@@ -7,7 +6,7 @@ module Alba
|
|
7
6
|
module Resource
|
8
7
|
# @!parse include InstanceMethods
|
9
8
|
# @!parse extend ClassMethods
|
10
|
-
DSLS = {_attributes: {},
|
9
|
+
DSLS = {_attributes: {}, _key: nil, _transform_keys: nil, _on_error: nil}.freeze
|
11
10
|
private_constant :DSLS
|
12
11
|
|
13
12
|
# @private
|
@@ -25,32 +24,26 @@ module Alba
|
|
25
24
|
|
26
25
|
# Instance methods
|
27
26
|
module InstanceMethods
|
28
|
-
attr_reader :object, :
|
27
|
+
attr_reader :object, :params
|
29
28
|
|
30
29
|
# @param object [Object] the object to be serialized
|
31
30
|
# @param params [Hash] user-given Hash for arbitrary data
|
32
|
-
|
31
|
+
# @param within [Hash] determines what associations to be serialized. If not set, it serializes all associations.
|
32
|
+
def initialize(object, params: {}, within: true)
|
33
33
|
@object = object
|
34
34
|
@params = params.freeze
|
35
|
+
@within = within
|
35
36
|
DSLS.each_key { |name| instance_variable_set("@#{name}", self.class.public_send(name)) }
|
36
37
|
end
|
37
38
|
|
38
|
-
#
|
39
|
+
# Serialize object into JSON string
|
39
40
|
#
|
40
|
-
# @param
|
41
|
+
# @param key [Symbol]
|
41
42
|
# @return [String] serialized JSON string
|
42
|
-
def serialize(
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
when ->(obj) { obj.is_a?(Class) && obj <= Alba::Serializer }
|
47
|
-
with
|
48
|
-
when Proc
|
49
|
-
inline_extended_serializer(with)
|
50
|
-
else
|
51
|
-
raise ArgumentError, 'Unexpected type for with, possible types are Class or Proc'
|
52
|
-
end
|
53
|
-
serializer.new(self).serialize
|
43
|
+
def serialize(key: nil)
|
44
|
+
key = key.nil? ? _key : key
|
45
|
+
hash = key && key != '' ? {key.to_s => serializable_hash} : serializable_hash
|
46
|
+
Alba.encoder.call(hash)
|
54
47
|
end
|
55
48
|
|
56
49
|
# A Hash for serialization
|
@@ -61,40 +54,108 @@ module Alba
|
|
61
54
|
end
|
62
55
|
alias to_hash serializable_hash
|
63
56
|
|
64
|
-
# @return [Symbol]
|
65
|
-
def key
|
66
|
-
@_key || self.class.name.delete_suffix('Resource').downcase.gsub(/:{2}/, '_').to_sym
|
67
|
-
end
|
68
|
-
|
69
57
|
private
|
70
58
|
|
59
|
+
# @return [String]
|
60
|
+
def _key
|
61
|
+
if @_key == true && Alba.inferring
|
62
|
+
demodulized = ActiveSupport::Inflector.demodulize(self.class.name)
|
63
|
+
meth = collection? ? :tableize : :singularize
|
64
|
+
ActiveSupport::Inflector.public_send(meth, demodulized.delete_suffix('Resource').downcase)
|
65
|
+
else
|
66
|
+
@_key.to_s
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
71
70
|
def converter
|
72
|
-
lambda do |
|
73
|
-
@_attributes.
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
when Proc
|
78
|
-
instance_exec(resource, &attribute)
|
79
|
-
when Alba::One, Alba::Many
|
80
|
-
attribute.to_hash(resource, params: params)
|
71
|
+
lambda do |object|
|
72
|
+
arrays = @_attributes.map do |key, attribute|
|
73
|
+
key = transform_key(key)
|
74
|
+
if attribute.is_a?(Array) # Conditional
|
75
|
+
conditional_attribute(object, key, attribute)
|
81
76
|
else
|
82
|
-
|
77
|
+
[key, fetch_attribute(object, attribute)]
|
83
78
|
end
|
79
|
+
rescue ::Alba::Error, FrozenError
|
80
|
+
raise
|
81
|
+
rescue StandardError => e
|
82
|
+
handle_error(e, object, key, attribute)
|
84
83
|
end
|
84
|
+
arrays.reject(&:empty?).to_h
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def conditional_attribute(object, key, attribute)
|
89
|
+
condition = attribute.last
|
90
|
+
arity = condition.arity
|
91
|
+
return [] if arity <= 1 && !condition.call(object)
|
92
|
+
|
93
|
+
fetched_attribute = fetch_attribute(object, attribute.first)
|
94
|
+
attr = if attribute.first.is_a?(Alba::Association)
|
95
|
+
attribute.first.object
|
96
|
+
else
|
97
|
+
fetched_attribute
|
98
|
+
end
|
99
|
+
return [] if arity >= 2 && !condition.call(object, attr)
|
100
|
+
|
101
|
+
[key, fetched_attribute]
|
102
|
+
end
|
103
|
+
|
104
|
+
def handle_error(error, object, key, attribute)
|
105
|
+
on_error = @_on_error || Alba._on_error
|
106
|
+
case on_error
|
107
|
+
when :raise, nil
|
108
|
+
raise
|
109
|
+
when :nullify
|
110
|
+
[key, nil]
|
111
|
+
when :ignore
|
112
|
+
[]
|
113
|
+
when Proc
|
114
|
+
on_error.call(error, object, key, attribute, self.class)
|
115
|
+
else
|
116
|
+
raise ::Alba::Error, "Unknown on_error: #{on_error.inspect}"
|
85
117
|
end
|
86
118
|
end
|
87
119
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
120
|
+
# Override this method to supply custom key transform method
|
121
|
+
def transform_key(key)
|
122
|
+
return key unless @_transform_keys
|
123
|
+
|
124
|
+
require_relative 'key_transformer'
|
125
|
+
KeyTransformer.transform(key, @_transform_keys)
|
126
|
+
end
|
127
|
+
|
128
|
+
def fetch_attribute(object, attribute)
|
129
|
+
case attribute
|
130
|
+
when Symbol
|
131
|
+
object.public_send attribute
|
132
|
+
when Proc
|
133
|
+
instance_exec(object, &attribute)
|
134
|
+
when Alba::One, Alba::Many
|
135
|
+
within = check_within
|
136
|
+
return unless within
|
137
|
+
|
138
|
+
attribute.to_hash(object, params: params, within: within)
|
139
|
+
else
|
140
|
+
raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
|
141
|
+
end
|
92
142
|
end
|
93
143
|
|
94
|
-
def
|
95
|
-
|
96
|
-
|
97
|
-
|
144
|
+
def check_within
|
145
|
+
case @within
|
146
|
+
when Hash # Traverse within tree
|
147
|
+
@within.fetch(_key.to_sym, nil)
|
148
|
+
when Array # within tree ends with Array
|
149
|
+
@within.find { |item| item.to_sym == _key.to_sym } # Check if at least one item in the array matches current resource
|
150
|
+
when Symbol # within tree could end with Symbol
|
151
|
+
@within == _key.to_sym # Check if the symbol matches current resource
|
152
|
+
when true # In this case, Alba serializes all associations.
|
153
|
+
true
|
154
|
+
when nil, false # In these cases, Alba stops serialization here.
|
155
|
+
false
|
156
|
+
else
|
157
|
+
raise Alba::Error, "Unknown type for within option: #{@within.class}"
|
158
|
+
end
|
98
159
|
end
|
99
160
|
|
100
161
|
def collection?
|
@@ -115,19 +176,24 @@ module Alba
|
|
115
176
|
# Set multiple attributes at once
|
116
177
|
#
|
117
178
|
# @param attrs [Array<String, Symbol>]
|
118
|
-
|
119
|
-
|
179
|
+
# @param options [Hash] option hash including `if` that is a condition to render these attributes
|
180
|
+
def attributes(*attrs, **options)
|
181
|
+
attrs.each do |attr_name|
|
182
|
+
attr = options[:if] ? [attr_name.to_sym, options[:if]] : attr_name.to_sym
|
183
|
+
@_attributes[attr_name.to_sym] = attr
|
184
|
+
end
|
120
185
|
end
|
121
186
|
|
122
187
|
# Set an attribute with the given block
|
123
188
|
#
|
124
189
|
# @param name [String, Symbol] key name
|
190
|
+
# @param options [Hash] option hash including `if` that is a condition to render
|
125
191
|
# @param block [Block] the block called during serialization
|
126
192
|
# @raise [ArgumentError] if block is absent
|
127
|
-
def attribute(name, &block)
|
193
|
+
def attribute(name, **options, &block)
|
128
194
|
raise ArgumentError, 'No block given in attribute method' unless block
|
129
195
|
|
130
|
-
@_attributes[name.to_sym] = block
|
196
|
+
@_attributes[name.to_sym] = options[:if] ? [block, options[:if]] : block
|
131
197
|
end
|
132
198
|
|
133
199
|
# Set One association
|
@@ -136,10 +202,13 @@ module Alba
|
|
136
202
|
# @param condition [Proc]
|
137
203
|
# @param resource [Class<Alba::Resource>]
|
138
204
|
# @param key [String, Symbol] used as key when given
|
205
|
+
# @param options [Hash] option hash including `if` that is a condition to render
|
139
206
|
# @param block [Block]
|
140
207
|
# @see Alba::One#initialize
|
141
|
-
def one(name, condition = nil, resource: nil, key: nil, &block)
|
142
|
-
|
208
|
+
def one(name, condition = nil, resource: nil, key: nil, **options, &block)
|
209
|
+
nesting = self.name&.rpartition('::')&.first
|
210
|
+
one = One.new(name: name, condition: condition, resource: resource, nesting: nesting, &block)
|
211
|
+
@_attributes[key&.to_sym || name.to_sym] = options[:if] ? [one, options[:if]] : one
|
143
212
|
end
|
144
213
|
alias has_one one
|
145
214
|
|
@@ -149,25 +218,27 @@ module Alba
|
|
149
218
|
# @param condition [Proc]
|
150
219
|
# @param resource [Class<Alba::Resource>]
|
151
220
|
# @param key [String, Symbol] used as key when given
|
221
|
+
# @param options [Hash] option hash including `if` that is a condition to render
|
152
222
|
# @param block [Block]
|
153
223
|
# @see Alba::Many#initialize
|
154
|
-
def many(name, condition = nil, resource: nil, key: nil, &block)
|
155
|
-
|
224
|
+
def many(name, condition = nil, resource: nil, key: nil, **options, &block)
|
225
|
+
nesting = self.name&.rpartition('::')&.first
|
226
|
+
many = Many.new(name: name, condition: condition, resource: resource, nesting: nesting, &block)
|
227
|
+
@_attributes[key&.to_sym || name.to_sym] = options[:if] ? [many, options[:if]] : many
|
156
228
|
end
|
157
229
|
alias has_many many
|
158
230
|
|
159
|
-
# Set serializer for the resource
|
160
|
-
#
|
161
|
-
# @param name [Alba::Serializer]
|
162
|
-
def serializer(name)
|
163
|
-
@_serializer = name <= Alba::Serializer ? name : nil
|
164
|
-
end
|
165
|
-
|
166
231
|
# Set key
|
167
232
|
#
|
168
233
|
# @param key [String, Symbol]
|
169
234
|
def key(key)
|
170
|
-
@_key = key.to_sym
|
235
|
+
@_key = key.respond_to?(:to_sym) ? key.to_sym : key
|
236
|
+
end
|
237
|
+
|
238
|
+
# Set key to true
|
239
|
+
#
|
240
|
+
def key!
|
241
|
+
@_key = true
|
171
242
|
end
|
172
243
|
|
173
244
|
# Delete attributes
|
@@ -179,6 +250,24 @@ module Alba
|
|
179
250
|
@_attributes.delete(attr_name.to_sym)
|
180
251
|
end
|
181
252
|
end
|
253
|
+
|
254
|
+
# Transform keys as specified type
|
255
|
+
#
|
256
|
+
# @param type [String, Symbol]
|
257
|
+
def transform_keys(type)
|
258
|
+
@_transform_keys = type.to_sym
|
259
|
+
end
|
260
|
+
|
261
|
+
# Set error handler
|
262
|
+
#
|
263
|
+
# @param [Symbol] handler
|
264
|
+
# @param [Block]
|
265
|
+
def on_error(handler = nil, &block)
|
266
|
+
raise ArgumentError, 'You cannot specify error handler with both Symbol and block' if handler && block
|
267
|
+
raise ArgumentError, 'You must specify error handler with either Symbol or block' unless handler || block
|
268
|
+
|
269
|
+
@_on_error = handler || block
|
270
|
+
end
|
182
271
|
end
|
183
272
|
end
|
184
273
|
end
|
data/lib/alba/version.rb
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:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- OKURA Masafumi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-04-23 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,25 +19,30 @@ 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
25
|
- ".yardopts"
|
26
|
+
- CHANGELOG.md
|
26
27
|
- CODE_OF_CONDUCT.md
|
27
28
|
- Gemfile
|
28
|
-
- Gemfile.lock
|
29
29
|
- LICENSE.txt
|
30
30
|
- README.md
|
31
31
|
- Rakefile
|
32
32
|
- alba.gemspec
|
33
|
+
- benchmark/local.rb
|
33
34
|
- bin/console
|
34
35
|
- bin/setup
|
36
|
+
- codecov.yml
|
37
|
+
- gemfiles/all.gemfile
|
38
|
+
- gemfiles/without_active_support.gemfile
|
39
|
+
- gemfiles/without_oj.gemfile
|
35
40
|
- lib/alba.rb
|
36
41
|
- lib/alba/association.rb
|
42
|
+
- lib/alba/key_transformer.rb
|
37
43
|
- lib/alba/many.rb
|
38
44
|
- lib/alba/one.rb
|
39
45
|
- lib/alba/resource.rb
|
40
|
-
- lib/alba/serializer.rb
|
41
46
|
- lib/alba/version.rb
|
42
47
|
- sider.yml
|
43
48
|
homepage: https://github.com/okuramasafumi/alba
|
@@ -46,7 +51,7 @@ licenses:
|
|
46
51
|
metadata:
|
47
52
|
homepage_uri: https://github.com/okuramasafumi/alba
|
48
53
|
source_code_uri: https://github.com/okuramasafumi/alba
|
49
|
-
changelog_uri: https://github.com/okuramasafumi/alba/CHANGELOG.md
|
54
|
+
changelog_uri: https://github.com/okuramasafumi/alba/blob/master/CHANGELOG.md
|
50
55
|
post_install_message:
|
51
56
|
rdoc_options: []
|
52
57
|
require_paths:
|
@@ -55,14 +60,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
55
60
|
requirements:
|
56
61
|
- - ">="
|
57
62
|
- !ruby/object:Gem::Version
|
58
|
-
version: 2.5.
|
63
|
+
version: 2.5.0
|
59
64
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
65
|
requirements:
|
61
66
|
- - ">="
|
62
67
|
- !ruby/object:Gem::Version
|
63
68
|
version: '0'
|
64
69
|
requirements: []
|
65
|
-
rubygems_version: 3.
|
70
|
+
rubygems_version: 3.2.16
|
66
71
|
signing_key:
|
67
72
|
specification_version: 4
|
68
73
|
summary: Alba is the fastest JSON serializer for Ruby.
|