alba 1.5.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 +70 -0
- data/.github/workflows/main.yml +4 -4
- data/.github/workflows/perf.yml +2 -2
- data/.rubocop.yml +5 -1
- data/CHANGELOG.md +34 -0
- data/CONTRIBUTING.md +30 -0
- data/Gemfile +6 -2
- data/HACKING.md +41 -0
- data/README.md +599 -128
- data/Rakefile +2 -2
- data/alba.gemspec +8 -4
- data/benchmark/README.md +81 -0
- data/benchmark/collection.rb +60 -74
- data/benchmark/single_resource.rb +33 -2
- data/docs/migrate_from_jbuilder.md +18 -4
- data/docs/rails.md +44 -0
- data/lib/alba/association.rb +49 -9
- data/lib/alba/conditional_attribute.rb +54 -0
- data/lib/alba/default_inflector.rb +13 -24
- data/lib/alba/errors.rb +10 -0
- data/lib/alba/layout.rb +67 -0
- data/lib/alba/nested_attribute.rb +18 -0
- data/lib/alba/resource.rb +240 -156
- data/lib/alba/typed_attribute.rb +1 -1
- data/lib/alba/version.rb +1 -1
- data/lib/alba.rb +43 -58
- data/logo/alba-card.png +0 -0
- data/logo/alba-sign.png +0 -0
- data/logo/alba-typography.png +0 -0
- metadata +21 -11
- data/gemfiles/all.gemfile +0 -19
- data/lib/alba/key_transform_factory.rb +0 -33
- data/lib/alba/many.rb +0 -21
- data/lib/alba/one.rb +0 -21
- data/sider.yml +0 -60
data/lib/alba/resource.rb
CHANGED
@@ -1,15 +1,16 @@
|
|
1
|
-
require_relative '
|
2
|
-
require_relative '
|
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,
|
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
|
41
|
+
@params = params
|
41
42
|
@within = within
|
42
|
-
|
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(
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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? ?
|
97
|
+
collection? ? serializable_hash_for_collection : converter.call(@object)
|
69
98
|
end
|
70
|
-
alias
|
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
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
-
|
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
|
-
|
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
|
119
|
-
|
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
|
180
|
+
@_transforming_root_key
|
124
181
|
end
|
125
182
|
|
126
183
|
def converter
|
127
184
|
lambda do |object|
|
128
|
-
|
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
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
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
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
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
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
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
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
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
|
-
#
|
189
|
-
def transform_key(key)
|
190
|
-
|
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
|
-
|
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
|
251
|
+
when Symbol then fetch_attribute_from_object_and_resource(object, attribute)
|
198
252
|
when Proc then instance_exec(object, &attribute)
|
199
|
-
when Alba::
|
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
|
-
@
|
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 ?
|
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 ?
|
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] ?
|
351
|
+
@_attributes[name.to_sym] = options[:if] ? ConditionalAttribute.new(body: block, condition: options[:if]) : block
|
290
352
|
end
|
291
353
|
|
292
|
-
# Set
|
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::
|
303
|
-
def
|
304
|
-
|
305
|
-
|
306
|
-
|
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
|
-
|
386
|
+
private :nesting
|
309
387
|
|
310
|
-
# Set
|
388
|
+
# Set a nested attribute with the given block
|
311
389
|
#
|
312
|
-
# @param name [String, Symbol]
|
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
|
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
|
-
|
321
|
-
|
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
|
-
|
329
|
-
|
330
|
-
|
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
|
415
|
+
# Set root key for collection
|
348
416
|
#
|
349
|
-
# @
|
350
|
-
|
351
|
-
|
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 =
|
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
|
440
|
+
@_layout = Layout.new(file: file, inline: inline)
|
373
441
|
end
|
374
442
|
|
375
|
-
#
|
376
|
-
# Use this DSL in child class to ignore certain attributes
|
443
|
+
# Transform keys as specified type
|
377
444
|
#
|
378
|
-
# @param
|
379
|
-
|
380
|
-
|
381
|
-
|
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
|
-
#
|
462
|
+
# Sets key for collection serialization
|
386
463
|
#
|
387
|
-
# @param
|
388
|
-
|
389
|
-
|
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 =
|
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
|
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