alba 1.5.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.
@@ -15,7 +15,9 @@ gemfile(true) do
15
15
  gem "benchmark-ips"
16
16
  gem "benchmark-memory"
17
17
  gem "blueprinter"
18
+ gem "fast_serializer_ruby"
18
19
  gem "jbuilder"
20
+ gem 'turbostreamer'
19
21
  gem "jserializer"
20
22
  gem "jsonapi-serializer" # successor of fast_jsonapi
21
23
  gem "multi_json"
@@ -138,6 +140,27 @@ class PostBlueprint < Blueprinter::Base
138
140
  end
139
141
  end
140
142
 
143
+ # --- Fast Serializer Ruby
144
+
145
+ require "fast_serializer"
146
+
147
+ class FastSerializerCommentResource
148
+ include ::FastSerializer::Schema::Mixin
149
+ attributes :id, :body
150
+ end
151
+
152
+ class FastSerializerPostResource
153
+ include ::FastSerializer::Schema::Mixin
154
+
155
+ attributes :id, :body
156
+
157
+ attribute :commenter_names do
158
+ object.commenters.pluck(:name)
159
+ end
160
+
161
+ has_many :comments, serializer: FastSerializerCommentResource
162
+ end
163
+
141
164
  # --- JBuilder serializers ---
142
165
 
143
166
  require "jbuilder"
@@ -255,7 +278,7 @@ class PankoPostSerializer < Panko::Serializer
255
278
  has_many :comments, serializer: PankoCommentSerializer
256
279
 
257
280
  def commenter_names
258
- object.comments.pluck(:name)
281
+ object.commenters.pluck(:name)
259
282
  end
260
283
  end
261
284
 
@@ -332,6 +355,31 @@ class SimpleAMSPostSerializer
332
355
  end
333
356
  end
334
357
 
358
+ require 'turbostreamer'
359
+ TurboStreamer.set_default_encoder(:json, :oj)
360
+
361
+ class TurbostreamerSerializer
362
+ def initialize(posts)
363
+ @posts = posts
364
+ end
365
+
366
+ def to_json
367
+ TurboStreamer.encode do |json|
368
+ json.array! @posts do |post|
369
+ json.object! do
370
+ json.extract! post, :id, :body, :commenter_names
371
+
372
+ json.comments post.comments do |comment|
373
+ json.object! do
374
+ json.extract! comment, :id, :body
375
+ end
376
+ end
377
+ end
378
+ end
379
+ end
380
+ end
381
+ end
382
+
335
383
  # --- Test data creation ---
336
384
 
337
385
  100.times do |i|
@@ -344,7 +392,7 @@ end
344
392
  end
345
393
  end
346
394
 
347
- posts = Post.all.to_a
395
+ posts = Post.all.includes(:comments, :commenters)
348
396
 
349
397
  # --- Store the serializers in procs ---
350
398
 
@@ -360,8 +408,9 @@ alba_inline = Proc.new do
360
408
  end
361
409
  end
362
410
  end
363
- ams = Proc.new { ActiveModelSerializers::SerializableResource.new(posts, {}).as_json }
411
+ ams = Proc.new { ActiveModelSerializers::SerializableResource.new(posts, {each_serializer: AMSPostSerializer}).to_json }
364
412
  blueprinter = Proc.new { PostBlueprint.render(posts) }
413
+ fast_serializer = Proc.new { FastSerializerPostResource.new(posts).to_json }
365
414
  jbuilder = Proc.new do
366
415
  Jbuilder.new do |json|
367
416
  json.array!(posts) do |post|
@@ -379,15 +428,17 @@ rails = Proc.new do
379
428
  end
380
429
  representable = Proc.new { PostsRepresenter.new(posts).to_json }
381
430
  simple_ams = Proc.new { SimpleAMS::Renderer::Collection.new(posts, serializer: SimpleAMSPostSerializer).to_json }
431
+ turbostreamer = Proc.new { TurbostreamerSerializer.new(posts).to_json }
382
432
 
383
433
  # --- Execute the serializers to check their output ---
