alba 1.5.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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