alba 1.5.0 → 2.0.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/lib/alba/resource.rb CHANGED
@@ -1,15 +1,16 @@
1
- require_relative 'one'
2
- require_relative 'many'
3
- require_relative 'key_transform_factory'
1
+ require_relative 'association'
2
+ require_relative 'conditional_attribute'
4
3
  require_relative 'typed_attribute'
4
+ require_relative 'nested_attribute'
5
5
  require_relative 'deprecation'
6
+ require_relative 'layout'
6
7
 
7
8
  module Alba
8
9
  # This module represents what should be serialized
9
10
  module Resource
10
11
  # @!parse include InstanceMethods
11
12
  # @!parse extend ClassMethods
12
- DSLS = {_attributes: {}, _key: nil, _key_for_collection: nil, _meta: nil, _transform_key_function: nil, _transforming_root_key: false, _on_error: nil, _on_nil: nil, _layout: nil}.freeze # rubocop:disable Layout/LineLength
13
+ DSLS = {_attributes: {}, _key: nil, _key_for_collection: nil, _meta: nil, _transform_type: :none, _transforming_root_key: false, _on_error: nil, _on_nil: nil, _layout: nil, _collection_key: nil}.freeze # rubocop:disable Layout/LineLength
13
14
  private_constant :DSLS
14
15
 
15
16
  WITHIN_DEFAULT = Object.new.freeze
@@ -37,175 +38,235 @@ module Alba
37
38
  # @param within [Object, nil, false, true] determines what associations to be serialized. If not set, it serializes all associations.
38
39
  def initialize(object, params: {}, within: WITHIN_DEFAULT)
39
40
  @object = object
40
- @params = params.freeze
41
+ @params = params
41
42
  @within = within
42
- DSLS.each_key { |name| instance_variable_set("@#{name}", self.class.public_send(name)) }
43
+ @method_existence = {} # Cache for `respond_to?` result
44
+ DSLS.each_key { |name| instance_variable_set("@#{name}", self.class.__send__(name)) }
43
45
  end
44
46
 
45
47
  # Serialize object into JSON string
46
48
  #
47
- # @param key [Symbol, nil, true] DEPRECATED, use root_key instead
48
49
  # @param root_key [Symbol, nil, true]
49
50
  # @param meta [Hash] metadata for this seialization
50
51
  # @return [String] serialized JSON string
51
- def serialize(key: nil, root_key: nil, meta: {})
52
- Alba::Deprecation.warn '`key` option to `serialize` method is deprecated, use `root_key` instead.' if key
53
- key = key.nil? && root_key.nil? ? fetch_key : root_key || key
54
- hash = if key && key != ''
55
- h = {key.to_s => serializable_hash}
56
- hash_with_metadata(h, meta)
57
- else
58
- serializable_hash
59
- end
60
- serialize_with(hash)
61
- end
62
- alias to_json serialize
52
+ def serialize(root_key: nil, meta: {})
53
+ serialize_with(as_json(root_key: root_key, meta: meta))
54
+ end
55
+
56
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.0')
57
+ # For Rails compatibility
58
+ # The first options is a dummy parameter but required
59
+ # You can pass empty Hash if you don't want to pass any arguments
60
+ #
61
+ # @see #serialize
62
+ # @see https://github.com/rails/rails/blob/7-0-stable/actionpack/lib/action_controller/metal/renderers.rb#L156
63
+ def to_json(options, root_key: nil, meta: {})
64
+ _to_json(root_key, meta, options)
65
+ end
66
+ else
67
+ # For Rails compatibility
68
+ # The first options is a dummy parameter
69
+ #
70
+ # @see #serialize
71
+ # @see https://github.com/rails/rails/blob/7-0-stable/actionpack/lib/action_controller/metal/renderers.rb#L156
72
+ def to_json(options = {}, root_key: nil, meta: {})
73
+ _to_json(root_key, meta, options)
74
+ end
75
+ end
76
+
77
+ # Returns a Hash correspondng {Resource#serialize}
78
+ #
79
+ # @param root_key [Symbol, nil, true]
80
+ # @param meta [Hash] metadata for this seialization
81
+ # @param symbolize_root_key [Boolean] determines if root key should be symbolized
82
+ # @return [Hash]
83
+ def as_json(root_key: nil, meta: {})
84
+ key = root_key.nil? ? fetch_key : root_key.to_s
85
+ if key && !key.empty?
86
+ h = {key => serializable_hash}
87
+ hash_with_metadata(h, meta)
88
+ else
89
+ serializable_hash
90
+ end
91
+ end
63
92
 
