alba 0.12.0 → 1.1.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.
@@ -2,25 +2,40 @@ module Alba
2
2
  # Base class for `One` and `Many`
3
3
  # Child class should implement `to_hash` method
4
4
  class Association
5
+ attr_reader :object
6
+
5
7
  # @param name [Symbol] name of the method to fetch association
6
8
  # @param condition [Proc] a proc filtering data
7
9
  # @param resource [Class<Alba::Resource>] a resource class for the association
8
10
  # @param block [Block] used to define resource when resource arg is absent
9
- def initialize(name:, condition: nil, resource: nil, &block)
11
+ def initialize(name:, condition: nil, resource: nil, nesting: nil, &block)
10
12
  @name = name
11
13
  @condition = condition
12
14
  @block = block
13
- @resource = resource || resource_class
14
- raise ArgumentError, 'resource or block is required' if @resource.nil? && @block.nil?
15
- end
15
+ @resource = resource
16
+ return if @resource
16
17
 
17
- # @abstract
18
- def to_hash
19
- :not_implemented
18
+ if @block
19
+ @resource = resource_class
20
+ elsif Alba.inferring
21
+ const_parent = nesting.nil? ? Object : Object.const_get(nesting)
22
+ @resource = const_parent.const_get("#{ActiveSupport::Inflector.classify(@name)}Resource")
23
+ else
24
+ raise ArgumentError, 'When Alba.inferring is false, either resource or block is required'
25
+ end
20
26
  end
21
27
 
22
28
  private
23
29
 
30
+ def constantize(resource)
31
+ case resource # rubocop:disable Style/MissingElse
32
+ when Class
33
+ resource
34
+ when Symbol, String
35
+ Object.const_get(resource)
36
+ end
37
+ end
38
+
24
39
  def resource_class
25
40
  klass = Class.new
26
41
  klass.include(Alba::Resource)
@@ -0,0 +1,32 @@
1
+ module Alba
2
+ # Transform keys using `ActiveSupport::Inflector`
3
+ module KeyTransformer
4
+ begin
5
+ require 'active_support/inflector'
6
+ rescue LoadError
7
+ raise ::Alba::Error, 'To use transform_keys, please install `ActiveSupport` gem.'
8
+ end
9
+
10
+ module_function
11
+
12
+ # Transform key as given transform_type
13
+ #
14
+ # @params key [String] key to be transformed
15
+ # @params transform_type [Symbol] transform type
16
+ # @return [String] transformed key
17
+ # @raise [Alba::Error] when transform_type is not supported
18
+ def transform(key, transform_type)
19
+ key = key.to_s
20
+ case transform_type
21
+ when :camel
22
+ ActiveSupport::Inflector.camelize(key)
23
+ when :lower_camel
24
+ ActiveSupport::Inflector.camelize(key, false)
25
+ when :dash
26
+ ActiveSupport::Inflector.dasherize(key)
27
+ else
28
+ raise ::Alba::Error, "Unknown transform_type: #{transform_type}. Supported transform_type are :camel, :lower_camel and :dash."
29
+ end
30
+ end
31
+ end
32
+ end
data/lib/alba/many.rb CHANGED
@@ -6,12 +6,16 @@ module Alba
6
6
  # Recursively converts objects into an Array of Hashes
7
7
  #
8
8
  # @param target [Object] the object having an association method
9
+ # @param within [Hash] determines what associations to be serialized. If not set, it serializes all associations.
9
10
  # @param params [Hash] user-given Hash for arbitrary data
10
11
  # @return [Array<Hash>]
11
- def to_hash(target, params: {})
12
- objects = target.public_send(@name)
13
- objects = @condition.call(objects, params) if @condition
14
- objects.map { |o| @resource.new(o, params: params).to_hash }
12
+ def to_hash(target, within: nil, params: {})
13
+ @object = target.public_send(@name)
14
+ @object = @condition.call(@object, params) if @condition
15
+ return if @object.nil?
16
+
17
+ @resource = constantize(@resource)
18
+ @object.map { |o| @resource.new(o, params: params, within: within).to_hash }
15
19
  end