384
-
434
+ GC.disable
385
435
  puts "Serializer outputs ----------------------------------"
386
436
  {
387
437
  alba: alba,
388
438
  alba_inline: alba_inline,
389
439
  ams: ams,
390
440
  blueprinter: blueprinter,
441
+ fast_serializer: fast_serializer,
391
442
  jbuilder: jbuilder, # different order
392
443
  jserializer: jserializer,
393
444
  jsonapi: jsonapi, # nested JSON:API format
@@ -397,6 +448,7 @@ puts "Serializer outputs ----------------------------------"
397
448
  rails: rails,
398
449
  representable: representable,
399
450
  simple_ams: simple_ams,
451
+ turbostreamer: turbostreamer
400
452
  }.each { |name, serializer| puts "#{name.to_s.ljust(24, ' ')} #{serializer.call}" }
401
453
 
402
454
  # --- Run the benchmarks ---
@@ -407,6 +459,7 @@ Benchmark.ips do |x|
407
459
  x.report(:alba_inline, &alba_inline)
408
460
  x.report(:ams, &ams)
409
461
  x.report(:blueprinter, &blueprinter)
462
+ x.report(:fast_serializer, &fast_serializer)
410
463
  x.report(:jbuilder, &jbuilder)
411
464
  x.report(:jserializer, &jserializer)
412
465
  x.report(:jsonapi, &jsonapi)
@@ -416,6 +469,7 @@ Benchmark.ips do |x|
416
469
  x.report(:rails, &rails)
417
470
  x.report(:representable, &representable)
418
471
  x.report(:simple_ams, &simple_ams)
472
+ x.report(:turbostreamer, &turbostreamer)
419
473
 
420
474
  x.compare!
421
475
  end
@@ -427,6 +481,7 @@ Benchmark.memory do |x|
427
481
  x.report(:alba_inline, &alba_inline)
428
482
  x.report(:ams, &ams)
429
483
  x.report(:blueprinter, &blueprinter)
484
+ x.report(:fast_serializer, &fast_serializer)
430
485
  x.report(:jbuilder, &jbuilder)
431
486
  x.report(:jserializer, &jserializer)
432
487
  x.report(:jsonapi, &jsonapi)
@@ -436,6 +491,7 @@ Benchmark.memory do |x|
436
491
  x.report(:rails, &rails)
437
492
  x.report(:representable, &representable)
438
493
  x.report(:simple_ams, &simple_ams)
494
+ x.report(:turbostreamer, &turbostreamer)
439
495
 
440
496
  x.compare!
441
497
  end
@@ -16,6 +16,7 @@ gemfile(true) do
16
16
  gem "benchmark-memory"
17
17
  gem "blueprinter"
18
18
  gem "jbuilder"
19
+ gem 'turbostreamer'
19
20
  gem "jserializer"
20
21
  gem "jsonapi-serializer" # successor of fast_jsonapi
21
22
  gem "multi_json"
@@ -253,7 +254,7 @@ class PankoPostSerializer < Panko::Serializer
253
254
  has_many :comments, serializer: PankoCommentSerializer
254
255
 
255
256
  def commenter_names
256
- object.comments.pluck(:name)
257
+ object.commenters.pluck(:name)
257
258
  end
258
259
  end
259
260
 
@@ -324,6 +325,29 @@ class SimpleAMSPostSerializer
324
325
  end
325
326
  end
326
327
 
328
+ require 'turbostreamer'
329
+ TurboStreamer.set_default_encoder(:json, :oj)
330
+
331
+ class TurbostreamerSerializer
332
+ def initialize(post)
333
+ @post = post
334
+ end
335
+
336
+ def to_json
337
+ TurboStreamer.encode do |json|
338
+ json.object! do
339
+ json.extract! @post, :id, :body, :commenter_names
340
+
341
+ json.comments @post.comments do |comment|
342
+ json.object! do
343
+ json.extract! comment, :id, :body
344
+ end
345
+ end
346
+ end
347
+ end
348
+ end
349
+ end
350
+
327
351
  # --- Test data creation ---
328
352
 
329
353
  post = Post.create!(body: 'post')