64
93
  # A Hash for serialization
65
94
  #
66
95
  # @return [Hash]
67
96
  def serializable_hash
68
- collection? ? @object.map(&converter) : converter.call(@object)
97
+ collection? ? serializable_hash_for_collection : converter.call(@object)
69
98
  end
70
- alias to_hash serializable_hash
99
+ alias to_h serializable_hash
71
100
 
72
101
  private
73
102
 
74
- attr_reader :serialized_json # Mainly for layout
75
-
76
103
  def encode(hash)
77
104
  Alba.encoder.call(hash)
78
105
  end
79
106
 
80
- def serialize_with(hash)
81
- @serialized_json = encode(hash)
82
- case @_layout
83
- when String # file
84
- ERB.new(File.read(@_layout)).result(binding)
85
- when Proc # inline
86
- inline = instance_eval(&@_layout)
87
- inline.is_a?(Hash) ? encode(inline) : inline
88
- else # no layout
89
- @serialized_json
107
+ def _to_json(root_key, meta, options)
108
+ options.reject! { |k, _| %i[layout prefixes template status].include?(k) } # Rails specific guard
109
+ # TODO: use `filter_map` after dropping support of Ruby 2.6
110
+ names = options.map { |k, v| k unless v.nil? }
111
+ names.compact!
112
+ unless names.empty?
113
+ names.sort!
114
+ names.map! { |s| "\"#{s}\"" }
115
+ message = "You passed #{names.join(', ')} options but ignored. Please refer to the document: https://github.com/okuramasafumi/alba/blob/main/docs/rails.md"
116
+ Kernel.warn(message)
90
117
  end
118
+ serialize(root_key: root_key, meta: meta)
119
+ end
120
+
121
+ def serialize_with(hash)
122
+ serialized_json = encode(hash)
123
+ return serialized_json unless @_layout
124
+
125
+ @_layout.serialize(resource: self, serialized_json: serialized_json, binding: binding)
91
126
  end
92
127
 
93
128
  def hash_with_metadata(hash, meta)
94
- base = @_meta ? instance_eval(&@_meta) : {}
95
- metadata = base.merge(meta)
96
- hash[:meta] = metadata unless metadata.empty?
129
+ return hash if meta.empty? && @_meta.nil?
130
+
131
+ metadata = @_meta ? instance_eval(&@_meta).merge(meta) : meta
132
+ hash[:meta] = metadata
97
133
  hash
98
134
  end
99
135
 
136
+ def serializable_hash_for_collection
137
+ if @_collection_key
138
+ @object.to_h { |item| [item.public_send(@_collection_key).to_s, converter.call(item)] }
139
+ else
140
+ @object.each_with_object([], &collection_converter)
141
+ end
142
+ end
143
+
144
+ # @return [String]
100
145
  def fetch_key
101
- collection? ? _key_for_collection : _key
146
+ k = collection? ? _key_for_collection : _key
147
+ transforming_root_key? ? transform_key(k) : k
102
148
  end
103
149
 
104
150
  def _key_for_collection
105
- return @_key_for_collection.to_s unless @_key_for_collection == true && Alba.inferring
106
-
107
- key = resource_name.pluralize
108
- transforming_root_key? ? transform_key(key) : key
151
+ if Alba.inferring
152
+ @_key_for_collection == true ? resource_name(pluralized: true) : @_key_for_collection.to_s
153
+ else
154
+ @_key_for_collection == true ? raise_root_key_inference_error : @_key_for_collection.to_s
155
+ end
109
156
  end
110
157
 
111
158
  # @return [String]
112
159
  def _key
113
- return @_key.to_s unless @_key == true && Alba.inferring
160
+ if Alba.inferring
161
+ @_key == true ? resource_name(pluralized: false) : @_key.to_s
162
+ else
163
+ @_key == true ? raise_root_key_inference_error : @_key.to_s
164
+ end
165
+ end
114
166
 
115
- transforming_root_key? ? transform_key(resource_name) : resource_name
167
+ def resource_name(pluralized: false)
168
+ class_name = self.class.name
169
+ inflector = Alba.inflector
170
+ name = inflector.demodulize(class_name).delete_suffix('Resource')
171
+ underscore_name = inflector.underscore(name)
172
+ pluralized ? inflector.pluralize(underscore_name) : underscore_name
116
173
  end
117
174
 
