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.
data/codecov.yml ADDED
@@ -0,0 +1,8 @@
1
+ coverage:
2
+ status:
3
+ project:
4
+ default:
5
+ informational: true
6
+ patch:
7
+ default:
8
+ target: 90%
@@ -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 :default_serializer
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 with [nil, Proc, Alba::Serializer] selializer
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, with: nil, &block)
37
+ def serialize(object, key: nil, &block)
36
38
  raise ArgumentError, 'Block required' unless block
37
39
 
38
- resource_class.class_eval(&block)
39
- resource = resource_class.new(object)
40
- with ||= @default_serializer
41
- resource.serialize(with: with)
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: :strict) }
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
@@ -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 || resource_class
14
- raise ArgumentError, 'resource or block is required' if @resource.nil? && @block.nil?
15
- end
15
+ @resource = resource
16
+ return if @resource
16
17
 
17
- # @abstract
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
- objects = target.public_send(@name)
13
- objects = @condition.call(objects, params) if @condition
14
- return if objects.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
- objects.map { |o| @resource.new(o, params: params).to_hash }
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.new(object, params: params).to_hash
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: {}, _serializer: nil, _key: nil, _transform_keys: nil}.freeze
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, :_key, :params
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
- def initialize(object, params: {})
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
- # Get serializer with `with` argument and serialize self with it
44
+ # Serialize object into JSON string
39
45
  #
40
- # @param with [nil, Proc, Alba::Serializer] selializer
46
+ # @param key [Symbol]
41
47
  # @return [String] serialized JSON string
42
- def serialize(with: nil)
43
- serializer = case with
44
- when nil
45
- @_serializer || empty_serializer
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
- # @return [Symbol]
65
- def key
66
- @_key || self.class.name.delete_suffix('Resource').downcase.gsub(/:{2}/, '_').to_sym
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
- private
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 |resource|
74
- @_attributes.map do |key, attribute|
75
- [transform_key(key), fetch_attribute(resource, attribute)]
76
- end.to_h
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 unless @_transform_keys
139
+ return key if @_transform_key_function.nil?
84
140
 
85
- require_relative 'key_transformer'
86
- KeyTransformer.transform(key, @_transform_keys)
141
+ @_transform_key_function.call(key.to_s)
87
142
  end
88
143
 
89
- def fetch_attribute(resource, attribute)
144
+ def fetch_attribute(object, attribute)
90
145
  case attribute
91
146
  when Symbol
92
- resource.public_send attribute
147
+ object.public_send attribute
93
148
  when Proc
94
- instance_exec(resource, &attribute)
149
+ instance_exec(object, &attribute)
95
150
  when Alba::One, Alba::Many
96
- attribute.to_hash(resource, params: params)
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 empty_serializer
103
- klass = Class.new
104
- klass.include Alba::Serializer
105
- klass
106
- end
107
-
108
- def inline_extended_serializer(with)
109
- klass = empty_serializer
110
- klass.class_eval(&with)
111
- klass
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
- def attributes(*attrs)
133
- attrs.each { |attr_name| @_attributes[attr_name.to_sym] = attr_name.to_sym }
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
- @_attributes[key&.to_sym || name.to_sym] = One.new(name: name, condition: condition, resource: resource, &block)
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
- @_attributes[key&.to_sym || name.to_sym] = Many.new(name: name, condition: condition, resource: resource, &block)
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
- def transform_keys(type)
201
- @_transform_keys = type.to_sym
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