@@ -347,6 +371,7 @@ alba_inline = Proc.new do
347
371
  end
348
372
  end
349
373
  end
374
+
350
375
  ams = Proc.new { AMSPostSerializer.new(post, {}).to_json }
351
376
  blueprinter = Proc.new { PostBlueprint.render(post) }
352
377
  jbuilder = Proc.new { post.to_builder.target! }
@@ -358,6 +383,7 @@ primalize = proc { PrimalizePostResource.new(post).to_json }
358
383
  rails = Proc.new { ActiveSupport::JSON.encode(post.serializable_hash(include: :comments)) }
359
384
  representable = Proc.new { PostRepresenter.new(post).to_json }
360
385
  simple_ams = Proc.new { SimpleAMS::Renderer.new(post, serializer: SimpleAMSPostSerializer).to_json }
386
+ turbostreamer = Proc.new { TurbostreamerSerializer.new(post).to_json }
361
387
 
362
388
  # --- Execute the serializers to check their output ---
363
389
 
@@ -376,7 +402,10 @@ puts "Serializer outputs ----------------------------------"
376
402
  rails: rails,
377
403
  representable: representable,
378
404
  simple_ams: simple_ams,
379
- }.each { |name, serializer| puts "#{name.to_s.ljust(24, ' ')} #{serializer.call}" }
405
+ turbostreamer: turbostreamer
406
+ }.each do |name, serializer|
407
+ puts "#{name.to_s.ljust(24, ' ')} #{serializer.call}"
408
+ end
380
409
 
381
410
  # --- Run the benchmarks ---
382
411
 
@@ -395,6 +424,7 @@ Benchmark.ips do |x|
395
424
  x.report(:rails, &rails)
396
425
  x.report(:representable, &representable)
397
426
  x.report(:simple_ams, &simple_ams)
427
+ x.report(:turbostreamer, &turbostreamer)
398
428
 
399
429
  x.compare!
400
430
  end
@@ -415,6 +445,7 @@ Benchmark.memory do |x|
415
445
  x.report(:rails, &rails)
416
446
  x.report(:representable, &representable)
417
447
  x.report(:simple_ams, &simple_ams)
448
+ x.report(:turbostreamer, &turbostreamer)
418
449
 
419
450
  x.compare!
420
451
  end
data/gemfiles/all.gemfile CHANGED
@@ -1,6 +1,7 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gem 'activesupport', require: false # For backend
4
+ gem 'dry-inflector', require: false # For inflector
4
5
  gem 'ffaker', require: false # For testing
5
6
  gem 'minitest', '~> 5.14' # For test
6
7
  gem 'rake', '~> 13.0' # For test and automation
@@ -1,7 +1,11 @@
1
1
  module Alba
2
- # Base class for `One` and `Many`
3
- # Child class should implement `to_hash` method
2
+ # Representing association
4
3
  class Association
4
+ @const_cache = {}
5
+ class << self
6
+ attr_reader :const_cache
7
+ end
8
+
5
9
  attr_reader :object, :name
6
10
 
7
11
  # @param name [Symbol, String] name of the method to fetch association
@@ -12,11 +16,25 @@ module Alba
12
16
  def initialize(name:, condition: nil, resource: nil, nesting: nil, &block)
13
17
  @name = name
14
18
  @condition = condition
15
- @block = block
16
19
  @resource = resource
17
20
  return if @resource
18
21
 
19
- assign_resource(nesting)
22
+ assign_resource(nesting, block)
23
+ end
24
+
25
+ # Recursively converts an object into a Hash
26
+ #
27
+ # @param target [Object] the object having an association method
28
+ # @param within [Hash] determines what associations to be serialized. If not set, it serializes all associations.
29
+ # @param params [Hash] user-given Hash for arbitrary data
30
+ # @return [Hash]
31
+ def to_h(target, within: nil, params: {})
32
+ @object = target.public_send(@name)
33
+ @object = @condition.call(object, params) if @condition
34
+ return if @object.nil?
35
+
36
+ @resource = constantize(@resource)
37
+ @resource.new(object, params: params, within: within).to_h
20
38
  end
