alba 1.3.0 → 1.6.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/.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