118
- def resource_name
119
- self.class.name.demodulize.delete_suffix('Resource').underscore
175
+ def raise_root_key_inference_error
176
+ raise Alba::Error, 'You must call Alba.enable_inference! to set root_key to true for inferring root key.'
120
177
  end
121
178
 
122
179
  def transforming_root_key?
123
- @_transforming_root_key.nil? ? Alba.transforming_root_key : @_transforming_root_key
180
+ @_transforming_root_key
124
181
  end
125
182
 
126
183
  def converter
127
184
  lambda do |object|
128
- arrays = @_attributes.map do |key, attribute|
129
- key_and_attribute_body_from(object, key, attribute)
130
- rescue ::Alba::Error, FrozenError, TypeError
131
- raise
132
- rescue StandardError => e
133
- handle_error(e, object, key, attribute)
134
- end
135
- arrays.reject(&:empty?).to_h
185
+ attributes_to_hash(object, {})
136
186
  end
137
187
  end
138
188
 
139
- def key_and_attribute_body_from(object, key, attribute)
140
- key = transform_key(key)
141
- if attribute.is_a?(Array) # Conditional
142
- conditional_attribute(object, key, attribute)
143
- else
144
- fetched_attribute = fetch_attribute(object, key, attribute)
145
- [key, fetched_attribute]
189
+ def collection_converter
190
+ lambda do |object, a|
191
+ a << {}
192
+ h = a.last
193
+ attributes_to_hash(object, h)
194
+ a
146
195
  end
147
196
  end
148
197
 
149
- def conditional_attribute(object, key, attribute)
150
- condition = attribute.last
151
- if condition.is_a?(Proc)
152
- conditional_attribute_with_proc(object, key, attribute.first, condition)
153
- else
154
- conditional_attribute_with_symbol(object, key, attribute.first, condition)
198
+ def attributes_to_hash(object, hash)
199
+ attributes.each do |key, attribute|
200
+ set_key_and_attribute_body_from(object, key, attribute, hash)
201
+ rescue ::Alba::Error, FrozenError, TypeError
202
+ raise
203
+ rescue StandardError => e
204
+ handle_error(e, object, key, attribute, hash)
155
205
  end
206
+ hash
156
207
  end
157
208
 
158
- def conditional_attribute_with_proc(object, key, attribute, condition)
159
- arity = condition.arity
160
- # We can return early to skip fetch_attribute
161
- return [] if arity <= 1 && !instance_exec(object, &condition)
162
-
163
- fetched_attribute = fetch_attribute(object, key, attribute)
164
- attr = attribute.is_a?(Alba::Association) ? attribute.object : fetched_attribute
165
- return [] if arity >= 2 && !instance_exec(object, attr, &condition)
166
-
167
- [key, fetched_attribute]
168
- end
169
-
170
- def conditional_attribute_with_symbol(object, key, attribute, condition)
171
- return [] unless __send__(condition)
172
-
173
- [key, fetch_attribute(object, key, attribute)]
209
+ # This is default behavior for getting attributes for serialization
210
+ # Override this method to filter certain attributes
211
+ def attributes
212
+ @_attributes
174
213
  end
175
214
 
176
- def handle_error(error, object, key, attribute)
177
- on_error = @_on_error || Alba._on_error
178
- case on_error
179
- when :raise, nil then raise
180
- when :nullify then [key, nil]
181
- when :ignore then []
182
- when Proc then on_error.call(error, object, key, attribute, self.class)
183
- else
184
- raise ::Alba::Error, "Unknown on_error: #{on_error.inspect}"
215
+ def set_key_and_attribute_body_from(object, key, attribute, hash)
216
+ key = transform_key(key)
217
+ value = fetch_attribute(object, key, attribute)
218
+ hash[key] = value unless value == ConditionalAttribute::CONDITION_UNMET
219
+ end
220
+
221
+ def handle_error(error, object, key, attribute, hash)
222
+ on_error = @_on_error || :raise
223
+ case on_error # rubocop:disable Style/MissingElse
224
+ when :raise, nil then raise(error)
225
+ when :nullify then hash[key] = nil
226
+ when :ignore then nil
227
+ when Proc
228
+ key, value = on_error.call(error, object, key, attribute, self.class)
229
+ hash[key] = value
185
230
  end
186
231
  end
187
232
 
188
- # Override this method to supply custom key transform method
189
- def transform_key(key)
190
- return key if @_transform_key_function.nil?
233
+ # @return [Symbol]
234
+ def transform_key(key) # rubocop:disable Metrics/CyclomaticComplexity
235
+ key = key.to_s
236
+ return key if @_transform_type == :none || key.empty? # We can skip transformation
191
237
 
