alba 0.13.1 → 1.3.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/ISSUE_TEMPLATE/bug_report.md +26 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/dependabot.yml +26 -0
- data/.github/workflows/main.yml +10 -1
- data/.gitignore +3 -0
- data/.rubocop.yml +33 -2
- data/.yardopts +2 -0
- data/CHANGELOG.md +47 -0
- data/Gemfile +10 -4
- data/README.md +308 -45
- data/Rakefile +4 -1
- data/SECURITY.md +12 -0
- data/alba.gemspec +3 -3
- data/benchmark/collection.rb +392 -0
- data/benchmark/single_resource.rb +370 -0
- data/codecov.yml +8 -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 +57 -19
- data/lib/alba/association.rb +30 -7
- data/lib/alba/default_inflector.rb +36 -0
- data/lib/alba/key_transform_factory.rb +33 -0
- data/lib/alba/many.rb +7 -5
- data/lib/alba/one.rb +7 -5
- data/lib/alba/resource.rb +170 -63
- data/lib/alba/typed_attribute.rb +64 -0
- data/lib/alba/version.rb +1 -1
- data/sider.yml +2 -4
- metadata +21 -12
- data/Gemfile.lock +0 -92
- data/benchmark/local.rb +0 -198
- data/lib/alba/key_transformer.rb +0 -31
- data/lib/alba/serializer.rb +0 -75
data/codecov.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gem 'activesupport', require: false # For backend
|
4
|
+
gem 'ffaker', require: false # For testing
|
5
|
+
gem 'minitest', '~> 5.14' # For test
|
6
|
+
gem 'rake', '~> 13.0' # For test and automation
|
7
|
+
gem 'rubocop', '>= 0.79.0', require: false # For lint
|
8
|
+
gem 'rubocop-minitest', '~> 0.11.0', require: false # For lint
|
9
|
+
gem 'rubocop-performance', '~> 1.10.1', require: false # For lint
|
10
|
+
gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
|
11
|
+
gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
|
12
|
+
gem 'simplecov', '~> 0.21.0', require: false # For test coverage
|
13
|
+
gem 'simplecov-cobertura', require: false # For test coverage
|
14
|
+
gem 'yard', require: false
|
15
|
+
|
16
|
+
platforms :ruby do
|
17
|
+
gem 'oj', '~> 3.11', require: false # For backend
|
18
|
+
gem 'ruby-prof', require: false # For performance profiling
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gem 'minitest', '~> 5.14' # For test
|
4
|
+
gem 'rake', '~> 13.0' # For test and automation
|
5
|
+
gem 'rubocop', '>= 0.79.0', require: false # For lint
|
6
|
+
gem 'rubocop-minitest', '~> 0.11.0', require: false # For lint
|
7
|
+
gem 'rubocop-performance', '~> 1.10.1', require: false # For lint
|
8
|
+
gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
|
9
|
+
gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
|
10
|
+
gem 'simplecov', '~> 0.21.0', require: false # For test coverage
|
11
|
+
gem 'simplecov-cobertura', require: false # For test coverage
|
12
|
+
gem 'yard', require: false
|
13
|
+
|
14
|
+
platforms :ruby do
|
15
|
+
gem 'oj', '~> 3.11', require: false # For backend
|
16
|
+
gem 'ruby-prof', require: false # For performance profiling
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gem 'activesupport', require: false # For backend
|
4
|
+
gem 'minitest', '~> 5.14' # For test
|
5
|
+
gem 'rake', '~> 13.0' # For test and automation
|
6
|
+
gem 'rubocop', '>= 0.79.0', require: false # For lint
|
7
|
+
gem 'rubocop-minitest', '~> 0.11.0', require: false # For lint
|
8
|
+
gem 'rubocop-performance', '~> 1.10.1', require: false # For lint
|
9
|
+
gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
|
10
|
+
gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
|
11
|
+
gem 'simplecov', '~> 0.21.0', require: false # For test coverage
|
12
|
+
gem 'simplecov-cobertura', require: false # For test coverage
|
13
|
+
gem 'yard', require: false
|
14
|
+
|
15
|
+
platforms :ruby do
|
16
|
+
gem 'ruby-prof', require: false # For performance profiling
|
17
|
+
end
|
data/lib/alba.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require_relative 'alba/version'
|
2
|
-
require_relative 'alba/serializer'
|
3
2
|
require_relative 'alba/resource'
|
4
3
|
|
5
4
|
# Core module
|
@@ -10,9 +9,12 @@ module Alba
|
|
10
9
|
# Error class for backend which is not supported
|
11
10
|
class UnsupportedBackend < Error; end
|
12
11
|
|
12
|
+
# Error class for type which is not supported
|
13
|
+
class UnsupportedType < Error; end
|
14
|
+
|
13
15
|
class << self
|
14
|
-
attr_reader :backend, :encoder
|
15
|
-
attr_accessor :
|
16
|
+
attr_reader :backend, :encoder, :inferring, :_on_error, :transforming_root_key
|
17
|
+
attr_accessor :inflector
|
16
18
|
|
17
19
|
# Set the backend, which actually serializes object into JSON
|
18
20
|
#
|
@@ -28,25 +30,64 @@ module Alba
|
|
28
30
|
# Serialize the object with inline definitions
|
29
31
|
#
|
30
32
|
# @param object [Object] the object to be serialized
|
31
|
-
# @param
|
33
|
+
# @param key [Symbol]
|
32
34
|
# @param block [Block] resource block
|
33
35
|
# @return [String] serialized JSON string
|
34
36
|
# @raise [ArgumentError] if block is absent or `with` argument's type is wrong
|
35
|
-
def serialize(object,
|
37
|
+
def serialize(object, key: nil, &block)
|
36
38
|
raise ArgumentError, 'Block required' unless block
|
37
39
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
resource.
|
40
|
+
klass = Class.new
|
41
|
+
klass.include(Alba::Resource)
|
42
|
+
klass.class_eval(&block)
|
43
|
+
resource = klass.new(object)
|
44
|
+
resource.serialize(key: key)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Enable inference for key and resource name
|
48
|
+
def enable_inference!
|
49
|
+
begin
|
50
|
+
require 'active_support/inflector'
|
51
|
+
rescue LoadError
|
52
|
+
raise ::Alba::Error, 'To enable inference, please install `ActiveSupport` gem.'
|
53
|
+
end
|
54
|
+
@inferring = true
|
55
|
+
end
|
56
|
+
|
57
|
+
# Disable inference for key and resource name
|
58
|
+
def disable_inference!
|
59
|
+
@inferring = false
|
60
|
+
end
|
61
|
+
|
62
|
+
# Set error handler
|
63
|
+
#
|
64
|
+
# @param [Symbol] handler
|
65
|
+
# @param [Block]
|
66
|
+
def on_error(handler = nil, &block)
|
67
|
+
raise ArgumentError, 'You cannot specify error handler with both Symbol and block' if handler && block
|
68
|
+
raise ArgumentError, 'You must specify error handler with either Symbol or block' unless handler || block
|
69
|
+
|
70
|
+
@_on_error = handler || block
|
71
|
+
end
|
72
|
+
|
73
|
+
# Enable root key transformation
|
74
|
+
def enable_root_key_transformation!
|
75
|
+
@transforming_root_key = true
|
76
|
+
end
|
77
|
+
|
78
|
+
# Disable root key transformation
|
79
|
+
def disable_root_key_transformation!
|
80
|
+
@transforming_root_key = false
|
42
81
|
end
|
43
82
|
|
44
83
|
private
|
45
84
|
|
46
85
|
def set_encoder
|
47
86
|
@encoder = case @backend
|
48
|
-
when :oj
|
87
|
+
when :oj, :oj_strict
|
49
88
|
try_oj
|
89
|
+
when :oj_rails
|
90
|
+
try_oj(mode: :rails)
|
50
91
|
when :active_support
|
51
92
|
try_active_support
|
52
93
|
when nil, :default, :json
|
@@ -56,10 +97,11 @@ module Alba
|
|
56
97
|
end
|
57
98
|
end
|
58
99
|
|
59
|
-
def try_oj
|
100
|
+
def try_oj(mode: :strict)
|
60
101
|
require 'oj'
|
61
|
-
->(hash) { Oj.dump(hash, mode:
|
102
|
+
->(hash) { Oj.dump(hash, mode: mode) }
|
62
103
|
rescue LoadError
|
104
|
+
Kernel.warn '`Oj` is not installed, falling back to default JSON encoder.'
|
63
105
|
default_encoder
|
64
106
|
end
|
65
107
|
|
@@ -67,6 +109,7 @@ module Alba
|
|
67
109
|
require 'active_support/json'
|
68
110
|
->(hash) { ActiveSupport::JSON.encode(hash) }
|
69
111
|
rescue LoadError
|
112
|
+
Kernel.warn '`ActiveSupport` is not installed, falling back to default JSON encoder.'
|
70
113
|
default_encoder
|
71
114
|
end
|
72
115
|
|
@@ -76,14 +119,9 @@ module Alba
|
|
76
119
|
JSON.dump(hash)
|
77
120
|
end
|
78
121
|
end
|
79
|
-
|
80
|
-
def resource_class
|
81
|
-
@resource_class ||= begin
|
82
|
-
klass = Class.new
|
83
|
-
klass.include(Alba::Resource)
|
84
|
-
end
|
85
|
-
end
|
86
122
|
end
|
87
123
|
|
88
124
|
@encoder = default_encoder
|
125
|
+
@_on_error = :raise
|
126
|
+
@transforming_root_key = false # TODO: This will be true since 2.0
|
89
127
|
end
|
data/lib/alba/association.rb
CHANGED
@@ -2,30 +2,53 @@ 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, :name
|
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
|
-
def to_hash
|
19
|
-
:not_implemented
|
18
|
+
assign_resource(nesting)
|
20
19
|
end
|
21
20
|
|
22
21
|
private
|
23
22
|
|
23
|
+
def constantize(resource)
|
24
|
+
case resource # rubocop:disable Style/MissingElse
|
25
|
+
when Class
|
26
|
+
resource
|
27
|
+
when Symbol, String
|
28
|
+
Object.const_get(resource)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def assign_resource(nesting)
|
33
|
+
@resource = if @block
|
34
|
+
resource_class
|
35
|
+
elsif Alba.inferring
|
36
|
+
resource_class_with_nesting(nesting)
|
37
|
+
else
|
38
|
+
raise ArgumentError, 'When Alba.inferring is false, either resource or block is required'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
24
42
|
def resource_class
|
25
43
|
klass = Class.new
|
26
44
|
klass.include(Alba::Resource)
|
27
45
|
klass.class_eval(&@block)
|
28
46
|
klass
|
29
47
|
end
|
48
|
+
|
49
|
+
def resource_class_with_nesting(nesting)
|
50
|
+
const_parent = nesting.nil? ? Object : Object.const_get(nesting)
|
51
|
+
const_parent.const_get("#{ActiveSupport::Inflector.classify(@name)}Resource")
|
52
|
+
end
|
30
53
|
end
|
31
54
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Alba
|
2
|
+
# This module represents the inflector, which is used by default
|
3
|
+
module DefaultInflector
|
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
|
+
# Camelizes a key
|
13
|
+
#
|
14
|
+
# @params key [String] key to be camelized
|
15
|
+
# @return [String] camelized key
|
16
|
+
def camelize(key)
|
17
|
+
ActiveSupport::Inflector.camelize(key)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Camelizes a key, 1st letter lowercase
|
21
|
+
#
|
22
|
+
# @params key [String] key to be camelized
|
23
|
+
# @return [String] camelized key
|
24
|
+
def camelize_lower(key)
|
25
|
+
ActiveSupport::Inflector.camelize(key, false)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Dasherizes a key
|
29
|
+
#
|
30
|
+
# @params key [String] key to be dasherized
|
31
|
+
# @return [String] dasherized key
|
32
|
+
def dasherize(key)
|
33
|
+
ActiveSupport::Inflector.dasherize(key)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Alba
|
2
|
+
# This module creates key transform functions
|
3
|
+
module KeyTransformFactory
|
4
|
+
class << self
|
5
|
+
# Create key transform function for given transform_type
|
6
|
+
#
|
7
|
+
# @params transform_type [Symbol] transform type
|
8
|
+
# @return [Proc] transform function
|
9
|
+
# @raise [Alba::Error] when transform_type is not supported
|
10
|
+
def create(transform_type)
|
11
|
+
case transform_type
|
12
|
+
when :camel
|
13
|
+
->(key) { _inflector.camelize(key) }
|
14
|
+
when :lower_camel
|
15
|
+
->(key) { _inflector.camelize_lower(key) }
|
16
|
+
when :dash
|
17
|
+
->(key) { _inflector.dasherize(key) }
|
18
|
+
else
|
19
|
+
raise ::Alba::Error, "Unknown transform_type: #{transform_type}. Supported transform_type are :camel, :lower_camel and :dash."
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def _inflector
|
26
|
+
Alba.inflector || begin
|
27
|
+
require_relative './default_inflector'
|
28
|
+
Alba::DefaultInflector
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/alba/many.rb
CHANGED
@@ -6,14 +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
|
-
return if
|
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?
|
15
16
|
|
16
|
-
|
17
|
+
@resource = constantize(@resource)
|
18
|
+
@resource.new(@object, params: params, within: within).to_hash
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
data/lib/alba/one.rb
CHANGED
@@ -6,14 +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
|
-
return if object.nil?
|
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?
|
15
16
|
|
16
|
-
@resource
|
17
|
+
@resource = constantize(@resource)
|
18
|
+
@resource.new(object, params: params, within: within).to_hash
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
data/lib/alba/resource.rb
CHANGED
@@ -1,15 +1,19 @@
|
|
1
|
-
require_relative 'serializer'
|
2
1
|
require_relative 'one'
|
3
2
|
require_relative 'many'
|
3
|
+
require_relative 'key_transform_factory'
|
4
|
+
require_relative 'typed_attribute'
|
4
5
|
|
5
6
|
module Alba
|
6
7
|
# This module represents what should be serialized
|
7
8
|
module Resource
|
8
9
|
# @!parse include InstanceMethods
|
9
10
|
# @!parse extend ClassMethods
|
10
|
-
DSLS = {_attributes: {},
|
11
|
+
DSLS = {_attributes: {}, _key: nil, _transform_key_function: nil, _transforming_root_key: false, _on_error: nil}.freeze
|
11
12
|
private_constant :DSLS
|
12
13
|
|
14
|
+
WITHIN_DEFAULT = Object.new.freeze
|
15
|
+
private_constant :WITHIN_DEFAULT
|
16
|
+
|
13
17
|
# @private
|
14
18
|
def self.included(base)
|
15
19
|
super
|
@@ -25,32 +29,26 @@ module Alba
|
|
25
29
|
|
26
30
|
# Instance methods
|
27
31
|
module InstanceMethods
|
28
|
-
attr_reader :object, :
|
32
|
+
attr_reader :object, :params
|
29
33
|
|
30
34
|
# @param object [Object] the object to be serialized
|
31
35
|
# @param params [Hash] user-given Hash for arbitrary data
|
32
|
-
|
36
|
+
# @param within [Hash] determines what associations to be serialized. If not set, it serializes all associations.
|
37
|
+
def initialize(object, params: {}, within: WITHIN_DEFAULT)
|
33
38
|
@object = object
|
34
39
|
@params = params.freeze
|
40
|
+
@within = within
|
35
41
|
DSLS.each_key { |name| instance_variable_set("@#{name}", self.class.public_send(name)) }
|
36
42
|
end
|
37
43
|
|
38
|
-
#
|
44
|
+
# Serialize object into JSON string
|
39
45
|
#
|
40
|
-
# @param
|
46
|
+
# @param key [Symbol]
|
41
47
|
# @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
|
48
|
+
def serialize(key: nil)
|
49
|
+
key = key.nil? ? _key : key
|
50
|
+
hash = key && key != '' ? {key.to_s => serializable_hash} : serializable_hash
|
51
|
+
Alba.encoder.call(hash)
|
54
52
|
end
|
55
53
|
|
56
54
|
# A Hash for serialization
|
@@ -61,54 +59,121 @@ module Alba
|
|
61
59
|
end
|
62
60
|
alias to_hash serializable_hash
|
63
61
|
|
64
|
-
|
65
|
-
|
66
|
-
|
62
|
+
private
|
63
|
+
|
64
|
+
# @return [String]
|
65
|
+
def _key
|
66
|
+
return @_key.to_s unless @_key == true && Alba.inferring
|
67
|
+
|
68
|
+
transforming_root_key? ? transform_key(key_from_resource_name) : key_from_resource_name
|
67
69
|
end
|
68
70
|
|
69
|
-
|
71
|
+
def key_from_resource_name
|
72
|
+
collection? ? resource_name.pluralize : resource_name
|
73
|
+
end
|
74
|
+
|
75
|
+
def resource_name
|
76
|
+
self.class.name.demodulize.delete_suffix('Resource').underscore
|
77
|
+
end
|
78
|
+
|
79
|
+
def transforming_root_key?
|
80
|
+
@_transforming_root_key.nil? ? Alba.transforming_root_key : @_transforming_root_key
|
81
|
+
end
|
70
82
|
|
71
|
-
# rubocop:disable Style/MethodCalledOnDoEndBlock
|
72
83
|
def converter
|
73
|
-
lambda do |
|
74
|
-
@_attributes.map do |key, attribute|
|
75
|
-
|
76
|
-
|
84
|
+
lambda do |object|
|
85
|
+
arrays = @_attributes.map do |key, attribute|
|
86
|
+
key_and_attribute_body_from(object, key, attribute)
|
87
|
+
rescue ::Alba::Error, FrozenError, TypeError
|
88
|
+
raise
|
89
|
+
rescue StandardError => e
|
90
|
+
handle_error(e, object, key, attribute)
|
91
|
+
end
|
92
|
+
arrays.reject(&:empty?).to_h
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def key_and_attribute_body_from(object, key, attribute)
|
97
|
+
key = transform_key(key)
|
98
|
+
if attribute.is_a?(Array) # Conditional
|
99
|
+
conditional_attribute(object, key, attribute)
|
100
|
+
else
|
101
|
+
[key, fetch_attribute(object, attribute)]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def conditional_attribute(object, key, attribute)
|
106
|
+
condition = attribute.last
|
107
|
+
arity = condition.arity
|
108
|
+
return [] if arity <= 1 && !instance_exec(object, &condition)
|
109
|
+
|
110
|
+
fetched_attribute = fetch_attribute(object, attribute.first)
|
111
|
+
attr = if attribute.first.is_a?(Alba::Association)
|
112
|
+
attribute.first.object
|
113
|
+
else
|
114
|
+
fetched_attribute
|
115
|
+
end
|
116
|
+
return [] if arity >= 2 && !instance_exec(object, attr, &condition)
|
117
|
+
|
118
|
+
[key, fetched_attribute]
|
119
|
+
end
|
120
|
+
|
121
|
+
def handle_error(error, object, key, attribute)
|
122
|
+
on_error = @_on_error || Alba._on_error
|
123
|
+
case on_error
|
124
|
+
when :raise, nil
|
125
|
+
raise
|
126
|
+
when :nullify
|
127
|
+
[key, nil]
|
128
|
+
when :ignore
|
129
|
+
[]
|
130
|
+
when Proc
|
131
|
+
on_error.call(error, object, key, attribute, self.class)
|
132
|
+
else
|
133
|
+
raise ::Alba::Error, "Unknown on_error: #{on_error.inspect}"
|
77
134
|
end
|
78
135
|
end
|
79
|
-
# rubocop:enable Style/MethodCalledOnDoEndBlock
|
80
136
|
|
81
137
|
# Override this method to supply custom key transform method
|
82
138
|
def transform_key(key)
|
83
|
-
return key
|
139
|
+
return key if @_transform_key_function.nil?
|
84
140
|
|
85
|
-
|
86
|
-
KeyTransformer.transform(key, @_transform_keys)
|
141
|
+
@_transform_key_function.call(key.to_s)
|
87
142
|
end
|
88
143
|
|
89
|
-
def fetch_attribute(
|
144
|
+
def fetch_attribute(object, attribute)
|
90
145
|
case attribute
|
91
146
|
when Symbol
|
92
|
-
|
147
|
+
object.public_send attribute
|
93
148
|
when Proc
|
94
|
-
instance_exec(
|
149
|
+
instance_exec(object, &attribute)
|
95
150
|
when Alba::One, Alba::Many
|
96
|
-
attribute.
|
151
|
+
within = check_within(attribute.name.to_sym)
|
152
|
+
return unless within
|
153
|
+
|
154
|
+
attribute.to_hash(object, params: params, within: within)
|
155
|
+
when TypedAttribute
|
156
|
+
attribute.value(object)
|
97
157
|
else
|
98
158
|
raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
|
99
159
|
end
|
100
160
|
end
|
101
161
|
|
102
|
-
def
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
162
|
+
def check_within(association_name)
|
163
|
+
case @within
|
164
|
+
when WITHIN_DEFAULT # Default value, doesn't check within tree
|
165
|
+
WITHIN_DEFAULT
|
166
|
+
when Hash # Traverse within tree
|
167
|
+
@within.fetch(association_name, nil)
|
168
|
+
when Array # within tree ends with Array
|
169
|
+
@within.find { |item| item.to_sym == association_name }
|
170
|
+
when Symbol # within tree could end with Symbol
|
171
|
+
@within == association_name
|
172
|
+
when nil, true, false # In these cases, Alba stops serialization here.
|
173
|
+
false
|
174
|
+
else
|
175
|
+
raise Alba::Error, "Unknown type for within option: #{@within.class}"
|
176
|
+
end
|
112
177
|
end
|
113
178
|
|
114
179
|
def collection?
|
@@ -129,19 +194,43 @@ module Alba
|
|
129
194
|
# Set multiple attributes at once
|
130
195
|
#
|
131
196
|
# @param attrs [Array<String, Symbol>]
|
132
|
-
|
133
|
-
|
197
|
+
# @param if [Boolean] condition to decide if it should render these attributes
|
198
|
+
# @param attrs_with_types [Hash] attributes with name in its key and type and optional type converter in its value
|
199
|
+
def attributes(*attrs, if: nil, **attrs_with_types) # rubocop:disable Naming/MethodParameterName
|
200
|
+
if_value = binding.local_variable_get(:if)
|
201
|
+
assign_attributes(attrs, if_value)
|
202
|
+
assign_attributes_with_types(attrs_with_types, if_value)
|
203
|
+
end
|
204
|
+
|
205
|
+
def assign_attributes(attrs, if_value)
|
206
|
+
attrs.each do |attr_name|
|
207
|
+
attr = if_value ? [attr_name.to_sym, if_value] : attr_name.to_sym
|
208
|
+
@_attributes[attr_name.to_sym] = attr
|
209
|
+
end
|
210
|
+
end
|
211
|
+
private :assign_attributes
|
212
|
+
|
213
|
+
def assign_attributes_with_types(attrs_with_types, if_value)
|
214
|
+
attrs_with_types.each do |attr_name, type_and_converter|
|
215
|
+
attr_name = attr_name.to_sym
|
216
|
+
type, type_converter = type_and_converter
|
217
|
+
typed_attr = TypedAttribute.new(name: attr_name, type: type, converter: type_converter)
|
218
|
+
attr = if_value ? [typed_attr, if_value] : typed_attr
|
219
|
+
@_attributes[attr_name] = attr
|
220
|
+
end
|
134
221
|
end
|
222
|
+
private :assign_attributes_with_types
|
135
223
|
|
136
224
|
# Set an attribute with the given block
|
137
225
|
#
|
138
226
|
# @param name [String, Symbol] key name
|
227
|
+
# @param options [Hash] option hash including `if` that is a condition to render
|
139
228
|
# @param block [Block] the block called during serialization
|
140
229
|
# @raise [ArgumentError] if block is absent
|
141
|
-
def attribute(name, &block)
|
230
|
+
def attribute(name, **options, &block)
|
142
231
|
raise ArgumentError, 'No block given in attribute method' unless block
|
143
232
|
|
144
|
-
@_attributes[name.to_sym] = block
|
233
|
+
@_attributes[name.to_sym] = options[:if] ? [block, options[:if]] : block
|
145
234
|
end
|
146
235
|
|
147
236
|
# Set One association
|
@@ -150,10 +239,13 @@ module Alba
|
|
150
239
|
# @param condition [Proc]
|
151
240
|
# @param resource [Class<Alba::Resource>]
|
152
241
|
# @param key [String, Symbol] used as key when given
|
242
|
+
# @param options [Hash] option hash including `if` that is a condition to render
|
153
243
|
# @param block [Block]
|
154
244
|
# @see Alba::One#initialize
|
155
|
-
def one(name, condition = nil, resource: nil, key: nil, &block)
|
156
|
-
|
245
|
+
def one(name, condition = nil, resource: nil, key: nil, **options, &block)
|
246
|
+
nesting = self.name&.rpartition('::')&.first
|
247
|
+
one = One.new(name: name, condition: condition, resource: resource, nesting: nesting, &block)
|
248
|
+
@_attributes[key&.to_sym || name.to_sym] = options[:if] ? [one, options[:if]] : one
|
157
249
|
end
|
158
250
|
alias has_one one
|
159
251
|
|
@@ -163,25 +255,27 @@ module Alba
|
|
163
255
|
# @param condition [Proc]
|
164
256
|
# @param resource [Class<Alba::Resource>]
|
165
257
|
# @param key [String, Symbol] used as key when given
|
258
|
+
# @param options [Hash] option hash including `if` that is a condition to render
|
166
259
|
# @param block [Block]
|
167
260
|
# @see Alba::Many#initialize
|
168
|
-
def many(name, condition = nil, resource: nil, key: nil, &block)
|
169
|
-
|
261
|
+
def many(name, condition = nil, resource: nil, key: nil, **options, &block)
|
262
|
+
nesting = self.name&.rpartition('::')&.first
|
263
|
+
many = Many.new(name: name, condition: condition, resource: resource, nesting: nesting, &block)
|
264
|
+
@_attributes[key&.to_sym || name.to_sym] = options[:if] ? [many, options[:if]] : many
|
170
265
|
end
|
171
266
|
alias has_many many
|
172
267
|
|
173
|
-
# Set serializer for the resource
|
174
|
-
#
|
175
|
-
# @param name [Alba::Serializer]
|
176
|
-
def serializer(name)
|
177
|
-
@_serializer = name <= Alba::Serializer ? name : nil
|
178
|
-
end
|
179
|
-
|
180
268
|
# Set key
|
181
269
|
#
|
182
270
|
# @param key [String, Symbol]
|
183
271
|
def key(key)
|
184
|
-
@_key = key.to_sym
|
272
|
+
@_key = key.respond_to?(:to_sym) ? key.to_sym : key
|
273
|
+
end
|
274
|
+
|
275
|
+
# Set key to true
|
276
|
+
#
|
277
|
+
def key!
|
278
|
+
@_key = true
|
185
279
|
end
|
186
280
|
|
187
281
|
# Delete attributes
|
@@ -197,8 +291,21 @@ module Alba
|
|
197
291
|
# Transform keys as specified type
|
198
292
|
#
|
199
293
|
# @param type [String, Symbol]
|
200
|
-
|
201
|
-
|
294
|
+
# @param root [Boolean] decides if root key also should be transformed
|
295
|
+
def transform_keys(type, root: nil)
|
296
|
+
@_transform_key_function = KeyTransformFactory.create(type.to_sym)
|
297
|
+
@_transforming_root_key = root
|
298
|
+
end
|
299
|
+
|
300
|
+
# Set error handler
|
301
|
+
#
|
302
|
+
# @param [Symbol] handler
|
303
|
+
# @param [Block]
|
304
|
+
def on_error(handler = nil, &block)
|
305
|
+
raise ArgumentError, 'You cannot specify error handler with both Symbol and block' if handler && block
|
306
|
+
raise ArgumentError, 'You must specify error handler with either Symbol or block' unless handler || block
|
307
|
+
|
308
|
+
@_on_error = handler || block
|
202
309
|
end
|
203
310
|
end
|
204
311
|
end
|