alba 0.8.0 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 592412974accb17b359ab871e293e2c839519f7cfffb31a04485bfe261b88202
4
- data.tar.gz: d8fbb2ec751eac8e7dbe278e8993da6641eaa88f302b3438055b2062c08bd229
3
+ metadata.gz: 3a92c56eee7615f823efc93ed7edb87ba6c47a4bcf8e8b11e48edfc62c380545
4
+ data.tar.gz: 310eca11a4a5a83287d3d87c1346c2436900bf1283e5642baa3878c69087812d
5
5
  SHA512:
6
- metadata.gz: 97c3fc639d1d6b7fa588c78fa84f4be7318982ebf3154b9037e1b9c6ac38d1d9a0528bcf45618a5d1b1dc009df30441589eb27d1bdfbd8afaca1e61a0708e31e
7
- data.tar.gz: 7abd201ba80bdfff652840e948e8aede24e90d0cfc26722f08044916f239387fd0bada0a1e9a0d71cf252ced9400d1549124349a1070ea7bd8c8e5a7be0ebc38
6
+ metadata.gz: 2fed0c44e90d2d876b2db635867741b3520a21c470acfb07a0218b7e0b627ce718edb1da964dfafdf0c79b3fe16bf7502a4fd873e998778c5307bd0c37d0c7c0
7
+ data.tar.gz: 334edc5389f1fceba2ee880a322f894ccb145533793a885777ac29c08c273b5dda9a31cf9885b493b6ec3fabcf859a1d1643ce37b848c225db66b658b36e3e34
@@ -16,6 +16,10 @@ AllCops:
16
16
  Layout/SpaceInsideHashLiteralBraces:
17
17
  EnforcedStyle: no_space
18
18
 
19
+ Metrics/ClassLength:
20
+ Exclude:
21
+ - 'test/alba_test.rb'
22
+
19
23
  Metrics/MethodLength:
20
24
  Max: 20
21
25
 
@@ -5,4 +5,5 @@ rvm:
5
5
  - 2.5.8
6
6
  - 2.6.6
7
7
  - 2.7.1
8
+ - truffleruby
8
9
  before_install: gem install bundler -v 2.1.4
data/Gemfile CHANGED
@@ -3,9 +3,10 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in alba.gemspec
4
4
  gemspec
5
5
 
6
+ gem 'activesupport', require: false
6
7
  gem 'coveralls', require: false
7
8
  gem 'minitest', '~> 5.0'
8
- gem 'oj', '~> 3.10'
9
+ gem 'oj', '~> 3.10', platforms: :ruby
9
10
  gem 'rake', '~> 13.0'
10
11
  gem 'rubocop', '>= 0.79.0', require: false
11
12
  gem 'rubocop-minitest', '~> 0.10.1', require: false