16
20
  end
17
21
  end
data/lib/alba/one.rb CHANGED
@@ -6,12 +6,16 @@ module Alba
6
6
  # Recursively converts an object into a Hash
7
7
  #
8
8
  # @param target [Object] the object having an association method
9
+ # @param within [Hash] determines what associations to be serialized. If not set, it serializes all associations.
9
10
  # @param params [Hash] user-given Hash for arbitrary data
10
11
  # @return [Hash]
11
- def to_hash(target, params: {})
12
- object = target.public_send(@name)
13
- object = @condition.call(object, params) if @condition
14
- @resource.new(object, params: params).to_hash
12
+ def to_hash(target, within: nil, params: {})
13
+ @object = target.public_send(@name)
14
+ @object = @condition.call(object, params) if @condition
15
+ return if @object.nil?
16
+
17
+ @resource = constantize(@resource)
18
+ @resource.new(object, params: params, within: within).to_hash
15
19
  end
16
20
  end
17
21
  end
data/lib/alba/resource.rb CHANGED
@@ -1,4 +1,3 @@
1
- require_relative 'serializer'
2
1
  require_relative 'one'
3
2
  require_relative 'many'
4
3
 
@@ -7,7 +6,7 @@ module Alba
7
6
  module Resource
8
7
  # @!parse include InstanceMethods
9
8
  # @!parse extend ClassMethods
10
- DSLS = {_attributes: {}, _serializer: nil, _key: nil}.freeze
9
+ DSLS = {_attributes: {}, _key: nil, _transform_keys: nil, _on_error: nil}.freeze
11
10
  private_constant :DSLS
12
11
 
13
12
  # @private
@@ -25,32 +24,26 @@ module Alba
25
24
 
26
25
  # Instance methods
27
26
  module InstanceMethods
28
- attr_reader :object, :_key, :params
27
+ attr_reader :object, :params
29
28
 
30
29
  # @param object [Object] the object to be serialized
31
30
  # @param params [Hash] user-given Hash for arbitrary data
32
- def initialize(object, params: {})
31
+ # @param within [Hash] determines what associations to be serialized. If not set, it serializes all associations.
32
+ def initialize(object, params: {}, within: true)
33
33
  @object = object
34
34
  @params = params.freeze
35
+ @within = within
35
36
  DSLS.each_key { |name| instance_variable_set("@#{name}", self.class.public_send(name)) }
36
37
  end
37
38
 
38
- # Get serializer with `with` argument and serialize self with it
39
+ # Serialize object into JSON string
39
40
  #
40
- # @param with [nil, Proc, Alba::Serializer] selializer
41
+ # @param key [Symbol]
41
42
  # @return [String] serialized JSON string
42
- def serialize(with: nil)
43
- serializer = case with
44
- when nil
45
- @_serializer || empty_serializer
46
- when ->(obj) { obj.is_a?(Class) && obj <= Alba::Serializer }
47
- with
48
- when Proc
49
- inline_extended_serializer(with)
50
- else
51
- raise ArgumentError, 'Unexpected type for with, possible types are Class or Proc'
52
- end
53
- serializer.new(self).serialize
43
+ def serialize(key: nil)
44
+ key = key.nil? ? _key : key
45
+ hash = key && key != '' ? {key.to_s => serializable_hash} : serializable_hash
46
+ Alba.encoder.call(hash)
54
47
  end
55
48
 
56
49
  # A Hash for serialization
@@ -61,40 +54,108 @@ module Alba
61
54
  end
62
55
  alias to_hash serializable_hash
63
56
 
64
- # @return [Symbol]
65
- def key
66
- @_key || self.class.name.delete_suffix('Resource').downcase.gsub(/:{2}/, '_').to_sym
67
- end
68
-
69
57
  private
70
58
 
