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