@@ -1,12 +1,19 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- alba (0.8.0)
4
+ alba (0.9.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
+ activesupport (6.0.3.2)
10
+ concurrent-ruby (~> 1.0, >= 1.0.2)
11
+ i18n (>= 0.7, < 2)
12
+ minitest (~> 5.1)
13
+ tzinfo (~> 1.1)
14
+ zeitwerk (~> 2.2, >= 2.2.2)
9
15
  ast (2.4.1)
16
+ concurrent-ruby (1.1.6)
10
17
  coveralls (0.8.23)
11
18
  json (>= 1.8, < 3)
12
19
  simplecov (~> 0.16.1)
@@ -14,6 +21,8 @@ GEM
14
21
  thor (>= 0.19.4, < 2.0)
15
22
  tins (~> 1.6)
16
23
  docile (1.3.2)
24
+ i18n (1.8.5)
25
+ concurrent-ruby (~> 1.0)
17
26
  json (2.3.1)
18
27
  minitest (5.14.1)
19
28
  oj (3.10.8)
@@ -24,17 +33,17 @@ GEM
24
33
  rake (13.0.1)
25
34
  regexp_parser (1.7.1)
26
35
  rexml (3.2.4)
27
- rubocop (0.88.0)
36
+ rubocop (0.89.1)
28
37
  parallel (~> 1.10)
29
38
  parser (>= 2.7.1.1)
30
39
  rainbow (>= 2.2.2, < 4.0)
31
40
  regexp_parser (>= 1.7)
32
41
  rexml
33
- rubocop-ast (>= 0.1.0, < 1.0)
42
+ rubocop-ast (>= 0.3.0, < 1.0)
34
43
  ruby-progressbar (~> 1.7)
35
44
  unicode-display_width (>= 1.4.0, < 2.0)
36
- rubocop-ast (0.2.0)
37
- parser (>= 2.7.0.1)
45
+ rubocop-ast (0.3.0)
46
+ parser (>= 2.7.1.4)
38
47
  rubocop-minitest (0.10.1)
39
48
  rubocop (>= 0.87)
40
49
  rubocop-performance (1.7.1)
@@ -51,14 +60,19 @@ GEM
51
60
  term-ansicolor (1.7.1)
52
61
  tins (~> 1.0)
53
62
  thor (1.0.1)
63
+ thread_safe (0.3.6)
54
64
  tins (1.25.0)
55
65
  sync
66
+ tzinfo (1.2.7)
67
+ thread_safe (~> 0.1)
56
68
  unicode-display_width (1.7.0)
69
+ zeitwerk (2.4.0)
57
70
 
58
71
  PLATFORMS
59
72
  ruby
60
73
 
61
74
  DEPENDENCIES
75
+ activesupport
62
76
  alba!
63
77
  coveralls
64
78
  minitest (~> 5.0)
data/README.md CHANGED
@@ -25,6 +25,24 @@ Or install it yourself as:
25
25
 
26
26
  ## Usage
27
27
 
28
+ ### Configuration
29
+
30
+ Alba's configuration is fairly simple.
31
+
32
+ #### Backend
33
+
34
+ Backend is the actual part serializing an object into JSON. Alba supports these backends.
35
+
36
+ * Oj, the fastest. Gem installation required.
37
+ * active_support, mostly for Rails. Gem installation required.
38
+ * default or json, with no external dependencies.
39
+
40
+ You can set a backend like this:
41
+
42
+ ```ruby
43
+ Alba.backend = :oj
44
+ ```
45
+
28
46
  ### Simple serialization with key
29
47
 
30
48
  ```ruby
@@ -125,11 +143,49 @@ end
125
143
 
126
144
  Although this might be useful sometimes, it's generally recommended to define a class for both Resource and Serializer.
127
145
 
146
+ ### Inheritance and Ignorance
147
+
148
+ You can `exclude` or `ignore` certain attributes using `ignoring`.
149
+
150
+ ```ruby
151
+ class Foo
152
+ attr_accessor :id, :name, :body
153
+
154
+ def initialize(id, name, body)
155
+ @id = id
156
+ @name = name
157
+ @body = body
158
+ end
159
+ end
160
+
161
+ class GenericFooResource
162
+ include Alba::Resource
163
+
164
+ attributes :id, :name, :body
165
+ end
166
+
167
+ class RestrictedFooResouce < GenericFooResource
168
+ ignoring :id, :body
169
+ end
170
+
171
+ RestrictedFooResouce.new(foo).serialize
172
+ # => '{"name":"my foo"}'
173
+ end
174
+ ```
175
+
128
176
  ## Comparison
129
177
 
130
178
  Alba is faster than alternatives.
131
179
  For a performance benchmark, see https://gist.github.com/okuramasafumi/4e375525bd3a28e4ca812d2a3b3e5829.
132
180
 
181
+ ## Rails
182
+
183
+ When you use Alba in Rails, you can create an initializer file with the line below for compatibility with Rails JSON encoder.
184
+
185
+ ```ruby
186
+ Alba.backend = :active_support
187
+ ```
188
+
133
189
  ## Why named "Alba"?
134
190
 
135
191
  The name "Alba" comes from "albatross", a kind of birds. In Japanese, this bird is called "Aho-dori", which means "stupid bird". I find it funny because in fact albatrosses fly really fast. I hope Alba looks stupid but in fact it does its job quick.
@@ -142,7 +198,7 @@ Alba has three component, `Serializer`, `Resource` and `Value` (`Value` is conce
142
198
 
143
199
  `Resource` is a component responsible for defining how an object (or a collection of objects) is converted into JSON. The difference between `Serializer` and `Resource` is that while `Serializer` can add arbitrary data into JSON, `Resource` can get data only from the object under it. The main interface is `#serializable_hash`.
144
200
 
145
- `Value` is either `Attribute`, `One` or `Many`. They are responsible for fetching data from the object for `Resource`. The main interface is `#to_hash`.
201
+ `One` and `Many` are the special object fetching other resources and converting them into Hash.
146
202
 
147
203
  The main `Alba` module holds config values and one convenience method, `.serialize`.
148
204
 
@@ -9,11 +9,12 @@ module Alba
9
9
  class Error < StandardError; end
10
10
 
11
11
  class << self
12
- attr_reader :backend
12
+ attr_reader :backend, :encoder
13
13
  attr_accessor :default_serializer
14
14
 
15
15
  def backend=(backend)
16
16
  @backend = backend&.to_sym
17
+ set_encoder
17
18
  end
18
19
 
19
20
  def serialize(object, with: nil, &block)
@@ -27,8 +28,40 @@ module Alba
27
28
 
28
29
  private
29
30
 
31
+ def set_encoder
32
+ @encoder = case @backend
33
+ when :oj
34
+ begin
35
+ require 'oj'
36
+ ->(hash) { Oj.dump(hash, mode: :strict) }
37
+ rescue LoadError
38
+ default_encoder
39
+ end
40
+ when :active_support
41
+ begin
42
+ require 'active_support/json'
43
+ ->(hash) { ActiveSupport::JSON.encode(hash) }
44
+ rescue LoadError
45
+ default_encoder
46
+ end
47
+ when nil, :default, :json
48
+ default_encoder
49
+ else
50
+ raise Alba::Error, "Unsupported backend, #{backend}"
51
+ end
52
+ end
53
+
54
+ def default_encoder
55
+ lambda do |hash|
56
+ require 'json'
57
+ JSON.dump(hash)
58
+ end
59
+ end
60
+
30
61
  def resource_class
31
62
  ::Alba::Resources::DefaultResource.clone
32
63
  end
33
64
  end
65
+
66
+ @encoder = default_encoder
34
67
  end
@@ -0,0 +1,25 @@
1
+ module Alba
2
+ # Base class for `One` and `Many`
3
+ # Child class should implement `to_hash` method
4
+ class Association
5
+ def initialize(name:, resource: nil, &block)
6
+ @name = name
7
+ @resource = resource
8
+ @block = block
9
+ raise ArgumentError, 'resource or block is required' if @resource.nil? && @block.nil?
10
+ end
11
+
12
+ def to_hash
13
+ :not_implemented
14
+ end
15
+
16
+ private
17
+
18
+ def resource_class
19
+ klass = ::Alba::Resources::DefaultResource.dup
20
+ klass.reset
21
+ klass.class_eval(&@block)
22
+ klass
23
+ end
24
+ end
25
+ end
@@ -1,26 +1,12 @@
1
+ require 'alba/association'
2
+
1
3
  module Alba
2
4
  # Representing many association
3
- class Many
4
- def initialize(name:, resource: nil, &block)
5
- @name = name
6
- @resource = resource
7
- @block = block
8
- raise ArgumentError, 'resource or block is required' if @resource.nil? && @block.nil?
9
- end
10
-
5
+ class Many < Association
11
6
  def to_hash(target)
12
7
  objects = target.public_send(@name)
13
8
  @resource ||= resource_class
14
9
  objects.map { |o| @resource.new(o).to_hash }
15
10
  end
16
-
17
- private
18
-
19
- def resource_class
20
- klass = Class.new
21
- klass.include(::Alba::Resource)
22
- klass.class_exec(&@block)
23
- klass
24
- end
25
11
  end
26
12
  end
@@ -1,26 +1,12 @@
1
+ require 'alba/association'
2
+
1
3
  module Alba
2
4
  # Representing one association
3
- class One
4
- def initialize(name:, resource: nil, &block)
5
- @name = name
6
- @resource = resource
7
- @block = block
8
- raise ArgumentError, 'resource or block is required' if @resource.nil? && @block.nil?
9
- end
10
-
5
+ class One < Association
11
6
  def to_hash(target)
12
7
  object = target.public_send(@name)
13
8
  @resource ||= resource_class
14
9
  @resource.new(object).to_hash
15
10
  end
16
-
17
- private
18
-
19
- def resource_class
20
- klass = Class.new
21
- klass.include(::Alba::Resource)
22
- klass.class_exec(&@block)
23
- klass
24
- end
25
11
  end
26
12
  end
@@ -1,5 +1,4 @@
1
1
  require 'alba/serializer'
2
- require 'alba/attribute'
3
2
  require 'alba/one'
4
3
  require 'alba/many'
5
4
  require 'alba/serializers/default_serializer'
@@ -7,18 +6,13 @@ require 'alba/serializers/default_serializer'
7
6
  module Alba
8
7
  # This module represents what should be serialized
9
8
  module Resource
10
- DSLS = [:_attributes, :_serializer, :_key].freeze
9
+ DSLS = {_attributes: {}, _serializer: nil, _key: nil}.freeze
11
10
  def self.included(base)
11
+ super
12
12
  base.class_eval do
13
13
  # Initialize
14
- DSLS.each do |name|
15
- initial = case name
16
- when :_attributes
17
- {}
18
- when :_serializer, :_name
19
- nil
20
- end
21
- instance_variable_set("@#{name}", initial) unless instance_variable_defined?("@#{name}")
14
+ DSLS.each do |name, initial|
15
+ instance_variable_set("@#{name}", initial.dup) unless instance_variable_defined?("@#{name}")
22
16
  end
23
17
  end
24
18
  base.include InstanceMethods
@@ -27,11 +21,12 @@ module Alba
27
21
 
28
22
  # Instance methods
29
23
  module InstanceMethods
30
- attr_reader :_object
24
+ attr_reader :object, :_key, :params
31
25
 
32
- def initialize(object)
33
- @_object = object
34
- DSLS.each { |name| instance_variable_set("@#{name}", self.class.public_send(name)) }
26
+ def initialize(object, params: {})
27
+ @object = object
28
+ @params = params
29
+ DSLS.each_key { |name| instance_variable_set("@#{name}", self.class.public_send(name)) }
35
30
  end
36
31
 
37
32
  def serialize(with: nil)
@@ -48,18 +43,8 @@ module Alba
48
43
  serializer.new(self).serialize
49
44
  end
50
45
 
51
- def serializable_hash(with_key: true)
52
- get_attribute = lambda do |resource|
53
- @_attributes.transform_values do |attribute|
54
- attribute.to_hash(resource)
55
- end
56
- end
57
- serializable_hash = if collection?
58
- @_object.map(&get_attribute)
59
- else
60
- get_attribute.call(@_object)
61
- end
62
- with_key && @_key ? {@_key => serializable_hash} : serializable_hash
46
+ def serializable_hash
47
+ collection? ? @object.map(&converter) : converter.call(@object)
63
48
  end
64
49
  alias to_hash serializable_hash
65
50
 
@@ -69,6 +54,21 @@ module Alba
69
54
 
70
55
  private
71
56
 
57
+ def converter
58
+ lambda do |resource|
59
+ @_attributes.transform_values do |attribute|
60
+ case attribute
61
+ when Symbol
62
+ resource.public_send attribute
63
+ when Proc
64
+ instance_exec(resource, &attribute)
65
+ when Alba::One, Alba::Many
66
+ attribute.to_hash(resource)
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
72
  def inline_extended_serializer(with)
73
73
  klass = ::Alba::Serializers::DefaultSerializer.clone
74
74
  klass.class_eval(&with)
@@ -76,26 +76,27 @@ module Alba
76
76
  end
77
77
 
78
78
  def collection?
79
- @_object.is_a?(Enumerable)
79
+ @object.is_a?(Enumerable)
80
80
  end
81
81
  end
82
82
 
83
83
  # Class methods
84
84
  module ClassMethods
85
- attr_reader(*DSLS)
85
+ attr_reader(*DSLS.keys)
86
86
 
87
87
  def inherited(subclass)
88
- DSLS.each { |name| subclass.instance_variable_set("@#{name}", instance_variable_get("@#{name}")) }
88
+ super
89
+ DSLS.each_key { |name| subclass.instance_variable_set("@#{name}", instance_variable_get("@#{name}").clone) }
89
90
  end
90
91
 
91
92
  def attributes(*attrs)
92
- attrs.each { |attr_name| @_attributes[attr_name] = Attribute.new(name: attr_name, method: attr_name) }
93
+ attrs.each { |attr_name| @_attributes[attr_name] = attr_name }
93
94
  end
94
95
 
95
96
  def attribute(name, &block)
96
97
  raise ArgumentError, 'No block given in attribute method' unless block
97
98
 
98
- @_attributes[name] = Attribute.new(name: name, method: block)
99
+ @_attributes[name] = block
99
100
  end
100
101
 
101
102
  def one(name, resource: nil, &block)
@@ -113,6 +114,13 @@ module Alba
113
114
  def key(key)
114
115
  @_key = key.to_sym
115
116
  end
117
+
118
+ # Use this DSL in child class to ignore certain attributes
119
+ def ignoring(*attributes)
120
+ attributes.each do |attr_name|
121
+ @_attributes.delete(attr_name)
122
+ end
123
+ end
116
124
  end
117
125
  end
118
126
  end
@@ -3,7 +3,11 @@ module Alba
3
3
  # Empty resource class, use this with `class_eval` for
4
4
  # inline associations and serializations.
5
5
  class DefaultResource
6
- include ::Alba::Resource
6
+ include Alba::Resource
7
+
8
+ def self.reset
9
+ @_attributes = {}
10
+ end
7
11
  end
8
12
  end
9
13
  end
@@ -2,6 +2,7 @@ module Alba
2
2
  # This module represents how a resource should be serialized.
3
3
  module Serializer
4
4
  def self.included(base)
5
+ super
5
6
  base.include InstanceMethods
6
7
  base.extend ClassMethods
7
8
  end
@@ -9,37 +10,32 @@ module Alba
9
10
  # Instance methods
10
11
  module InstanceMethods
11
12
  def initialize(resource)
12
- @_opts = self.class._opts || {}
13
- @_metadata = self.class._metadata || {}
14
- @_metadata = @_metadata.transform_values { |block| block.call(resource._object) }
15
- key = case @_opts[:key]
16
- when true
17
- resource.key
18
- else
19
- @_opts[:key]
20
- end
21
- @hash = resource.serializable_hash(with_key: false)
13
+ @resource = resource
14
+ @hash = resource.serializable_hash
22
15
  @hash = {key.to_sym => @hash} if key
23
16
  # @hash is either Hash or Array
24
- @hash.is_a?(Hash) ? @hash.merge!(@_metadata.to_h) : @hash << @_metadata
17
+ @hash.is_a?(Hash) ? @hash.merge!(metadata.to_h) : @hash << metadata
25
18
  end
26
19
 
27
20
  def serialize
28
- fallback = lambda do
29
- require 'json'
30
- JSON.dump(@hash)
31
- end
32
- case Alba.backend
33
- when :oj
34
- begin
35
- require 'oj'
36
- -> { Oj.dump(@hash, mode: :strict) }
37
- rescue LoadError
38
- fallback
39
- end
21
+ Alba.encoder.call(@hash)
22
+ end
23
+
24
+ private
25
+
26
+ def key
27
+ opts = self.class._opts || {}
28
+ case opts[:key]
29
+ when true
30
+ @resource.key
40
31
  else
41
- fallback
42
- end.call
32
+ opts[:key]
33
+ end
34
+ end
35
+
36
+ def metadata
37
+ metadata = self.class._metadata || {}
38
+ metadata.transform_values { |block| block.call(@resource.object) }
43
39
  end
44
40
  end
45
41
 
@@ -48,6 +44,7 @@ module Alba
48
44
  attr_reader :_opts, :_metadata
49
45
 
50
46
  def inherited(subclass)
47
+ super
51
48
  %w[_opts _metadata].each { |name| subclass.instance_variable_set("@#{name}", instance_variable_get("@#{name}")) }
52
49
  end
53
50
 
@@ -1,3 +1,3 @@
1
1
  module Alba
2
- VERSION = '0.8.0'.freeze
2
+ VERSION = '0.9.0'.freeze
3
3
  end
data/sider.yml CHANGED
@@ -4,6 +4,7 @@ linter:
4
4
  # # https://help.sider.review/tools/ruby/rubocop
5
5
  rubocop:
6
6
  gems:
7
+ - "rubocop-minitest"
7
8
  - "rubocop-performance"
8
9
  - "rubocop-sensible"
9
10
  safe: false
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.8.0
4
+ version: 0.9.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: 2020-08-06 00:00:00.000000000 Z
11
+ date: 2020-08-11 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
@@ -32,7 +32,7 @@ files:
32
32
  - bin/console
33
33
  - bin/setup
34
34
  - lib/alba.rb
35
- - lib/alba/attribute.rb
35
+ - lib/alba/association.rb
36
36
  - lib/alba/many.rb
37
37
  - lib/alba/one.rb
38
38
  - lib/alba/resource.rb
@@ -1,19 +0,0 @@
1
- module Alba
2
- # This class represents an attribute, which is serialized
3
- # by either sending message or calling a Proc.
4
- class Attribute
5
- def initialize(name:, method:)
6
- @name = name
7
- @method = method
8
- end
9
-
10
- def to_hash(target)
11
- case @method
12
- when Symbol, String
13
- target.public_send(@method)
14
- when Proc
15
- @method.arity.zero? ? target.instance_exec(&@method) : @method.call(target)
16
- end
17
- end
18
- end
19
- end