59
+ # @return [String]
60
+ def _key
61
+ if @_key == true && Alba.inferring
62
+ demodulized = ActiveSupport::Inflector.demodulize(self.class.name)
63
+ meth = collection? ? :tableize : :singularize
64
+ ActiveSupport::Inflector.public_send(meth, demodulized.delete_suffix('Resource').downcase)
65
+ else
66
+ @_key.to_s
67
+ end
68
+ end
69
+
71
70
  def converter
72
- lambda do |resource|
73
- @_attributes.transform_values do |attribute|
74
- case attribute
75
- when Symbol
76
- resource.public_send attribute
77
- when Proc
78
- instance_exec(resource, &attribute)
79
- when Alba::One, Alba::Many
80
- attribute.to_hash(resource, params: params)
71
+ lambda do |object|
72
+ arrays = @_attributes.map do |key, attribute|
73
+ key = transform_key(key)
74
+ if attribute.is_a?(Array) # Conditional
75
+ conditional_attribute(object, key, attribute)
81
76
  else
82
- raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
77
+ [key, fetch_attribute(object, attribute)]
83
78
  end
79
+ rescue ::Alba::Error, FrozenError
80
+ raise
81
+ rescue StandardError => e
82
+ handle_error(e, object, key, attribute)
84
83
  end
84
+ arrays.reject(&:empty?).to_h
85
+ end
86
+ end
87
+
88
+ def conditional_attribute(object, key, attribute)
89
+ condition = attribute.last
90
+ arity = condition.arity
91
+ return [] if arity <= 1 && !condition.call(object)
92
+
93
+ fetched_attribute = fetch_attribute(object, attribute.first)
94
+ attr = if attribute.first.is_a?(Alba::Association)
95
+ attribute.first.object
96
+ else
97
+ fetched_attribute
98
+ end
99
+ return [] if arity >= 2 && !condition.call(object, attr)
100
+
101
+ [key, fetched_attribute]
102
+ end
103
+
104
+ def handle_error(error, object, key, attribute)
105
+ on_error = @_on_error || Alba._on_error
106
+ case on_error
107
+ when :raise, nil
108
+ raise
109
+ when :nullify
110
+ [key, nil]
111
+ when :ignore
112
+ []
113
+ when Proc
114
+ on_error.call(error, object, key, attribute, self.class)
115
+ else
116
+ raise ::Alba::Error, "Unknown on_error: #{on_error.inspect}"
85
117
  end
86
118
  end
87
119
 
88
- def empty_serializer
89
- klass = Class.new
90
- klass.include Alba::Serializer
91
- klass
120
+ # Override this method to supply custom key transform method
121
+ def transform_key(key)
122
+ return key unless @_transform_keys
123
+
124
+ require_relative 'key_transformer'
125
+ KeyTransformer.transform(key, @_transform_keys)
126
+ end
127
+
128
+ def fetch_attribute(object, attribute)
129
+ case attribute
130
+ when Symbol
131
+ object.public_send attribute
132
+ when Proc
133
+ instance_exec(object, &attribute)
134
+ when Alba::One, Alba::Many
135
+ within = check_within
136
+ return unless within
137
+
138
+ attribute.to_hash(object, params: params, within: within)
139
+ else
140
+ raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
141
+ end
92
142
  end
93
143
 
94
- def inline_extended_serializer(with)
95
- klass = empty_serializer
96
- klass.class_eval(&with)
97
- klass
144
+ def check_within
145
+ case @within
146
+ when Hash # Traverse within tree
147
+ @within.fetch(_key.to_sym, nil)
148
+ when Array # within tree ends with Array
149
+ @within.find { |item| item.to_sym == _key.to_sym } # Check if at least one item in the array matches current resource
150
+ when Symbol # within tree could end with Symbol
151
+ @within == _key.to_sym # Check if the symbol matches current resource
152
+ when true # In this case, Alba serializes all associations.
153
+ true
154
+ when nil, false # In these cases, Alba stops serialization here.
155
+ false
156
+ else
157
+ raise Alba::Error, "Unknown type for within option: #{@within.class}"
158
+ end
98
159
  end