192
- @_transform_key_function.call(key.to_s)
238
+ inflector = Alba.inflector
239
+ raise Alba::Error, 'Inflector is nil. You can set inflector with `Alba.enable_inference!(with: :active_support)` for example.' unless inflector
240
+
241
+ case @_transform_type # rubocop:disable Style/MissingElse
242
+ when :camel then inflector.camelize(key)
243
+ when :lower_camel then inflector.camelize_lower(key)
244
+ when :dash then inflector.dasherize(key)
245
+ when :snake then inflector.underscore(key)
246
+ end
193
247
  end
194
248
 
195
- def fetch_attribute(object, key, attribute)
249
+ def fetch_attribute(object, key, attribute) # rubocop:disable Metrics/CyclomaticComplexity
196
250
  value = case attribute
197
- when Symbol then object.public_send attribute
251
+ when Symbol then fetch_attribute_from_object_and_resource(object, attribute)
198
252
  when Proc then instance_exec(object, &attribute)
199
- when Alba::One, Alba::Many then yield_if_within(attribute.name.to_sym) { |within| attribute.to_hash(object, params: params, within: within) }
200
- when TypedAttribute then attribute.value(object)
253
+ when Alba::Association then yield_if_within(attribute.name.to_sym) { |within| attribute.to_h(object, params: params, within: within) }
254
+ when TypedAttribute, NestedAttribute then attribute.value(object)
255
+ when ConditionalAttribute then attribute.with_passing_condition(resource: self, object: object) { |attr| fetch_attribute(object, key, attr) }
201
256
  else
202
257
  raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
203
258
  end
204
259
  value.nil? && nil_handler ? instance_exec(object, key, attribute, &nil_handler) : value
205
260
  end
206
261
 
262
+ def fetch_attribute_from_object_and_resource(object, attribute)
263
+ has_method = @method_existence[attribute]
264
+ has_method = @method_existence[attribute] = object.respond_to?(attribute) if has_method.nil?
265
+ has_method ? object.__send__(attribute) : __send__(attribute, object)
266
+ end
267
+
207
268
  def nil_handler
208
- @nil_handler ||= (@_on_nil || Alba._on_nil)
269
+ @_on_nil
209
270
  end
210
271
 
211
272
  def yield_if_within(association_name)
@@ -225,8 +286,10 @@ module Alba
225
286
  end
226
287
  end
227
288
 
289
+ # Detect if object is a collection or not.
290
+ # When object is a Struct, it's Enumerable but not a collection
228
291
  def collection?
229
- @object.is_a?(Enumerable)
292
+ @object.is_a?(Enumerable) && !@object.is_a?(Struct)
230
293
  end
231
294
  end
232
295
 
@@ -241,7 +304,6 @@ module Alba
241
304
  end
242
305
 
243
306
  # Defining methods for DSLs and disable parameter number check since for users' benefits increasing params is fine
244
- # rubocop:disable Metrics/ParameterLists
245
307
 
246
308
  # Set multiple attributes at once
247
309
  #
@@ -258,7 +320,7 @@ module Alba
258
320
 
259
321
  def assign_attributes(attrs, if_value)
260
322
  attrs.each do |attr_name|
261
- attr = if_value ? [attr_name.to_sym, if_value] : attr_name.to_sym
323
+ attr = if_value ? ConditionalAttribute.new(body: attr_name.to_sym, condition: if_value) : attr_name.to_sym
262
324
  @_attributes[attr_name.to_sym] = attr
263
325
  end
264
326
  end
@@ -269,7 +331,7 @@ module Alba
269
331
  attr_name = attr_name.to_sym
270
332
  type, type_converter = type_and_converter
271
333
  typed_attr = TypedAttribute.new(name: attr_name, type: type, converter: type_converter)
272
- attr = if_value ? [typed_attr, if_value] : typed_attr
334
+ attr = if_value ? ConditionalAttribute.new(body: typed_attr, condition: if_value) : typed_attr
273
335
  @_attributes[attr_name] = attr
274
336
  end
275
337
  end
@@ -286,53 +348,59 @@ module Alba
286
348
  def attribute(name, **options, &block)
287
349
  raise ArgumentError, 'No block given in attribute method' unless block
288
350
 
289
- @_attributes[name.to_sym] = options[:if] ? [block, options[:if]] : block
351
+ @_attributes[name.to_sym] = options[:if] ? ConditionalAttribute.new(body: block, condition: options[:if]) : block
290
352
  end
