alba 1.6.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +11 -0
- data/.github/dependabot.yml +4 -18
- data/.github/workflows/codeql-analysis.yml +4 -4
- data/.github/workflows/main.yml +4 -6
- data/.github/workflows/perf.yml +2 -2
- data/.rubocop.yml +3 -1
- data/CHANGELOG.md +24 -0
- data/CONTRIBUTING.md +30 -0
- data/Gemfile +6 -2
- data/HACKING.md +41 -0
- data/README.md +381 -58
- data/Rakefile +2 -2
- data/alba.gemspec +1 -1
- data/benchmark/README.md +81 -0
- data/benchmark/collection.rb +0 -70
- data/docs/migrate_from_jbuilder.md +18 -4
- data/docs/rails.md +44 -0
- data/lib/alba/association.rb +25 -5
- data/lib/alba/conditional_attribute.rb +54 -0
- data/lib/alba/default_inflector.rb +10 -39
- data/lib/alba/layout.rb +67 -0
- data/lib/alba/nested_attribute.rb +18 -0
- data/lib/alba/resource.rb +201 -173
- data/lib/alba/typed_attribute.rb +1 -1
- data/lib/alba/version.rb +1 -1
- data/lib/alba.rb +13 -56
- data/logo/alba-card.png +0 -0
- data/logo/alba-sign.png +0 -0
- data/logo/alba-typography.png +0 -0
- metadata +15 -6
- data/gemfiles/all.gemfile +0 -20
- data/sider.yml +0 -60
data/lib/alba/resource.rb
CHANGED
@@ -1,13 +1,16 @@
|
|
1
1
|
require_relative 'association'
|
2
|
+
require_relative 'conditional_attribute'
|
2
3
|
require_relative 'typed_attribute'
|
4
|
+
require_relative 'nested_attribute'
|
3
5
|
require_relative 'deprecation'
|
6
|
+
require_relative 'layout'
|
4
7
|
|
5
8
|
module Alba
|
6
9
|
# This module represents what should be serialized
|
7
10
|
module Resource
|
8
11
|
# @!parse include InstanceMethods
|
9
12
|
# @!parse extend ClassMethods
|
10
|
-
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}.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
|
11
14
|
private_constant :DSLS
|
12
15
|
|
13
16
|
WITHIN_DEFAULT = Object.new.freeze
|
@@ -35,74 +38,91 @@ module Alba
|
|
35
38
|
# @param within [Object, nil, false, true] determines what associations to be serialized. If not set, it serializes all associations.
|
36
39
|
def initialize(object, params: {}, within: WITHIN_DEFAULT)
|
37
40
|
@object = object
|
38
|
-
@params = params
|
41
|
+
@params = params
|
39
42
|
@within = within
|
40
43
|
@method_existence = {} # Cache for `respond_to?` result
|
41
|
-
DSLS.each_key { |name| instance_variable_set("@#{name}", self.class.
|
44
|
+
DSLS.each_key { |name| instance_variable_set("@#{name}", self.class.__send__(name)) }
|
42
45
|
end
|
43
46
|
|
44
47
|
# Serialize object into JSON string
|
45
48
|
#
|
46
|
-
# @param key [Symbol, nil, true] DEPRECATED, use root_key instead
|
47
49
|
# @param root_key [Symbol, nil, true]
|
48
50
|
# @param meta [Hash] metadata for this seialization
|
49
51
|
# @return [String] serialized JSON string
|
50
|
-
def serialize(
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
62
92
|
|
63
93
|
# A Hash for serialization
|
64
94
|
#
|
65
95
|
# @return [Hash]
|
66
96
|
def serializable_hash
|
67
|
-
collection? ?
|
97
|
+
collection? ? serializable_hash_for_collection : converter.call(@object)
|
68
98
|
end
|
69
99
|
alias to_h serializable_hash
|
70
100
|
|
71
|
-
# @deprecated Use {#serializable_hash} instead
|
72
|
-
def to_hash
|
73
|
-
warn '[DEPRECATION] `to_hash` is deprecated, use `serializable_hash` instead.'
|
74
|
-
serializable_hash
|
75
|
-
end
|
76
|
-
|
77
101
|
private
|
78
102
|
|
79
|
-
attr_reader :serialized_json # Mainly for layout
|
80
|
-
|
81
103
|
def encode(hash)
|
82
104
|
Alba.encoder.call(hash)
|
83
105
|
end
|
84
106
|
|
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)
|
117
|
+
end
|
118
|
+
serialize(root_key: root_key, meta: meta)
|
119
|
+
end
|
120
|
+
|
85
121
|
def serialize_with(hash)
|
86
122
|
serialized_json = encode(hash)
|
87
123
|
return serialized_json unless @_layout
|
88
124
|
|
89
|
-
@serialized_json
|
90
|
-
if @_layout.is_a?(String) # file
|
91
|
-
ERB.new(File.read(@_layout)).result(binding)
|
92
|
-
|
93
|
-
else # inline
|
94
|
-
serialize_within_inline_layout
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
def serialize_within_inline_layout
|
99
|
-
inline = instance_eval(&@_layout)
|
100
|
-
case inline
|
101
|
-
when Hash then encode(inline)
|
102
|
-
when String then inline
|
103
|
-
else
|
104
|
-
raise Alba::Error, 'Inline layout must be a Proc returning a Hash or a String'
|
105
|
-
end
|
125
|
+
@_layout.serialize(resource: self, serialized_json: serialized_json, binding: binding)
|
106
126
|
end
|
107
127
|
|
108
128
|
def hash_with_metadata(hash, meta)
|
@@ -113,130 +133,126 @@ module Alba
|
|
113
133
|
hash
|
114
134
|
end
|
115
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]
|
116
145
|
def fetch_key
|
117
|
-
collection? ? _key_for_collection : _key
|
146
|
+
k = collection? ? _key_for_collection : _key
|
147
|
+
transforming_root_key? ? transform_key(k) : k
|
118
148
|
end
|
119
149
|
|
120
150
|
def _key_for_collection
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
125
156
|
end
|
126
157
|
|
127
158
|
# @return [String]
|
128
159
|
def _key
|
129
|
-
|
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
|
130
166
|
|
131
|
-
|
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
|
132
173
|
end
|
133
174
|
|
134
|
-
def
|
135
|
-
|
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.'
|
136
177
|
end
|
137
178
|
|
138
179
|
def transforming_root_key?
|
139
|
-
@_transforming_root_key
|
180
|
+
@_transforming_root_key
|
140
181
|
end
|
141
182
|
|
142
|
-
# rubocop:disable Metrics/MethodLength
|
143
183
|
def converter
|
144
184
|
lambda do |object|
|
145
|
-
|
146
|
-
key_and_attribute_body_from(object, key, attribute)
|
147
|
-
rescue ::Alba::Error, FrozenError, TypeError
|
148
|
-
raise
|
149
|
-
rescue StandardError => e
|
150
|
-
handle_error(e, object, key, attribute)
|
151
|
-
end
|
152
|
-
arrays.compact!
|
153
|
-
arrays.to_h
|
185
|
+
attributes_to_hash(object, {})
|
154
186
|
end
|
155
187
|
end
|
156
|
-
# rubocop:enable Metrics/MethodLength
|
157
188
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
def key_and_attribute_body_from(object, key, attribute)
|
165
|
-
key = transform_key(key)
|
166
|
-
if attribute.is_a?(Array) # Conditional
|
167
|
-
conditional_attribute(object, key, attribute)
|
168
|
-
else
|
169
|
-
fetched_attribute = fetch_attribute(object, key, attribute)
|
170
|
-
[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
|
171
195
|
end
|
172
196
|
end
|
173
197
|
|
174
|
-
def
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
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)
|
180
205
|
end
|
206
|
+
hash
|
181
207
|
end
|
182
208
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
fetched_attribute = fetch_attribute(object, key, attribute)
|
189
|
-
attr = attribute.is_a?(Alba::Association) ? attribute.object : fetched_attribute
|
190
|
-
return if arity >= 2 && !instance_exec(object, attr, &condition)
|
191
|
-
|
192
|
-
[key, fetched_attribute]
|
209
|
+
# This is default behavior for getting attributes for serialization
|
210
|
+
# Override this method to filter certain attributes
|
211
|
+
def attributes
|
212
|
+
@_attributes
|
193
213
|
end
|
194
214
|
|
195
|
-
def
|
196
|
-
|
197
|
-
|
198
|
-
[key
|
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
|
199
219
|
end
|
200
220
|
|
201
|
-
def handle_error(error, object, key, attribute)
|
202
|
-
on_error = @_on_error ||
|
203
|
-
case on_error
|
204
|
-
when :raise, nil then raise
|
205
|
-
when :nullify then [key
|
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
|
206
226
|
when :ignore then nil
|
207
|
-
when Proc
|
208
|
-
|
209
|
-
|
227
|
+
when Proc
|
228
|
+
key, value = on_error.call(error, object, key, attribute, self.class)
|
229
|
+
hash[key] = value
|
210
230
|
end
|
211
231
|
end
|
212
232
|
|
213
|
-
# rubocop:disable Metrics/MethodLength
|
214
233
|
# @return [Symbol]
|
215
|
-
def transform_key(key)
|
216
|
-
return key if @_transform_type == :none
|
217
|
-
|
234
|
+
def transform_key(key) # rubocop:disable Metrics/CyclomaticComplexity
|
218
235
|
key = key.to_s
|
219
|
-
|
220
|
-
|
221
|
-
inflector = Alba.inflector
|
222
|
-
|
223
|
-
|
224
|
-
end
|
236
|
+
return key if @_transform_type == :none || key.empty? # We can skip transformation
|
237
|
+
|
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
|
+
|
225
241
|
case @_transform_type # rubocop:disable Style/MissingElse
|
226
242
|
when :camel then inflector.camelize(key)
|
227
243
|
when :lower_camel then inflector.camelize_lower(key)
|
228
244
|
when :dash then inflector.dasherize(key)
|
229
245
|
when :snake then inflector.underscore(key)
|
230
|
-
end
|
246
|
+
end
|
231
247
|
end
|
232
|
-
# rubocop:enable Metrics/MethodLength
|
233
248
|
|
234
|
-
def fetch_attribute(object, key, attribute)
|
249
|
+
def fetch_attribute(object, key, attribute) # rubocop:disable Metrics/CyclomaticComplexity
|
235
250
|
value = case attribute
|
236
251
|
when Symbol then fetch_attribute_from_object_and_resource(object, attribute)
|
237
252
|
when Proc then instance_exec(object, &attribute)
|
238
253
|
when Alba::Association then yield_if_within(attribute.name.to_sym) { |within| attribute.to_h(object, params: params, within: within) }
|
239
|
-
when TypedAttribute then attribute.value(object)
|
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) }
|
240
256
|
else
|
241
257
|
raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
|
242
258
|
end
|
@@ -246,11 +262,11 @@ module Alba
|
|
246
262
|
def fetch_attribute_from_object_and_resource(object, attribute)
|
247
263
|
has_method = @method_existence[attribute]
|
248
264
|
has_method = @method_existence[attribute] = object.respond_to?(attribute) if has_method.nil?
|
249
|
-
has_method ? object.
|
265
|
+
has_method ? object.__send__(attribute) : __send__(attribute, object)
|
250
266
|
end
|
251
267
|
|
252
268
|
def nil_handler
|
253
|
-
@
|
269
|
+
@_on_nil
|
254
270
|
end
|
255
271
|
|
256
272
|
def yield_if_within(association_name)
|
@@ -304,7 +320,7 @@ module Alba
|
|
304
320
|
|
305
321
|
def assign_attributes(attrs, if_value)
|
306
322
|
attrs.each do |attr_name|
|
307
|
-
attr = if_value ?
|
323
|
+
attr = if_value ? ConditionalAttribute.new(body: attr_name.to_sym, condition: if_value) : attr_name.to_sym
|
308
324
|
@_attributes[attr_name.to_sym] = attr
|
309
325
|
end
|
310
326
|
end
|
@@ -315,7 +331,7 @@ module Alba
|
|
315
331
|
attr_name = attr_name.to_sym
|
316
332
|
type, type_converter = type_and_converter
|
317
333
|
typed_attr = TypedAttribute.new(name: attr_name, type: type, converter: type_converter)
|
318
|
-
attr = if_value ?
|
334
|
+
attr = if_value ? ConditionalAttribute.new(body: typed_attr, condition: if_value) : typed_attr
|
319
335
|
@_attributes[attr_name] = attr
|
320
336
|
end
|
321
337
|
end
|
@@ -332,38 +348,59 @@ module Alba
|
|
332
348
|
def attribute(name, **options, &block)
|
333
349
|
raise ArgumentError, 'No block given in attribute method' unless block
|
334
350
|
|
335
|
-
@_attributes[name.to_sym] = options[:if] ?
|
351
|
+
@_attributes[name.to_sym] = options[:if] ? ConditionalAttribute.new(body: block, condition: options[:if]) : block
|
336
352
|
end
|
337
353
|
|
338
354
|
# Set association
|
339
355
|
#
|
340
356
|
# @param name [String, Symbol] name of the association, used as key when `key` param doesn't exist
|
341
357
|
# @param condition [Proc, nil] a Proc to modify the association
|
342
|
-
# @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
|
343
359
|
# @param key [String, Symbol, nil] used as key when given
|
360
|
+
# @param params [Hash] params override for the association
|
344
361
|
# @param options [Hash<Symbol, Proc>]
|
345
362
|
# @option options [Proc] if a condition to decide if this association should be serialized
|
346
363
|
# @param block [Block]
|
347
364
|
# @return [void]
|
348
365
|
# @see Alba::Association#initialize
|
349
|
-
def association(name, condition = nil, resource: nil, key: nil, **options, &block)
|
350
|
-
|
351
|
-
assoc = Association.new(
|
352
|
-
|
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
|
353
373
|
end
|
354
374
|
alias one association
|
355
375
|
alias many association
|
356
376
|
alias has_one association
|
357
377
|
alias has_many association
|
358
378
|
|
359
|
-
|
379
|
+
def nesting
|
380
|
+
if name.nil?
|
381
|
+
nil
|
382
|
+
else
|
383
|
+
name.rpartition('::').first.tap { |n| n.empty? ? nil : n }
|
384
|
+
end
|
385
|
+
end
|
386
|
+
private :nesting
|
387
|
+
|
388
|
+
# Set a nested attribute with the given block
|
360
389
|
#
|
361
|
-
# @param
|
362
|
-
# @
|
363
|
-
|
364
|
-
|
365
|
-
|
390
|
+
# @param name [String, Symbol] key name
|
391
|
+
# @param options [Hash<Symbol, Proc>]
|
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
|
395
|
+
# @return [void]
|
396
|
+
def nested_attribute(name, **options, &block)
|
397
|
+
raise ArgumentError, 'No block given in attribute method' unless block
|
398
|
+
|
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
|
366
402
|
end
|
403
|
+
alias nested nested_attribute
|
367
404
|
|
368
405
|
# Set root key
|
369
406
|
#
|
@@ -375,13 +412,13 @@ module Alba
|
|
375
412
|
@_key_for_collection = key_for_collection&.to_sym
|
376
413
|
end
|
377
414
|
|
378
|
-
# Set key
|
415
|
+
# Set root key for collection
|
379
416
|
#
|
380
|
-
# @
|
381
|
-
|
382
|
-
|
417
|
+
# @param key [String, Symbol]
|
418
|
+
# @raise [NoMethodError] when key doesn't respond to `to_sym` method
|
419
|
+
def root_key_for_collection(key)
|
383
420
|
@_key = true
|
384
|
-
@_key_for_collection =
|
421
|
+
@_key_for_collection = key.to_sym
|
385
422
|
end
|
386
423
|
|
387
424
|
# Set root key to true
|
@@ -400,45 +437,17 @@ module Alba
|
|
400
437
|
# @params file [String] name of the layout file
|
401
438
|
# @params inline [Proc] a proc returning JSON string or a Hash representing JSON
|
402
439
|
def layout(file: nil, inline: nil)
|
403
|
-
@_layout =
|
404
|
-
end
|
405
|
-
|
406
|
-
def validated_file_layout(filename)
|
407
|
-
case filename
|
408
|
-
when String, nil then filename
|
409
|
-
else
|
410
|
-
raise ArgumentError, 'File layout must be a String representing filename'
|
411
|
-
end
|
412
|
-
end
|
413
|
-
private :validated_file_layout
|
414
|
-
|
415
|
-
def validated_inline_layout(inline_layout)
|
416
|
-
case inline_layout
|
417
|
-
when Proc, nil then inline_layout
|
418
|
-
else
|
419
|
-
raise ArgumentError, 'Inline layout must be a Proc returning a Hash or a String'
|
420
|
-
end
|
421
|
-
end
|
422
|
-
private :validated_inline_layout
|
423
|
-
|
424
|
-
# Delete attributes
|
425
|
-
# Use this DSL in child class to ignore certain attributes
|
426
|
-
#
|
427
|
-
# @param attributes [Array<String, Symbol>]
|
428
|
-
def ignoring(*attributes)
|
429
|
-
Alba::Deprecation.warn '`ignoring` is deprecated now. Instead please use `attributes` instance method to filter out attributes.'
|
430
|
-
attributes.each do |attr_name|
|
431
|
-
@_attributes.delete(attr_name.to_sym)
|
432
|
-
end
|
440
|
+
@_layout = Layout.new(file: file, inline: inline)
|
433
441
|
end
|
434
442
|
|
435
443
|
# Transform keys as specified type
|
436
444
|
#
|
437
445
|
# @param type [String, Symbol] one of `snake`, `:camel`, `:lower_camel`, `:dash` and `none`
|
438
|
-
# @param root [Boolean
|
439
|
-
#
|
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
|
440
449
|
# @raise [Alba::Error] when type is not supported
|
441
|
-
def transform_keys(type, root:
|
450
|
+
def transform_keys(type, root: true, cascade: true)
|
442
451
|
type = type.to_sym
|
443
452
|
unless %i[none snake camel lower_camel dash].include?(type)
|
444
453
|
# This should be `ArgumentError` but for backward compatibility it raises `Alba::Error`
|
@@ -447,6 +456,14 @@ module Alba
|
|
447
456
|
|
448
457
|
@_transform_type = type
|
449
458
|
@_transforming_root_key = root
|
459
|
+
@_key_transformation_cascade = cascade
|
460
|
+
end
|
461
|
+
|
462
|
+
# Sets key for collection serialization
|
463
|
+
#
|
464
|
+
# @param key [String, Symbol]
|
465
|
+
def collection_key(key)
|
466
|
+
@_collection_key = key.to_sym
|
450
467
|
end
|
451
468
|
|
452
469
|
# Set error handler
|
@@ -458,8 +475,19 @@ module Alba
|
|
458
475
|
raise ArgumentError, 'You cannot specify error handler with both Symbol and block' if handler && block
|
459
476
|
raise ArgumentError, 'You must specify error handler with either Symbol or block' unless handler || block
|
460
477
|
|
461
|
-
@_on_error =
|
478
|
+
@_on_error = block || validated_error_handler(handler)
|
479
|
+
end
|
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
|
462
489
|
end
|
490
|
+
private :validated_error_handler
|
463
491
|
|
464
492
|
# Set nil handler
|
465
493
|
#
|
data/lib/alba/typed_attribute.rb
CHANGED
@@ -26,7 +26,7 @@ module Alba
|
|
26
26
|
private
|
27
27
|
|
28
28
|
def check(object)
|
29
|
-
value = object.
|
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