99
160
 
100
161
  def collection?
@@ -115,19 +176,24 @@ module Alba
115
176
  # Set multiple attributes at once
116
177
  #
117
178
  # @param attrs [Array<String, Symbol>]
118
- def attributes(*attrs)
119
- attrs.each { |attr_name| @_attributes[attr_name.to_sym] = attr_name.to_sym }
179
+ # @param options [Hash] option hash including `if` that is a condition to render these attributes
180
+ def attributes(*attrs, **options)
181
+ attrs.each do |attr_name|
182
+ attr = options[:if] ? [attr_name.to_sym, options[:if]] : attr_name.to_sym
183
+ @_attributes[attr_name.to_sym] = attr
184
+ end
120
185
  end
121
186
 
122
187
  # Set an attribute with the given block
123
188
  #
124
189
  # @param name [String, Symbol] key name
190
+ # @param options [Hash] option hash including `if` that is a condition to render
125
191
  # @param block [Block] the block called during serialization
126
192
  # @raise [ArgumentError] if block is absent
127
- def attribute(name, &block)
193
+ def attribute(name, **options, &block)
128
194
  raise ArgumentError, 'No block given in attribute method' unless block
129
195
 
130
- @_attributes[name.to_sym] = block
196
+ @_attributes[name.to_sym] = options[:if] ? [block, options[:if]] : block
131
197
  end
132
198
 
133
199
  # Set One association
@@ -136,10 +202,13 @@ module Alba
136
202
  # @param condition [Proc]
137
203
  # @param resource [Class<Alba::Resource>]
138
204
  # @param key [String, Symbol] used as key when given
205
+ # @param options [Hash] option hash including `if` that is a condition to render
139
206
  # @param block [Block]
140
207
  # @see Alba::One#initialize
141
- def one(name, condition = nil, resource: nil, key: nil, &block)
142
- @_attributes[key&.to_sym || name.to_sym] = One.new(name: name, condition: condition, resource: resource, &block)
208
+ def one(name, condition = nil, resource: nil, key: nil, **options, &block)
209
+ nesting = self.name&.rpartition('::')&.first
210
+ one = One.new(name: name, condition: condition, resource: resource, nesting: nesting, &block)
211
+ @_attributes[key&.to_sym || name.to_sym] = options[:if] ? [one, options[:if]] : one
143
212
  end
144
213
  alias has_one one
145
214
 
@@ -149,25 +218,27 @@ module Alba
149
218
  # @param condition [Proc]
150
219
  # @param resource [Class<Alba::Resource>]
151
220
  # @param key [String, Symbol] used as key when given
221
+ # @param options [Hash] option hash including `if` that is a condition to render
152
222
  # @param block [Block]
153
223
  # @see Alba::Many#initialize
154
- def many(name, condition = nil, resource: nil, key: nil, &block)
155
- @_attributes[key&.to_sym || name.to_sym] = Many.new(name: name, condition: condition, resource: resource, &block)
224
+ def many(name, condition = nil, resource: nil, key: nil, **options, &block)
225
+ nesting = self.name&.rpartition('::')&.first
226
+ many = Many.new(name: name, condition: condition, resource: resource, nesting: nesting, &block)
227
+ @_attributes[key&.to_sym || name.to_sym] = options[:if] ? [many, options[:if]] : many
156
228
  end
157
229
  alias has_many many
158
230
 
159
- # Set serializer for the resource
160
- #
161
- # @param name [Alba::Serializer]
162
- def serializer(name)
163
- @_serializer = name <= Alba::Serializer ? name : nil
164
- end
165
-
166
231
  # Set key
167
232
  #
168
233
  # @param key [String, Symbol]
169
234
  def key(key)
