alba 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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