21
39
 
22
40
  private
@@ -26,13 +44,15 @@ module Alba
26
44
  when Class
27
45
  resource
28
46
  when Symbol, String
29
- Object.const_get(resource)
47
+ self.class.const_cache.fetch(resource) do
48
+ self.class.const_cache[resource] = Object.const_get(resource)
49
+ end
30
50
  end
31
51
  end
32
52
 
33
- def assign_resource(nesting)
34
- @resource = if @block
35
- Alba.resource_class(&@block)
53
+ def assign_resource(nesting, block)
54
+ @resource = if block
55
+ Alba.resource_class(&block)
36
56
  elsif Alba.inferring
37
57
  Alba.infer_resource_class(@name, nesting: nesting)
38
58
  else
@@ -1,5 +1,7 @@
1
1
  module Alba
2
- # This module represents the inflector, which is used by default
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'
@@ -32,5 +34,21 @@ module Alba
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,10 @@
1
+ module Alba
2
+ # Base class for Errors
3
+ class Error < StandardError; end
4
+
5
+ # Error class for backend which is not supported
6
+ class UnsupportedBackend < Error; end
7
+
8
+ # Error class for type which is not supported
9
+ class UnsupportedType < Error; end
10
+ end
data/lib/alba/resource.rb CHANGED
@@ -1,6 +1,4 @@
1
- require_relative 'one'
2
- require_relative 'many'
3
- require_relative 'key_transform_factory'
1
+ require_relative 'association'
4
2
  require_relative 'typed_attribute'
5
3
  require_relative 'deprecation'
6
4
 
@@ -9,7 +7,7 @@ module Alba
9
7
  module Resource
10
8
  # @!parse include InstanceMethods
11
9
  # @!parse extend ClassMethods
12
- DSLS = {_attributes: {}, _key: nil, _key_for_collection: nil, _meta: nil, _transform_key_function: nil, _transforming_root_key: false, _on_error: nil, _on_nil: nil, _layout: nil}.freeze # rubocop:disable Layout/LineLength
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
13
11
  private_constant :DSLS
14
12
 
15
13
  WITHIN_DEFAULT = Object.new.freeze
@@ -39,6 +37,7 @@ module Alba
39
37
  @object = object
40
38
  @params = params.freeze
41
39
  @within = within
40
+ @method_existence = {} # Cache for `respond_to?` result
42
41
  DSLS.each_key { |name| instance_variable_set("@#{name}", self.class.public_send(name)) }
43
42
  end
44
43
 
@@ -67,7 +66,13 @@ module Alba
67
66
  def serializable_hash
68
67
  collection? ? @object.map(&converter) : converter.call(@object)
69
68
  end
70
- alias to_hash serializable_hash
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
71
76
 
72
77
  private
73
78
 
@@ -78,22 +83,33 @@ module Alba
78
83
  end
79
84
 
80
85
  def serialize_with(hash)
81
- @serialized_json = encode(hash)
82
- case @_layout
83
- when String # file
86
+ serialized_json = encode(hash)
87
+ return serialized_json unless @_layout
88
+
89
+ @serialized_json = serialized_json
90
+ if @_layout.is_a?(String) # file
84
91
  ERB.new(File.read(@_layout)).result(binding)
85
- when Proc # inline
86
- inline = instance_eval(&@_layout)
87
- inline.is_a?(Hash) ? encode(inline) : inline
88
- else # no layout
89
- @serialized_json
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'
90
105
  end
91
106
  end
92
107
 
93
108
  def hash_with_metadata(hash, meta)
94
- base = @_meta ? instance_eval(&@_meta) : {}
95
- metadata = base.merge(meta)
96
- hash[:meta] = metadata unless metadata.empty?
109
+ return hash if meta.empty? && @_meta.nil?
110
+
111
+ metadata = @_meta ? instance_eval(&@_meta).merge(meta) : meta
112
+ hash[:meta] = metadata
97
113
  hash
98
114
  end
99
115
 
@@ -116,25 +132,34 @@ module Alba
116
132
  end
117
133
 
118
134
  def resource_name