291
353
 
292
- # Set One association
354
+ # Set association
293
355
  #
294
356
  # @param name [String, Symbol] name of the association, used as key when `key` param doesn't exist
295
357
  # @param condition [Proc, nil] a Proc to modify the association
296
- # @param resource [Class<Alba::Resource>, String, nil] representing resource for this association
358
+ # @param resource [Class<Alba::Resource>, String, Proc, nil] representing resource for this association
297
359
  # @param key [String, Symbol, nil] used as key when given
360
+ # @param params [Hash] params override for the association
298
361
  # @param options [Hash<Symbol, Proc>]
299
362
  # @option options [Proc] if a condition to decide if this association should be serialized
300
363
  # @param block [Block]
301
364
  # @return [void]
302
- # @see Alba::One#initialize
303
- def one(name, condition = nil, resource: nil, key: nil, **options, &block)
304
- nesting = self.name&.rpartition('::')&.first
305
- one = One.new(name: name, condition: condition, resource: resource, nesting: nesting, &block)
306
- @_attributes[key&.to_sym || name.to_sym] = options[:if] ? [one, options[:if]] : one
365
+ # @see Alba::Association#initialize
366
+ def association(name, condition = nil, resource: nil, key: nil, params: {}, **options, &block)
367
+ key_transformation = @_key_transformation_cascade ? @_transform_type : :none
368
+ assoc = Association.new(
369
+ name: name, condition: condition, resource: resource, params: params, nesting: nesting, key_transformation: key_transformation,
370
+ &block
371
+ )
372
+ @_attributes[key&.to_sym || name.to_sym] = options[:if] ? ConditionalAttribute.new(body: assoc, condition: options[:if]) : assoc
373
+ end
374
+ alias one association
375
+ alias many association
376
+ alias has_one association
377
+ alias has_many association
378
+
379
+ def nesting
380
+ if name.nil?
381
+ nil
382
+ else
383
+ name.rpartition('::').first.tap { |n| n.empty? ? nil : n }
384
+ end
307
385
  end
308
- alias has_one one
386
+ private :nesting
309
387
 
310
- # Set Many association
388
+ # Set a nested attribute with the given block
311
389
  #
312
- # @param name [String, Symbol] name of the association, used as key when `key` param doesn't exist
313
- # @param condition [Proc, nil] a Proc to filter the collection
314
- # @param resource [Class<Alba::Resource>, String, nil] representing resource for this association
315
- # @param key [String, Symbol, nil] used as key when given
390
+ # @param name [String, Symbol] key name
316
391
  # @param options [Hash<Symbol, Proc>]
317
- # @option options [Proc] if a condition to decide if this association should be serialized
318
- # @param block [Block]
392
+ # @option options [Proc] if a condition to decide if this attribute should be serialized
393
+ # @param block [Block] the block called during serialization
394
+ # @raise [ArgumentError] if block is absent
319
395
  # @return [void]
320
- # @see Alba::Many#initialize
321
- def many(name, condition = nil, resource: nil, key: nil, **options, &block)
322
- nesting = self.name&.rpartition('::')&.first
323
- many = Many.new(name: name, condition: condition, resource: resource, nesting: nesting, &block)
324
- @_attributes[key&.to_sym || name.to_sym] = options[:if] ? [many, options[:if]] : many
325
- end
326
- alias has_many many
396
+ def nested_attribute(name, **options, &block)
397
+ raise ArgumentError, 'No block given in attribute method' unless block
327
398
 
328
- # Set key
329
- #
330
- # @param key [String, Symbol]
331
- # @deprecated Use {#root_key} instead
332
- def key(key)
333
- Alba::Deprecation.warn '[DEPRECATION] `key` is deprecated, use `root_key` instead.'
334
- @_key = key.respond_to?(:to_sym) ? key.to_sym : key
399
+ key_transformation = @_key_transformation_cascade ? @_transform_type : :none
400
+ attribute = NestedAttribute.new(key_transformation: key_transformation, &block)
401
+ @_attributes[name.to_sym] = options[:if] ? ConditionalAttribute.new(body: attribute, condition: options[:if]) : attribute
335
402
  end
403
+ alias nested nested_attribute
336
404
 
337
405
  # Set root key
338
406
  #
@@ -344,13 +412,13 @@ module Alba
344
412
  @_key_for_collection = key_for_collection&.to_sym
345
413
  end
