alba 1.5.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 +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