119
- self.class.name.demodulize.delete_suffix('Resource').underscore
135
+ @resource_name ||= self.class.name.demodulize.delete_suffix('Resource').underscore
120
136
  end
121
137
 
122
138
  def transforming_root_key?
123
139
  @_transforming_root_key.nil? ? Alba.transforming_root_key : @_transforming_root_key
124
140
  end
125
141
 
142
+ # rubocop:disable Metrics/MethodLength
126
143
  def converter
127
144
  lambda do |object|
128
- arrays = @_attributes.map do |key, attribute|
145
+ arrays = attributes.map do |key, attribute|
129
146
  key_and_attribute_body_from(object, key, attribute)
130
147
  rescue ::Alba::Error, FrozenError, TypeError
131
148
  raise
132
149
  rescue StandardError => e
133
150
  handle_error(e, object, key, attribute)
134
151
  end
135
- arrays.reject(&:empty?).to_h
152
+ arrays.compact!
153
+ arrays.to_h
136
154
  end
137
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
138
163
 
139
164
  def key_and_attribute_body_from(object, key, attribute)
140
165
  key = transform_key(key)
@@ -158,17 +183,17 @@ module Alba
158
183
  def conditional_attribute_with_proc(object, key, attribute, condition)
159
184
  arity = condition.arity
160
185
  # We can return early to skip fetch_attribute
161
- return [] if arity <= 1 && !instance_exec(object, &condition)
186
+ return if arity <= 1 && !instance_exec(object, &condition)
162
187
 
163
188
  fetched_attribute = fetch_attribute(object, key, attribute)
164
189
  attr = attribute.is_a?(Alba::Association) ? attribute.object : fetched_attribute
165
- return [] if arity >= 2 && !instance_exec(object, attr, &condition)
190
+ return if arity >= 2 && !instance_exec(object, attr, &condition)
166
191
 
167
192
  [key, fetched_attribute]
168
193
  end
169
194
 
170
195
  def conditional_attribute_with_symbol(object, key, attribute, condition)
171
- return [] unless __send__(condition)
196
+ return unless __send__(condition)
172
197
 
173
198
  [key, fetch_attribute(object, key, attribute)]
174
199
  end
@@ -178,25 +203,39 @@ module Alba
178
203
  case on_error
179
204
  when :raise, nil then raise
180
205
  when :nullify then [key, nil]
181
- when :ignore then []
206
+ when :ignore then nil
182
207
  when Proc then on_error.call(error, object, key, attribute, self.class)
183
208
  else
184
209
  raise ::Alba::Error, "Unknown on_error: #{on_error.inspect}"
185
210
  end
186
211
  end
187
212
 
188
- # Override this method to supply custom key transform method
213
+ # rubocop:disable Metrics/MethodLength
214
+ # @return [Symbol]
189
215
  def transform_key(key)
190
- return key if @_transform_key_function.nil?
191
-
192
- @_transform_key_function.call(key.to_s)
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
193
231
  end
232
+ # rubocop:enable Metrics/MethodLength
194
233
 
195
234
  def fetch_attribute(object, key, attribute)
196
235
  value = case attribute
197
- when Symbol then object.public_send attribute
236
+ when Symbol then fetch_attribute_from_object_and_resource(object, attribute)
198
237
  when Proc then instance_exec(object, &attribute)
199
- when Alba::One, Alba::Many then yield_if_within(attribute.name.to_sym) { |within| attribute.to_hash(object, params: params, within: within) }
238
+ when Alba::Association then yield_if_within(attribute.name.to_sym) { |within| attribute.to_h(object, params: params, within: within) }
200
239
  when TypedAttribute then attribute.value(object)
201
240
  else
202
241
  raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
@@ -204,6 +243,12 @@ module Alba
204
243
  value.nil? && nil_handler ? instance_exec(object, key, attribute, &nil_handler) : value
205
244
  end
206
245
 
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)
250
+ end
251
+
207
252
  def nil_handler
208
253
  @nil_handler ||= (@_on_nil || Alba._on_nil)
209
254
  end