346
414
 
347
- # Set key to true
415
+ # Set root key for collection
348
416
  #
349
- # @deprecated Use {#root_key!} instead
350
- def key!
351
- Alba::Deprecation.warn '[DEPRECATION] `key!` is deprecated, use `root_key!` instead.'
417
+ # @param key [String, Symbol]
418
+ # @raise [NoMethodError] when key doesn't respond to `to_sym` method
419
+ def root_key_for_collection(key)
352
420
  @_key = true
353
- @_key_for_collection = true
421
+ @_key_for_collection = key.to_sym
354
422
  end
355
423
 
356
424
  # Set root key to true
@@ -369,26 +437,33 @@ module Alba
369
437
  # @params file [String] name of the layout file
370
438
  # @params inline [Proc] a proc returning JSON string or a Hash representing JSON
371
439
  def layout(file: nil, inline: nil)
372
- @_layout = file || inline
440
+ @_layout = Layout.new(file: file, inline: inline)
373
441
  end
374
442
 
375
- # Delete attributes
376
- # Use this DSL in child class to ignore certain attributes
443
+ # Transform keys as specified type
377
444
  #
378
- # @param attributes [Array<String, Symbol>]
379
- def ignoring(*attributes)
380
- attributes.each do |attr_name|
381
- @_attributes.delete(attr_name.to_sym)
445
+ # @param type [String, Symbol] one of `snake`, `:camel`, `:lower_camel`, `:dash` and `none`
446
+ # @param root [Boolean] decides if root key also should be transformed
447
+ # @param cascade [Boolean] decides if key transformation cascades into inline association
448
+ # Default is true but can be set false for old (v1) behavior
449
+ # @raise [Alba::Error] when type is not supported
450
+ def transform_keys(type, root: true, cascade: true)
451
+ type = type.to_sym
452
+ unless %i[none snake camel lower_camel dash].include?(type)
453
+ # This should be `ArgumentError` but for backward compatibility it raises `Alba::Error`
454
+ raise ::Alba::Error, "Unknown transform type: #{type}. Supported type are :camel, :lower_camel and :dash."
382
455
  end
456
+
457
+ @_transform_type = type
458
+ @_transforming_root_key = root
459
+ @_key_transformation_cascade = cascade
383
460
  end
384
461
 
385
- # Transform keys as specified type
462
+ # Sets key for collection serialization
386
463
  #
387
- # @param type [String, Symbol]
388
- # @param root [Boolean] decides if root key also should be transformed
389
- def transform_keys(type, root: nil)
390
- @_transform_key_function = KeyTransformFactory.create(type.to_sym)
391
- @_transforming_root_key = root
464
+ # @param key [String, Symbol]
465
+ def collection_key(key)
466
+ @_collection_key = key.to_sym
392
467
  end
393
468
 
394
469
  # Set error handler
@@ -400,17 +475,26 @@ module Alba
400
475
  raise ArgumentError, 'You cannot specify error handler with both Symbol and block' if handler && block
401
476
  raise ArgumentError, 'You must specify error handler with either Symbol or block' unless handler || block
402
477
 
403
- @_on_error = handler || block
478
+ @_on_error = block || validated_error_handler(handler)
404
479
  end
405
480
 
481
+ def validated_error_handler(handler)
482
+ unless %i[raise ignore nullify].include?(handler)
483
+ # For backward compatibility
484
+ # TODO: Change this to ArgumentError
485
+ raise Alba::Error, "Unknown error handler: #{handler}. It must be one of `:raise`, `:ignore` or `:nullify`."
486
+ end
487
+
488
+ handler
489
+ end
490
+ private :validated_error_handler
491
+
406
492
  # Set nil handler
407
493
  #
408
494
  # @param block [Block]
409
495
  def on_nil(&block)
410
496
  @_on_nil = block
411
497
  end
412
-
413
- # rubocop:enable Metrics/ParameterLists
414
498
  end
415
499
  end
416
500
  end
@@ -26,7 +26,7 @@ module Alba
26
26
  private
27
27
 
28
28
  def check(object)
29
- value = object.public_send(@name)
29
+ value = object.__send__(@name)
30
30
  type_correct = case @type
31
31
  when :String, ->(klass) { klass == String } then value.is_a?(String)
32
32
  when :Integer, ->(klass) { klass == Integer } then value.is_a?(Integer)
data/lib/alba/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Alba
2
- VERSION = '1.5.0'.freeze
2
+ VERSION = '2.0.0'.freeze
3
3
  end