alba 1.3.0 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/codeql-analysis.yml +70 -0
- data/.github/workflows/main.yml +3 -1
- data/.github/workflows/perf.yml +21 -0
- data/.rubocop.yml +9 -7
- data/CHANGELOG.md +28 -0
- data/Gemfile +4 -3
- data/README.md +469 -60
- data/alba.gemspec +7 -3
- data/benchmark/collection.rb +108 -3
- data/benchmark/single_resource.rb +82 -1
- data/docs/migrate_from_active_model_serializers.md +359 -0
- data/docs/migrate_from_jbuilder.md +223 -0
- data/gemfiles/all.gemfile +2 -1
- data/gemfiles/without_active_support.gemfile +1 -1
- data/gemfiles/without_oj.gemfile +1 -1
- data/lib/alba/association.rb +33 -24
- data/lib/alba/default_inflector.rb +22 -4
- data/lib/alba/deprecation.rb +14 -0
- data/lib/alba/errors.rb +10 -0
- data/lib/alba/resource.rb +260 -100
- data/lib/alba/typed_attribute.rb +3 -6
- data/lib/alba/version.rb +1 -1
- data/lib/alba.rb +110 -36
- data/script/perf_check.rb +174 -0
- metadata +14 -8
- data/lib/alba/key_transform_factory.rb +0 -33
- data/lib/alba/many.rb +0 -21
- data/lib/alba/one.rb +0 -21
@@ -1,5 +1,7 @@
|
|
1
1
|
module Alba
|
2
|
-
# This module
|
2
|
+
# This module has two purposes.
|
3
|
+
# One is that we require `active_support/inflector` in this module so that we don't do that all over the place.
|
4
|
+
# Another is that `ActiveSupport::Inflector` doesn't have `camelize_lower` method that we want it to have, so this module works as an adapter.
|
3
5
|
module DefaultInflector
|
4
6
|
begin
|
5
7
|
require 'active_support/inflector'
|
@@ -11,7 +13,7 @@ module Alba
|
|
11
13
|
|
12
14
|
# Camelizes a key
|
13
15
|
#
|
14
|
-
# @
|
16
|
+
# @param key [String] key to be camelized
|
15
17
|
# @return [String] camelized key
|
16
18
|
def camelize(key)
|
17
19
|
ActiveSupport::Inflector.camelize(key)
|
@@ -19,7 +21,7 @@ module Alba
|
|
19
21
|
|
20
22
|
# Camelizes a key, 1st letter lowercase
|
21
23
|
#
|
22
|
-
# @
|
24
|
+
# @param key [String] key to be camelized
|
23
25
|
# @return [String] camelized key
|
24
26
|
def camelize_lower(key)
|
25
27
|
ActiveSupport::Inflector.camelize(key, false)
|
@@ -27,10 +29,26 @@ module Alba
|
|
27
29
|
|
28
30
|
# Dasherizes a key
|
29
31
|
#
|
30
|
-
# @
|
32
|
+
# @param key [String] key to be dasherized
|
31
33
|
# @return [String] dasherized key
|
32
34
|
def dasherize(key)
|
33
35
|
ActiveSupport::Inflector.dasherize(key)
|
34
36
|
end
|
37
|
+
|
38
|
+
# Underscore a key
|
39
|
+
#
|
40
|
+
# @param key [String] key to be underscore
|
41
|
+
# @return [String] underscored key
|
42
|
+
def underscore(key)
|
43
|
+
ActiveSupport::Inflector.underscore(key)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Classify a key
|
47
|
+
#
|
48
|
+
# @param key [String] key to be classified
|
49
|
+
# @return [String] classified key
|
50
|
+
def classify(key)
|
51
|
+
ActiveSupport::Inflector.classify(key)
|
52
|
+
end
|
35
53
|
end
|
36
54
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Alba
|
2
|
+
# Module for printing deprecation warning
|
3
|
+
module Deprecation
|
4
|
+
# Similar to {Kernel.warn} but prints caller as well
|
5
|
+
#
|
6
|
+
# @param message [String] main message to print
|
7
|
+
# @return void
|
8
|
+
def warn(message)
|
9
|
+
Kernel.warn(message)
|
10
|
+
Kernel.warn(caller_locations(2..2).first) # For performance reason we use (2..2).first
|
11
|
+
end
|
12
|
+
module_function :warn
|
13
|
+
end
|
14
|
+
end
|
data/lib/alba/errors.rb
ADDED
data/lib/alba/resource.rb
CHANGED
@@ -1,14 +1,13 @@
|
|
1
|
-
require_relative '
|
2
|
-
require_relative 'many'
|
3
|
-
require_relative 'key_transform_factory'
|
1
|
+
require_relative 'association'
|
4
2
|
require_relative 'typed_attribute'
|
3
|
+
require_relative 'deprecation'
|
5
4
|
|
6
5
|
module Alba
|
7
6
|
# This module represents what should be serialized
|
8
7
|
module Resource
|
9
8
|
# @!parse include InstanceMethods
|
10
9
|
# @!parse extend ClassMethods
|
11
|
-
DSLS = {_attributes: {}, _key: nil,
|
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
|
12
11
|
private_constant :DSLS
|
13
12
|
|
14
13
|
WITHIN_DEFAULT = Object.new.freeze
|
@@ -33,23 +32,33 @@ module Alba
|
|
33
32
|
|
34
33
|
# @param object [Object] the object to be serialized
|
35
34
|
# @param params [Hash] user-given Hash for arbitrary data
|
36
|
-
# @param within [
|
35
|
+
# @param within [Object, nil, false, true] determines what associations to be serialized. If not set, it serializes all associations.
|
37
36
|
def initialize(object, params: {}, within: WITHIN_DEFAULT)
|
38
37
|
@object = object
|
39
38
|
@params = params.freeze
|
40
39
|
@within = within
|
40
|
+
@method_existence = {} # Cache for `respond_to?` result
|
41
41
|
DSLS.each_key { |name| instance_variable_set("@#{name}", self.class.public_send(name)) }
|
42
42
|
end
|
43
43
|
|
44
44
|
# Serialize object into JSON string
|
45
45
|
#
|
46
|
-
# @param key [Symbol]
|
46
|
+
# @param key [Symbol, nil, true] DEPRECATED, use root_key instead
|
47
|
+
# @param root_key [Symbol, nil, true]
|
48
|
+
# @param meta [Hash] metadata for this seialization
|
47
49
|
# @return [String] serialized JSON string
|
48
|
-
def serialize(key: nil)
|
49
|
-
key
|
50
|
-
|
51
|
-
|
50
|
+
def serialize(key: nil, root_key: nil, meta: {})
|
51
|
+
Alba::Deprecation.warn '`key` option to `serialize` method is deprecated, use `root_key` instead.' if key
|
52
|
+
key = key.nil? && root_key.nil? ? fetch_key : root_key || key
|
53
|
+
hash = if key && key != ''
|
54
|
+
h = {key.to_s => serializable_hash}
|
55
|
+
hash_with_metadata(h, meta)
|
56
|
+
else
|
57
|
+
serializable_hash
|
58
|
+
end
|
59
|
+
serialize_with(hash)
|
52
60
|
end
|
61
|
+
alias to_json serialize
|
53
62
|
|
54
63
|
# A Hash for serialization
|
55
64
|
#
|
@@ -57,127 +66,214 @@ module Alba
|
|
57
66
|
def serializable_hash
|
58
67
|
collection? ? @object.map(&converter) : converter.call(@object)
|
59
68
|
end
|
60
|
-
alias
|
69
|
+
alias to_h serializable_hash
|
70
|
+
|
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
|
61
76
|
|
62
77
|
private
|
63
78
|
|
79
|
+
attr_reader :serialized_json # Mainly for layout
|
80
|
+
|
81
|
+
def encode(hash)
|
82
|
+
Alba.encoder.call(hash)
|
83
|
+
end
|
84
|
+
|
85
|
+
def serialize_with(hash)
|
86
|
+
serialized_json = encode(hash)
|
87
|
+
return serialized_json unless @_layout
|
88
|
+
|
89
|
+
@serialized_json = 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
|
106
|
+
end
|
107
|
+
|
108
|
+
def hash_with_metadata(hash, meta)
|
109
|
+
return hash if meta.empty? && @_meta.nil?
|
110
|
+
|
111
|
+
metadata = @_meta ? instance_eval(&@_meta).merge(meta) : meta
|
112
|
+
hash[:meta] = metadata
|
113
|
+
hash
|
114
|
+
end
|
115
|
+
|
116
|
+
def fetch_key
|
117
|
+
collection? ? _key_for_collection : _key
|
118
|
+
end
|
119
|
+
|
120
|
+
def _key_for_collection
|
121
|
+
return @_key_for_collection.to_s unless @_key_for_collection == true && Alba.inferring
|
122
|
+
|
123
|
+
key = resource_name.pluralize
|
124
|
+
transforming_root_key? ? transform_key(key) : key
|
125
|
+
end
|
126
|
+
|
64
127
|
# @return [String]
|
65
128
|
def _key
|
66
129
|
return @_key.to_s unless @_key == true && Alba.inferring
|
67
130
|
|
68
|
-
transforming_root_key? ? transform_key(
|
69
|
-
end
|
70
|
-
|
71
|
-
def key_from_resource_name
|
72
|
-
collection? ? resource_name.pluralize : resource_name
|
131
|
+
transforming_root_key? ? transform_key(resource_name) : resource_name
|
73
132
|
end
|
74
133
|
|
75
134
|
def resource_name
|
76
|
-
self.class.name.demodulize.delete_suffix('Resource').underscore
|
135
|
+
@resource_name ||= self.class.name.demodulize.delete_suffix('Resource').underscore
|
77
136
|
end
|
78
137
|
|
79
138
|
def transforming_root_key?
|
80
139
|
@_transforming_root_key.nil? ? Alba.transforming_root_key : @_transforming_root_key
|
81
140
|
end
|
82
141
|
|
142
|
+
# rubocop:disable Metrics/MethodLength
|
83
143
|
def converter
|
84
144
|
lambda do |object|
|
85
|
-
arrays =
|
145
|
+
arrays = attributes.map do |key, attribute|
|
86
146
|
key_and_attribute_body_from(object, key, attribute)
|
87
147
|
rescue ::Alba::Error, FrozenError, TypeError
|
88
148
|
raise
|
89
149
|
rescue StandardError => e
|
90
150
|
handle_error(e, object, key, attribute)
|
91
151
|
end
|
92
|
-
arrays.
|
152
|
+
arrays.compact!
|
153
|
+
arrays.to_h
|
93
154
|
end
|
94
155
|
end
|
156
|
+
# rubocop:enable Metrics/MethodLength
|
157
|
+
|
158
|
+
# This is default behavior for getting attributes for serialization
|
159
|
+
# Override this method to filter certain attributes
|
160
|
+
def attributes
|
161
|
+
@_attributes
|
162
|
+
end
|
95
163
|
|
96
164
|
def key_and_attribute_body_from(object, key, attribute)
|
97
165
|
key = transform_key(key)
|
98
166
|
if attribute.is_a?(Array) # Conditional
|
99
167
|
conditional_attribute(object, key, attribute)
|
100
168
|
else
|
101
|
-
|
169
|
+
fetched_attribute = fetch_attribute(object, key, attribute)
|
170
|
+
[key, fetched_attribute]
|
102
171
|
end
|
103
172
|
end
|
104
173
|
|
105
174
|
def conditional_attribute(object, key, attribute)
|
106
175
|
condition = attribute.last
|
176
|
+
if condition.is_a?(Proc)
|
177
|
+
conditional_attribute_with_proc(object, key, attribute.first, condition)
|
178
|
+
else
|
179
|
+
conditional_attribute_with_symbol(object, key, attribute.first, condition)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def conditional_attribute_with_proc(object, key, attribute, condition)
|
107
184
|
arity = condition.arity
|
108
|
-
|
185
|
+
# We can return early to skip fetch_attribute
|
186
|
+
return if arity <= 1 && !instance_exec(object, &condition)
|
109
187
|
|
110
|
-
fetched_attribute = fetch_attribute(object, attribute
|
111
|
-
attr =
|
112
|
-
|
113
|
-
else
|
114
|
-
fetched_attribute
|
115
|
-
end
|
116
|
-
return [] if arity >= 2 && !instance_exec(object, attr, &condition)
|
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)
|
117
191
|
|
118
192
|
[key, fetched_attribute]
|
119
193
|
end
|
120
194
|
|
195
|
+
def conditional_attribute_with_symbol(object, key, attribute, condition)
|
196
|
+
return unless __send__(condition)
|
197
|
+
|
198
|
+
[key, fetch_attribute(object, key, attribute)]
|
199
|
+
end
|
200
|
+
|
121
201
|
def handle_error(error, object, key, attribute)
|
122
202
|
on_error = @_on_error || Alba._on_error
|
123
203
|
case on_error
|
124
|
-
when :raise, nil
|
125
|
-
|
126
|
-
when :
|
127
|
-
|
128
|
-
when :ignore
|
129
|
-
[]
|
130
|
-
when Proc
|
131
|
-
on_error.call(error, object, key, attribute, self.class)
|
204
|
+
when :raise, nil then raise
|
205
|
+
when :nullify then [key, nil]
|
206
|
+
when :ignore then nil
|
207
|
+
when Proc then on_error.call(error, object, key, attribute, self.class)
|
132
208
|
else
|
133
209
|
raise ::Alba::Error, "Unknown on_error: #{on_error.inspect}"
|
134
210
|
end
|
135
211
|
end
|
136
212
|
|
137
|
-
#
|
213
|
+
# rubocop:disable Metrics/MethodLength
|
214
|
+
# @return [Symbol]
|
138
215
|
def transform_key(key)
|
139
|
-
return key if @
|
216
|
+
return key if @_transform_type == :none
|
217
|
+
|
218
|
+
key = key.to_s
|
219
|
+
# TODO: Using default inflector here is for backward compatibility
|
220
|
+
# From 2.0 it'll raise error when inflector is nil
|
221
|
+
inflector = Alba.inflector || begin
|
222
|
+
require_relative 'default_inflector'
|
223
|
+
Alba::DefaultInflector
|
224
|
+
end
|
225
|
+
case @_transform_type # rubocop:disable Style/MissingElse
|
226
|
+
when :camel then inflector.camelize(key)
|
227
|
+
when :lower_camel then inflector.camelize_lower(key)
|
228
|
+
when :dash then inflector.dasherize(key)
|
229
|
+
when :snake then inflector.underscore(key)
|
230
|
+
end.to_sym
|
231
|
+
end
|
232
|
+
# rubocop:enable Metrics/MethodLength
|
233
|
+
|
234
|
+
def fetch_attribute(object, key, attribute)
|
235
|
+
value = case attribute
|
236
|
+
when Symbol then fetch_attribute_from_object_and_resource(object, attribute)
|
237
|
+
when Proc then instance_exec(object, &attribute)
|
238
|
+
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)
|
240
|
+
else
|
241
|
+
raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
|
242
|
+
end
|
243
|
+
value.nil? && nil_handler ? instance_exec(object, key, attribute, &nil_handler) : value
|
244
|
+
end
|
140
245
|
|
141
|
-
|
246
|
+
def fetch_attribute_from_object_and_resource(object, attribute)
|
247
|
+
has_method = @method_existence[attribute]
|
248
|
+
has_method = @method_existence[attribute] = object.respond_to?(attribute) if has_method.nil?
|
249
|
+
has_method ? object.public_send(attribute) : __send__(attribute, object)
|
142
250
|
end
|
143
251
|
|
144
|
-
def
|
145
|
-
|
146
|
-
|
147
|
-
object.public_send attribute
|
148
|
-
when Proc
|
149
|
-
instance_exec(object, &attribute)
|
150
|
-
when Alba::One, Alba::Many
|
151
|
-
within = check_within(attribute.name.to_sym)
|
152
|
-
return unless within
|
252
|
+
def nil_handler
|
253
|
+
@nil_handler ||= (@_on_nil || Alba._on_nil)
|
254
|
+
end
|
153
255
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
else
|
158
|
-
raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
|
159
|
-
end
|
256
|
+
def yield_if_within(association_name)
|
257
|
+
within = check_within(association_name)
|
258
|
+
yield(within) if within
|
160
259
|
end
|
161
260
|
|
162
261
|
def check_within(association_name)
|
163
262
|
case @within
|
164
|
-
when WITHIN_DEFAULT # Default value, doesn't check within tree
|
165
|
-
|
166
|
-
when
|
167
|
-
|
168
|
-
when
|
169
|
-
@within.find { |item| item.to_sym == association_name }
|
170
|
-
when Symbol # within tree could end with Symbol
|
171
|
-
@within == association_name
|
172
|
-
when nil, true, false # In these cases, Alba stops serialization here.
|
173
|
-
false
|
263
|
+
when WITHIN_DEFAULT then WITHIN_DEFAULT # Default value, doesn't check within tree
|
264
|
+
when Hash then @within.fetch(association_name, nil) # Traverse within tree
|
265
|
+
when Array then @within.find { |item| item.to_sym == association_name }
|
266
|
+
when Symbol then @within == association_name
|
267
|
+
when nil, true, false then false # Stop here
|
174
268
|
else
|
175
269
|
raise Alba::Error, "Unknown type for within option: #{@within.class}"
|
176
270
|
end
|
177
271
|
end
|
178
272
|
|
273
|
+
# Detect if object is a collection or not.
|
274
|
+
# When object is a Struct, it's Enumerable but not a collection
|
179
275
|
def collection?
|
180
|
-
@object.is_a?(Enumerable)
|
276
|
+
@object.is_a?(Enumerable) && !@object.is_a?(Struct)
|
181
277
|
end
|
182
278
|
end
|
183
279
|
|
@@ -191,11 +287,15 @@ module Alba
|
|
191
287
|
DSLS.each_key { |name| subclass.instance_variable_set("@#{name}", instance_variable_get("@#{name}").clone) }
|
192
288
|
end
|
193
289
|
|
290
|
+
# Defining methods for DSLs and disable parameter number check since for users' benefits increasing params is fine
|
291
|
+
|
194
292
|
# Set multiple attributes at once
|
195
293
|
#
|
196
294
|
# @param attrs [Array<String, Symbol>]
|
197
|
-
# @param if [
|
198
|
-
# @param attrs_with_types [Hash]
|
295
|
+
# @param if [Proc] condition to decide if it should serialize these attributes
|
296
|
+
# @param attrs_with_types [Hash<[Symbol, String], [Array<Symbol, Proc>, Symbol]>]
|
297
|
+
# attributes with name in its key and type and optional type converter in its value
|
298
|
+
# @return [void]
|
199
299
|
def attributes(*attrs, if: nil, **attrs_with_types) # rubocop:disable Naming/MethodParameterName
|
200
300
|
if_value = binding.local_variable_get(:if)
|
201
301
|
assign_attributes(attrs, if_value)
|
@@ -224,65 +324,109 @@ module Alba
|
|
224
324
|
# Set an attribute with the given block
|
225
325
|
#
|
226
326
|
# @param name [String, Symbol] key name
|
227
|
-
# @param options [Hash]
|
327
|
+
# @param options [Hash<Symbol, Proc>]
|
328
|
+
# @option options [Proc] if a condition to decide if this attribute should be serialized
|
228
329
|
# @param block [Block] the block called during serialization
|
229
330
|
# @raise [ArgumentError] if block is absent
|
331
|
+
# @return [void]
|
230
332
|
def attribute(name, **options, &block)
|
231
333
|
raise ArgumentError, 'No block given in attribute method' unless block
|
232
334
|
|
233
335
|
@_attributes[name.to_sym] = options[:if] ? [block, options[:if]] : block
|
234
336
|
end
|
235
337
|
|
236
|
-
# Set
|
237
|
-
#
|
238
|
-
# @param name [String, Symbol]
|
239
|
-
# @param condition [Proc]
|
240
|
-
# @param resource [Class<Alba::Resource>]
|
241
|
-
# @param key [String, Symbol] used as key when given
|
242
|
-
# @param options [Hash] option hash including `if` that is a condition to render
|
243
|
-
# @param block [Block]
|
244
|
-
# @see Alba::One#initialize
|
245
|
-
def one(name, condition = nil, resource: nil, key: nil, **options, &block)
|
246
|
-
nesting = self.name&.rpartition('::')&.first
|
247
|
-
one = One.new(name: name, condition: condition, resource: resource, nesting: nesting, &block)
|
248
|
-
@_attributes[key&.to_sym || name.to_sym] = options[:if] ? [one, options[:if]] : one
|
249
|
-
end
|
250
|
-
alias has_one one
|
251
|
-
|
252
|
-
# Set Many association
|
338
|
+
# Set association
|
253
339
|
#
|
254
|
-
# @param name [String, Symbol]
|
255
|
-
# @param condition [Proc]
|
256
|
-
# @param resource [Class<Alba::Resource
|
257
|
-
# @param key [String, Symbol] used as key when given
|
258
|
-
# @param options [Hash]
|
340
|
+
# @param name [String, Symbol] name of the association, used as key when `key` param doesn't exist
|
341
|
+
# @param condition [Proc, nil] a Proc to modify the association
|
342
|
+
# @param resource [Class<Alba::Resource>, String, nil] representing resource for this association
|
343
|
+
# @param key [String, Symbol, nil] used as key when given
|
344
|
+
# @param options [Hash<Symbol, Proc>]
|
345
|
+
# @option options [Proc] if a condition to decide if this association should be serialized
|
259
346
|
# @param block [Block]
|
260
|
-
# @
|
261
|
-
|
347
|
+
# @return [void]
|
348
|
+
# @see Alba::Association#initialize
|
349
|
+
def association(name, condition = nil, resource: nil, key: nil, **options, &block)
|
262
350
|
nesting = self.name&.rpartition('::')&.first
|
263
|
-
|
264
|
-
@_attributes[key&.to_sym || name.to_sym] = options[:if] ? [
|
351
|
+
assoc = Association.new(name: name, condition: condition, resource: resource, nesting: nesting, &block)
|
352
|
+
@_attributes[key&.to_sym || name.to_sym] = options[:if] ? [assoc, options[:if]] : assoc
|
265
353
|
end
|
266
|
-
alias
|
354
|
+
alias one association
|
355
|
+
alias many association
|
356
|
+
alias has_one association
|
357
|
+
alias has_many association
|
267
358
|
|
268
359
|
# Set key
|
269
360
|
#
|
270
361
|
# @param key [String, Symbol]
|
362
|
+
# @deprecated Use {#root_key} instead
|
271
363
|
def key(key)
|
364
|
+
Alba::Deprecation.warn '[DEPRECATION] `key` is deprecated, use `root_key` instead.'
|
272
365
|
@_key = key.respond_to?(:to_sym) ? key.to_sym : key
|
273
366
|
end
|
274
367
|
|
368
|
+
# Set root key
|
369
|
+
#
|
370
|
+
# @param key [String, Symbol]
|
371
|
+
# @param key_for_collection [String, Symbol]
|
372
|
+
# @raise [NoMethodError] when key doesn't respond to `to_sym` method
|
373
|
+
def root_key(key, key_for_collection = nil)
|
374
|
+
@_key = key.to_sym
|
375
|
+
@_key_for_collection = key_for_collection&.to_sym
|
376
|
+
end
|
377
|
+
|
275
378
|
# Set key to true
|
276
379
|
#
|
380
|
+
# @deprecated Use {#root_key!} instead
|
277
381
|
def key!
|
382
|
+
Alba::Deprecation.warn '[DEPRECATION] `key!` is deprecated, use `root_key!` instead.'
|
278
383
|
@_key = true
|
384
|
+
@_key_for_collection = true
|
279
385
|
end
|
280
386
|
|
387
|
+
# Set root key to true
|
388
|
+
def root_key!
|
389
|
+
@_key = true
|
390
|
+
@_key_for_collection = true
|
391
|
+
end
|
392
|
+
|
393
|
+
# Set metadata
|
394
|
+
def meta(&block)
|
395
|
+
@_meta = block
|
396
|
+
end
|
397
|
+
|
398
|
+
# Set layout
|
399
|
+
#
|
400
|
+
# @params file [String] name of the layout file
|
401
|
+
# @params inline [Proc] a proc returning JSON string or a Hash representing JSON
|
402
|
+
def layout(file: nil, inline: nil)
|
403
|
+
@_layout = validated_file_layout(file) || validated_inline_layout(inline)
|
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
|
+
|
281
424
|
# Delete attributes
|
282
425
|
# Use this DSL in child class to ignore certain attributes
|
283
426
|
#
|
284
427
|
# @param attributes [Array<String, Symbol>]
|
285
428
|
def ignoring(*attributes)
|
429
|
+
Alba::Deprecation.warn '`ignoring` is deprecated now. Instead please use `attributes` instance method to filter out attributes.'
|
286
430
|
attributes.each do |attr_name|
|
287
431
|
@_attributes.delete(attr_name.to_sym)
|
288
432
|
end
|
@@ -290,23 +434,39 @@ module Alba
|
|
290
434
|
|
291
435
|
# Transform keys as specified type
|
292
436
|
#
|
293
|
-
# @param type [String, Symbol]
|
294
|
-
# @param root [Boolean] decides if root key also should be transformed
|
437
|
+
# @param type [String, Symbol] one of `snake`, `:camel`, `:lower_camel`, `:dash` and `none`
|
438
|
+
# @param root [Boolean, nil] decides if root key also should be transformed
|
439
|
+
# When it's `nil`, Alba's default setting will be applied
|
440
|
+
# @raise [Alba::Error] when type is not supported
|
295
441
|
def transform_keys(type, root: nil)
|
296
|
-
|
442
|
+
type = type.to_sym
|
443
|
+
unless %i[none snake camel lower_camel dash].include?(type)
|
444
|
+
# This should be `ArgumentError` but for backward compatibility it raises `Alba::Error`
|
445
|
+
raise ::Alba::Error, "Unknown transform type: #{type}. Supported type are :camel, :lower_camel and :dash."
|
446
|
+
end
|
447
|
+
|
448
|
+
@_transform_type = type
|
297
449
|
@_transforming_root_key = root
|
298
450
|
end
|
299
451
|
|
300
452
|
# Set error handler
|
453
|
+
# If this is set it's used as a error handler overriding global one
|
301
454
|
#
|
302
|
-
# @param [Symbol]
|
303
|
-
# @param [Block]
|
455
|
+
# @param handler [Symbol] `:raise`, `:ignore` or `:nullify`
|
456
|
+
# @param block [Block]
|
304
457
|
def on_error(handler = nil, &block)
|
305
458
|
raise ArgumentError, 'You cannot specify error handler with both Symbol and block' if handler && block
|
306
459
|
raise ArgumentError, 'You must specify error handler with either Symbol or block' unless handler || block
|
307
460
|
|
308
461
|
@_on_error = handler || block
|
309
462
|
end
|
463
|
+
|
464
|
+
# Set nil handler
|
465
|
+
#
|
466
|
+
# @param block [Block]
|
467
|
+
def on_nil(&block)
|
468
|
+
@_on_nil = block
|
469
|
+
end
|
310
470
|
end
|
311
471
|
end
|
312
472
|
end
|
data/lib/alba/typed_attribute.rb
CHANGED
@@ -28,12 +28,9 @@ module Alba
|
|
28
28
|
def check(object)
|
29
29
|
value = object.public_send(@name)
|
30
30
|
type_correct = case @type
|
31
|
-
when :String, ->(klass) { klass == String }
|
32
|
-
|
33
|
-
when :
|
34
|
-
value.is_a?(Integer)
|
35
|
-
when :Boolean
|
36
|
-
[true, false].include?(value)
|
31
|
+
when :String, ->(klass) { klass == String } then value.is_a?(String)
|
32
|
+
when :Integer, ->(klass) { klass == Integer } then value.is_a?(Integer)
|
33
|
+
when :Boolean then [true, false].include?(value)
|
37
34
|
else
|
38
35
|
raise Alba::UnsupportedType, "Unknown type: #{@type}"
|
39
36
|
end
|
data/lib/alba/version.rb
CHANGED