@@ -225,8 +270,10 @@ module Alba
225
270
  end
226
271
  end
227
272
 
273
+ # Detect if object is a collection or not.
274
+ # When object is a Struct, it's Enumerable but not a collection
228
275
  def collection?
229
- @object.is_a?(Enumerable)
276
+ @object.is_a?(Enumerable) && !@object.is_a?(Struct)
230
277
  end
231
278
  end
232
279
 
@@ -241,7 +288,6 @@ module Alba
241
288
  end
242
289
 
243
290
  # Defining methods for DSLs and disable parameter number check since for users' benefits increasing params is fine
244
- # rubocop:disable Metrics/ParameterLists
245
291
 
246
292
  # Set multiple attributes at once
247
293
  #
@@ -289,7 +335,7 @@ module Alba
289
335
  @_attributes[name.to_sym] = options[:if] ? [block, options[:if]] : block
290
336
  end
291
337
 
292
- # Set One association
338
+ # Set association
293
339
  #
294
340
  # @param name [String, Symbol] name of the association, used as key when `key` param doesn't exist
295
341
  # @param condition [Proc, nil] a Proc to modify the association
@@ -299,31 +345,16 @@ module Alba
299
345
  # @option options [Proc] if a condition to decide if this association should be serialized
300
346
  # @param block [Block]
301
347
  # @return [void]
302
- # @see Alba::One#initialize
303
- def one(name, condition = nil, resource: nil, key: nil, **options, &block)
348
+ # @see Alba::Association#initialize
349
+ def association(name, condition = nil, resource: nil, key: nil, **options, &block)
304
350
  nesting = self.name&.rpartition('::')&.first
305
- one = One.new(name: name, condition: condition, resource: resource, nesting: nesting, &block)
306
- @_attributes[key&.to_sym || name.to_sym] = options[:if] ? [one, options[:if]] : one
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
307
353
  end
308
- alias has_one one
309
-
310
- # Set Many association
311
- #
312
- # @param name [String, Symbol] name of the association, used as key when `key` param doesn't exist
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
316
- # @param options [Hash<Symbol, Proc>]
317
- # @option options [Proc] if a condition to decide if this association should be serialized
318
- # @param block [Block]
319
- # @return [void]
320
- # @see Alba::Many#initialize
321
- def many(name, condition = nil, resource: nil, key: nil, **options, &block)
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
354
+ alias one association
355
+ alias many association
356
+ alias has_one association
357
+ alias has_many association
327
358
 
328
359
  # Set key
329
360
  #
@@ -369,14 +400,33 @@ module Alba
369
400
  # @params file [String] name of the layout file
370
401
  # @params inline [Proc] a proc returning JSON string or a Hash representing JSON
371
402
  def layout(file: nil, inline: nil)
372
- @_layout = file || inline
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
373
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
374
423
 
375
424
  # Delete attributes
376
425
  # Use this DSL in child class to ignore certain attributes
377
426
  #
378
427
  # @param attributes [Array<String, Symbol>]
379
428
  def ignoring(*attributes)
429
+ Alba::Deprecation.warn '`ignoring` is deprecated now. Instead please use `attributes` instance method to filter out attributes.'
380
430
  attributes.each do |attr_name|
381
431
  @_attributes.delete(attr_name.to_sym)
382
432
  end
@@ -384,10 +434,18 @@ module Alba
384
434
 
385
435
  # Transform keys as specified type
386
436
  #
387
- # @param type [String, Symbol]
388
- # @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
389
441
  def transform_keys(type, root: nil)
390
- @_transform_key_function = KeyTransformFactory.create(type.to_sym)
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
391
449
  @_transforming_root_key = root
392
450
  end
393
451
 
@@ -409,8 +467,6 @@ module Alba
409
467
  def on_nil(&block)
410
468
  @_on_nil = block
411
469
  end
412
-
413
- # rubocop:enable Metrics/ParameterLists
414
470
  end
415
471
  end
416
472
  end
data/lib/alba/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Alba
2
- VERSION = '1.5.0'.freeze
2
+ VERSION = '1.6.0'.freeze
3
3
  end