170
- @_key = key.to_sym
235
+ @_key = key.respond_to?(:to_sym) ? key.to_sym : key
236
+ end
237
+
238
+ # Set key to true
239
+ #
240
+ def key!
241
+ @_key = true
171
242
  end
172
243
 
173
244
  # Delete attributes
@@ -179,6 +250,24 @@ module Alba
179
250
  @_attributes.delete(attr_name.to_sym)
180
251
  end
181
252
  end
253
+
254
+ # Transform keys as specified type
255
+ #
256
+ # @param type [String, Symbol]
257
+ def transform_keys(type)
258
+ @_transform_keys = type.to_sym
259
+ end
260
+
261
+ # Set error handler
262
+ #
263
+ # @param [Symbol] handler
264
+ # @param [Block]
265
+ def on_error(handler = nil, &block)
266
+ raise ArgumentError, 'You cannot specify error handler with both Symbol and block' if handler && block
267
+ raise ArgumentError, 'You must specify error handler with either Symbol or block' unless handler || block
268
+
269
+ @_on_error = handler || block
270
+ end
182
271
  end
183
272
  end
184
273
  end
data/lib/alba/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Alba
2
- VERSION = '0.12.0'.freeze
2
+ VERSION = '1.1.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alba
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OKURA Masafumi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-20 00:00:00.000000000 Z
11
+ date: 2021-04-23 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Alba is designed to be a simple, easy to use and fast alternative to
14
14
  existing JSON serializers. Its performance is better than almost all gems which
@@ -19,25 +19,30 @@ executables: []
19
19
  extensions: []
20
20
  extra_rdoc_files: []
21
21
  files:
22
+ - ".github/workflows/main.yml"
22
23
  - ".gitignore"
23
24
  - ".rubocop.yml"
24
- - ".travis.yml"
25
25
  - ".yardopts"
26
+ - CHANGELOG.md
26
27
  - CODE_OF_CONDUCT.md
27
28
  - Gemfile
28
- - Gemfile.lock
29
29
  - LICENSE.txt
30
30
  - README.md
31
31
  - Rakefile
32
32
  - alba.gemspec
33
+ - benchmark/local.rb
33
34
  - bin/console
34
35
  - bin/setup
36
+ - codecov.yml
37
+ - gemfiles/all.gemfile
38
+ - gemfiles/without_active_support.gemfile
39
+ - gemfiles/without_oj.gemfile
35
40
  - lib/alba.rb
36
41
  - lib/alba/association.rb
42
+ - lib/alba/key_transformer.rb
37
43
  - lib/alba/many.rb
38
44
  - lib/alba/one.rb
39
45
  - lib/alba/resource.rb
40
- - lib/alba/serializer.rb
41
46
  - lib/alba/version.rb
42
47
  - sider.yml
43
48
  homepage: https://github.com/okuramasafumi/alba
@@ -46,7 +51,7 @@ licenses:
46
51
  metadata:
47
52
  homepage_uri: https://github.com/okuramasafumi/alba
48
53
  source_code_uri: https://github.com/okuramasafumi/alba
49
- changelog_uri: https://github.com/okuramasafumi/alba/CHANGELOG.md
54
+ changelog_uri: https://github.com/okuramasafumi/alba/blob/master/CHANGELOG.md
50
55
  post_install_message:
51
56
  rdoc_options: []
52
57
  require_paths:
@@ -55,14 +60,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
55
60
  requirements:
56
61
  - - ">="
57
62
  - !ruby/object:Gem::Version
58
- version: 2.5.7
63
+ version: 2.5.0
59
64
  required_rubygems_version: !ruby/object:Gem::Requirement
60
65
  requirements:
61
66
  - - ">="
62
67
  - !ruby/object:Gem::Version
63
68
  version: '0'
64
69
  requirements: []
65
- rubygems_version: 3.0.3
70
+ rubygems_version: 3.2.16
66
71
  signing_key:
67
72
  specification_version: 4
68
73
  summary: Alba is the fastest JSON serializer for Ruby.