alba 1.6.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.
- 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