alba 0.8.0 → 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 592412974accb17b359ab871e293e2c839519f7cfffb31a04485bfe261b88202
4
- data.tar.gz: d8fbb2ec751eac8e7dbe278e8993da6641eaa88f302b3438055b2062c08bd229
3
+ metadata.gz: 9aae01860e12e1e5fd71f066e8772fb3a80b4aec6e99cf9dd55ad8652896ea24
4
+ data.tar.gz: c9930853dcaaa5ea2ff28e954c3b0d7e2ba559efc64b82e7da6b0c28968f55ed
5
5
  SHA512:
6
- metadata.gz: 97c3fc639d1d6b7fa588c78fa84f4be7318982ebf3154b9037e1b9c6ac38d1d9a0528bcf45618a5d1b1dc009df30441589eb27d1bdfbd8afaca1e61a0708e31e
7
- data.tar.gz: 7abd201ba80bdfff652840e948e8aede24e90d0cfc26722f08044916f239387fd0bada0a1e9a0d71cf252ced9400d1549124349a1070ea7bd8c8e5a7be0ebc38
6
+ metadata.gz: 7882b0950c339f87fc75b19e79bcdc2481a296331e9be47a646db997fa4db5639af31c49d529063d099cd5df405357d245397af23b0038c568e32feb86bd5718
7
+ data.tar.gz: 2bc8c03f0ca5049a23b7dd5c2f6582051f98296a0ba9e0a436e08e9a426a53896e7ac94af276cc3a82e5a0a253f30d381db5384e483c77918ff3d5ef9165a1b7
@@ -12,12 +12,40 @@ AllCops:
12
12
  - 'Rakefile'
13
13
  - 'alba.gemspec'
14
14
  NewCops: enable
15
+ EnabledByDefault: true
16
+
17
+ # Oneline comment is not valid so until it gets valid, we disable it
18
+ Bundler/GemComment:
19
+ Enabled: false
15
20
 
16
21
  Layout/SpaceInsideHashLiteralBraces:
17
22
  EnforcedStyle: no_space
18
23
 
24
+ Layout/MultilineAssignmentLayout:
25
+ EnforcedStyle: same_line
26
+
27
+ Lint/ConstantResolution:
28
+ Enabled: false
29
+
30
+ Metrics/ClassLength:
31
+ Exclude:
32
+ - 'test/alba_test.rb'
33
+
19
34
  Metrics/MethodLength:
20
- Max: 20
35
+ Max: 15
36
+
37
+ Style/ConstantVisibility:
38
+ Exclude:
39
+ - 'lib/alba/version.rb'
40
+
41
+ Style/Copyright:
42
+ Enabled: false
21
43
 
22
44
  Style/FrozenStringLiteralComment:
23
45
  Enabled: false
46
+
47
+ Style/InlineComment:
48
+ Enabled: false
49
+
50
+ Style/MethodCallWithArgsParentheses:
51
+ Enabled: false
@@ -5,4 +5,6 @@ rvm:
5
5
  - 2.5.8
6
6
  - 2.6.6
7
7
  - 2.7.1
8
+ # - ruby-head # oj doesn't work with Ruby 2.8.0
9
+ - truffleruby
8
10
  before_install: gem install bundler -v 2.1.4
@@ -0,0 +1,2 @@
1
+ --no-private
2
+ --exclude lib/alba/version.rb
data/Gemfile CHANGED
@@ -3,11 +3,13 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in alba.gemspec
4
4
  gemspec
5
5
 
6
- gem 'coveralls', require: false
7
- gem 'minitest', '~> 5.0'
8
- gem 'oj', '~> 3.10'
9
- gem 'rake', '~> 13.0'
10
- gem 'rubocop', '>= 0.79.0', require: false
11
- gem 'rubocop-minitest', '~> 0.10.1', require: false
12
- gem 'rubocop-performance', '~> 1.7.1', require: false
13
- gem 'rubocop-sensible', '~> 0.3.0', require: false
6
+ gem 'activesupport', require: false # For backend
7
+ gem 'coveralls', require: false # For test coverage
8
+ gem 'minitest', '~> 5.14' # For test
9
+ gem 'oj', '~> 3.10', platform: :ruby, require: false # For backend
10
+ gem 'rake', '~> 13.0' # For test and automation
11
+ gem 'rubocop', '>= 0.79.0', require: false # For lint
12
+ gem 'rubocop-minitest', '~> 0.10.1', require: false # For lint
13
+ gem 'rubocop-performance', '~> 1.7.1', require: false # For lint
14
+ gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
15
+ gem 'yard', require: false
@@ -1,12 +1,19 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- alba (0.8.0)
4
+ alba (0.11.1)
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,9 +21,11 @@ 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
- minitest (5.14.1)
19
- oj (3.10.8)
27
+ minitest (5.14.2)
28
+ oj (3.10.13)
20
29
  parallel (1.19.2)
21
30
  parser (2.7.1.4)
22
31
  ast (~> 2.4.1)
@@ -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.90.0)
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,23 +60,30 @@ 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
+ yard (0.9.25)
70
+ zeitwerk (2.4.0)
57
71
 
58
72
  PLATFORMS
59
73
  ruby
60
74
 
61
75
  DEPENDENCIES
76
+ activesupport
62
77
  alba!
63
78
  coveralls
64
- minitest (~> 5.0)
79
+ minitest (~> 5.14)
65
80
  oj (~> 3.10)
66
81
  rake (~> 13.0)
67
82
  rubocop (>= 0.79.0)
68
83
  rubocop-minitest (~> 0.10.1)
69
84
  rubocop-performance (~> 1.7.1)
70
85
  rubocop-sensible (~> 0.3.0)
86
+ yard
71
87
 
72
88
  BUNDLED WITH
73
89
  2.1.4
data/README.md CHANGED
@@ -2,11 +2,29 @@
2
2
  [![Build Status](https://travis-ci.com/okuramasafumi/alba.svg?branch=master)](https://travis-ci.com/okuramasafumi/alba)
3
3
  [![Coverage Status](https://coveralls.io/repos/github/okuramasafumi/alba/badge.svg?branch=master)](https://coveralls.io/github/okuramasafumi/alba?branch=master)
4
4
  [![Maintainability](https://api.codeclimate.com/v1/badges/fdab4cc0de0b9addcfe8/maintainability)](https://codeclimate.com/github/okuramasafumi/alba/maintainability)
5
+ ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/okuramasafumi/alba)
6
+ ![GitHub](https://img.shields.io/github/license/okuramasafumi/alba)
5
7
 
6
8
  # Alba
7
9
 
8
10
  `Alba` is the fastest JSON serializer for Ruby.
9
11
 
12
+ ## Why yet another JSON serializer?
13
+
14
+ We know that there are several other JSON serializers for Ruby around, but none of them made us satisfied.
15
+
16
+ Alba has some advantages over other JSON serializers which we've wanted to have.
17
+
18
+ ### Easy to understand
19
+
20
+ DSL is great. It makes the coding experience natural and intuitive. However, remembering lots of DSL requires us a lot of effort. Unfortunately, most of the existing libraries have implemented their features via DSL and it's not easy to understand how they behave entirely. Alba's core DSL are only four (`attributes`, `attribute`, `one` and `many`) so it's easy to understand how to use.
21
+
22
+ Alba is also understandable internally. The codebase is much smaller than the alternatives. In fact, it's less than 300 lines of code. Look at the code on [GitHub](https://github.com/okuramasafumi/alba/tree/master/lib) and you'll be surprised how simple it is!
23
+
24
+ ### Performance
25
+
26
+ Alba is faster than most of the alternatives. We have a [benchmark](https://gist.github.com/okuramasafumi/4e375525bd3a28e4ca812d2a3b3e5829).
27
+
10
28
  ## Installation
11
29
 
12
30
  Add this line to your application's Gemfile:
@@ -23,8 +41,56 @@ Or install it yourself as:
23
41
 
24
42
  $ gem install alba
25
43
 
44
+ ## Supported Ruby versions
45
+
46
+ Alba supports CRuby 2.5.7 and higher and latest TruffleRuby.
47
+
48
+ ## Documentation
49
+
50
+ You can find the documentation on [RubyDoc](https://rubydoc.info/gems/alba).
51
+
52
+ ## Features
53
+
54
+ * Resource-based serialization
55
+ * Arbitrary attribute definition
56
+ * One and many association with the ability to define them inline
57
+ * Adding condition and filter to association
58
+ * Parameters can be injected and used in attributes and associations
59
+ * Setting root key separately in Serializer
60
+ * Adding metadata
61
+ * Selectable backend
62
+ * No runtime dependencies
63
+
64
+ ## Anti features
65
+
66
+ * Sorting keys
67
+ * Class level support of parameters
68
+ * Supporting all existing JSON encoder/decoder
69
+ * Cache
70
+ * [JSON:API](https://jsonapi.org) support
71
+ * Association name inflection
72
+ * And many others
73
+
26
74
  ## Usage
27
75
 
76
+ ### Configuration
77
+
78
+ Alba's configuration is fairly simple.
79
+
80
+ #### Backend
81
+
82
+ Backend is the actual part serializing an object into JSON. Alba supports these backends.
83
+
84
+ * Oj, the fastest. Gem installation required.
85
+ * active_support, mostly for Rails. Gem installation required.
86
+ * default or json, with no external dependencies.
87
+
88
+ You can set a backend like this:
89
+
90
+ ```ruby
91
+ Alba.backend = :oj
92
+ ```
93
+
28
94
  ### Simple serialization with key
29
95
 
30
96
  ```ruby
@@ -91,7 +157,7 @@ class ArticleResource
91
157
  attributes :title
92
158
  end
93
159
 
94
- class UserResource1
160
+ class UserResource
95
161
  include Alba::Resource
96
162
 
97
163
  attributes :id
@@ -105,7 +171,7 @@ user.articles << article1
105
171
  article2 = Article.new(2, 'Super nice', 'Really nice!')
106
172
  user.articles << article2
107
173
 
108
- UserResource1.new(user).serialize
174
+ UserResource.new(user).serialize
109
175
  # => '{"id":1,"articles":[{"title":"Hello World!"},{"title":"Super nice"}]}'
110
176
  ```
111
177
 
@@ -125,11 +191,49 @@ end
125
191
 
126
192
  Although this might be useful sometimes, it's generally recommended to define a class for both Resource and Serializer.
127
193
 
194
+ ### Inheritance and Ignorance
195
+
196
+ You can `exclude` or `ignore` certain attributes using `ignoring`.
197
+
198
+ ```ruby
199
+ class Foo
200
+ attr_accessor :id, :name, :body
201
+
202
+ def initialize(id, name, body)
203
+ @id = id
204
+ @name = name
205
+ @body = body
206
+ end
207
+ end
208
+
209
+ class GenericFooResource
210
+ include Alba::Resource
211
+
212
+ attributes :id, :name, :body
213
+ end
214
+
215
+ class RestrictedFooResouce < GenericFooResource
216
+ ignoring :id, :body
217
+ end
218
+
219
+ RestrictedFooResouce.new(foo).serialize
220
+ # => '{"name":"my foo"}'
221
+ end
222
+ ```
223
+
128
224
  ## Comparison
129
225
 
130
226
  Alba is faster than alternatives.
131
227
  For a performance benchmark, see https://gist.github.com/okuramasafumi/4e375525bd3a28e4ca812d2a3b3e5829.
132
228
 
229
+ ## Rails
230
+
231
+ When you use Alba in Rails, you can create an initializer file with the line below for compatibility with Rails JSON encoder.
232
+
233
+ ```ruby
234
+ Alba.backend = :active_support
235
+ ```
236
+
133
237
  ## Why named "Alba"?
134
238
 
135
239
  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 +246,7 @@ Alba has three component, `Serializer`, `Resource` and `Value` (`Value` is conce
142
246
 
143
247
  `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
248
 
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`.
249
+ `One` and `Many` are the special object fetching other resources and converting them into Hash.
146
250
 
147
251
  The main `Alba` module holds config values and one convenience method, `.serialize`.
148
252
 
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.description = "Alba is designed to be a simple, easy to use and fast alternative to existing JSON serializers. Its performance is better than almost all gems which do similar things. The internal is so simple that it's easy to hack and maintain."
11
11
  spec.homepage = 'https://github.com/okuramasafumi/alba'
12
12
  spec.license = 'MIT'
13
- spec.required_ruby_version = Gem::Requirement.new('>= 2.5.8')
13
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.5.7')
14
14
 
15
15
  spec.metadata['homepage_uri'] = spec.homepage
16
16
  spec.metadata['source_code_uri'] = 'https://github.com/okuramasafumi/alba'
@@ -1,21 +1,36 @@
1
- require 'alba/version'
2
- require 'alba/serializers/default_serializer'
3
- require 'alba/serializer'
4
- require 'alba/resource'
5
- require 'alba/resources/default_resource'
1
+ require_relative 'alba/version'
2
+ require_relative 'alba/serializer'
3
+ require_relative 'alba/resource'
6
4
 
7
5
  # Core module
8
6
  module Alba
7
+ # Base class for Errors
9
8
  class Error < StandardError; end
9
+ # Error class for backend which is not supported
10
+ class UnsupportedBackend < Error; end
10
11
 
11
12
  class << self
12
- attr_reader :backend
13
+ attr_reader :backend, :encoder
13
14
  attr_accessor :default_serializer
14
15
 
16
+ # Set the backend, which actually serializes object into JSON
17
+ #
18
+ # @param backend [#to_sym, nil] the name of the backend
19
+ # Possible values are `oj`, `active_support`, `default`, `json` and nil
20
+ # @return [Proc] the proc to encode object into JSON
21
+ # @raise [Alba::UnsupportedBackend] if backend is not supported
15
22
  def backend=(backend)
16
23
  @backend = backend&.to_sym
24
+ set_encoder
17
25
  end
18
26
 
27
+ # Serialize the object with inline definitions
28
+ #
29
+ # @param object [Object] the object to be serialized
30
+ # @param with [nil, Proc, Alba::Serializer] selializer
31
+ # @param block [Block] resource block
32
+ # @return [String] serialized JSON string
33
+ # @raise [ArgumentError] if block is absent or `with` argument's type is wrong
19
34
  def serialize(object, with: nil, &block)
20
35
  raise ArgumentError, 'Block required' unless block
21
36
 
@@ -27,8 +42,47 @@ module Alba
27
42
 
28
43
  private
29
44
 
45
+ def set_encoder
46
+ @encoder = case @backend
47
+ when :oj
48
+ try_oj
49
+ when :active_support
50
+ try_active_support
51
+ when nil, :default, :json
52
+ default_encoder
53
+ else
54
+ raise Alba::UnsupportedBackend, "Unsupported backend, #{backend}"
55
+ end
56
+ end
57
+
58
+ def try_oj
59
+ require 'oj'
60
+ ->(hash) { Oj.dump(hash, mode: :strict) }
61
+ rescue LoadError
62
+ default_encoder
63
+ end
64
+
65
+ def try_active_support
66
+ require 'active_support/json'
67
+ ->(hash) { ActiveSupport::JSON.encode(hash) }
68
+ rescue LoadError
69
+ default_encoder
70
+ end
71
+
72
+ def default_encoder
73
+ lambda do |hash|
74
+ require 'json'
75
+ JSON.dump(hash)
76
+ end
77
+ end
78
+
30
79
  def resource_class
31
- ::Alba::Resources::DefaultResource.clone
80
+ @resource_class ||= begin
81
+ klass = Class.new
82
+ klass.include(Alba::Resource)
83
+ end
32
84
  end
33
85
  end
86
+
87
+ @encoder = default_encoder
34
88
  end
@@ -0,0 +1,31 @@
1
+ module Alba
2
+ # Base class for `One` and `Many`
3
+ # Child class should implement `to_hash` method
4
+ class Association
5
+ # @param name [Symbol] name of the method to fetch association
6
+ # @param condition [Proc] a proc filtering data
7
+ # @param resource [Class<Alba::Resource>] a resource class for the association
8
+ # @param block [Block] used to define resource when resource arg is absent
9
+ def initialize(name:, condition: nil, resource: nil, &block)
10
+ @name = name
11
+ @condition = condition
12
+ @block = block
13
+ @resource = resource || resource_class
14
+ raise ArgumentError, 'resource or block is required' if @resource.nil? && @block.nil?
15
+ end
16
+
17
+ # @abstract
18
+ def to_hash
19
+ :not_implemented
20
+ end
21
+
22
+ private
23
+
24
+ def resource_class
25
+ klass = Class.new
26
+ klass.include(Alba::Resource)
27
+ klass.class_eval(&@block)
28
+ klass
29
+ end
30
+ end
31
+ end
@@ -1,26 +1,17 @@
1
+ require_relative '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
-
11
- def to_hash(target)
5
+ class Many < Association
6
+ # Recursively converts objects into an Array of Hashes
7
+ #
8
+ # @param target [Object] the object having an association method
9
+ # @param params [Hash] user-given Hash for arbitrary data
10
+ # @return [Array<Hash>]
11
+ def to_hash(target, params: {})
12
12
  objects = target.public_send(@name)
13
- @resource ||= resource_class
14
- objects.map { |o| @resource.new(o).to_hash }
15
- 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
13
+ objects = @condition.call(objects, params) if @condition
14
+ objects.map { |o| @resource.new(o, params: params).to_hash }
24
15
  end
25
16
  end
26
17
  end
@@ -1,26 +1,17 @@
1
+ require_relative '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
-
11
- def to_hash(target)
5
+ class One < Association
6
+ # Recursively converts an object into a Hash
7
+ #
8
+ # @param target [Object] the object having an association method
9
+ # @param params [Hash] user-given Hash for arbitrary data
10
+ # @return [Hash]
11
+ def to_hash(target, params: {})
12
12
  object = target.public_send(@name)
13
- @resource ||= resource_class
14
- @resource.new(object).to_hash
15
- 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
13
+ object = @condition.call(object, params) if @condition
14
+ @resource.new(object, params: params).to_hash
24
15
  end
25
16
  end
26
17
  end
@@ -1,24 +1,22 @@
1
- require 'alba/serializer'
2
- require 'alba/attribute'
3
- require 'alba/one'
4
- require 'alba/many'
5
- require 'alba/serializers/default_serializer'
1
+ require_relative 'serializer'
2
+ require_relative 'one'
3
+ require_relative 'many'
6
4
 
7
5
  module Alba
8
6
  # This module represents what should be serialized
9
7
  module Resource
10
- DSLS = [:_attributes, :_serializer, :_key].freeze
8
+ # @!parse include InstanceMethods
9
+ # @!parse extend ClassMethods
10
+ DSLS = {_attributes: {}, _serializer: nil, _key: nil}.freeze
11
+ private_constant :DSLS
12
+
13
+ # @private
11
14
  def self.included(base)
15
+ super
12
16
  base.class_eval do
13
17
  # 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}")
18
+ DSLS.each do |name, initial|
19
+ instance_variable_set("@#{name}", initial.dup) unless instance_variable_defined?("@#{name}")
22
20
  end
23
21
  end
24
22
  base.include InstanceMethods
@@ -27,17 +25,24 @@ module Alba
27
25
 
28
26
  # Instance methods
29
27
  module InstanceMethods
30
- attr_reader :_object
31
-
32
- def initialize(object)
33
- @_object = object
34
- DSLS.each { |name| instance_variable_set("@#{name}", self.class.public_send(name)) }
28
+ attr_reader :object, :_key, :params
29
+
30
+ # @param object [Object] the object to be serialized
31
+ # @param params [Hash] user-given Hash for arbitrary data
32
+ def initialize(object, params: {})
33
+ @object = object
34
+ @params = params.freeze
35
+ DSLS.each_key { |name| instance_variable_set("@#{name}", self.class.public_send(name)) }
35
36
  end
36
37
 
38
+ # Get serializer with `with` argument and serialize self with it
39
+ #
40
+ # @param with [nil, Proc, Alba::Serializer] selializer
41
+ # @return [String] serialized JSON string
37
42
  def serialize(with: nil)
38
43
  serializer = case with
39
44
  when nil
40
- @_serializer || Alba::Serializers::DefaultSerializer
45
+ @_serializer || empty_serializer
41
46
  when ->(obj) { obj.is_a?(Class) && obj <= Alba::Serializer }
42
47
  with
43
48
  when Proc
@@ -48,71 +53,130 @@ module Alba
48
53
  serializer.new(self).serialize
49
54
  end
50
55
 
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
56
+ # A Hash for serialization
57
+ #
58
+ # @return [Hash]
59
+ def serializable_hash
60
+ collection? ? @object.map(&converter) : converter.call(@object)
63
61
  end
64
62
  alias to_hash serializable_hash
65
63
 
64
+ # @return [Symbol]
66
65
  def key
67
66
  @_key || self.class.name.delete_suffix('Resource').downcase.gsub(/:{2}/, '_').to_sym
68
67
  end
69
68
 
70
69
  private
71
70
 
71
+ 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)
81
+ else
82
+ raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ def empty_serializer
89
+ klass = Class.new
90
+ klass.include Alba::Serializer
91
+ klass
92
+ end
93
+
72
94
  def inline_extended_serializer(with)
73
- klass = ::Alba::Serializers::DefaultSerializer.clone
95
+ klass = empty_serializer
74
96
  klass.class_eval(&with)
75
97
  klass
76
98
  end
77
99
 
78
100
  def collection?
79
- @_object.is_a?(Enumerable)
101
+ @object.is_a?(Enumerable)
80
102
  end
81
103
  end
82
104
 
83
105
  # Class methods
84
106
  module ClassMethods
85
- attr_reader(*DSLS)
107
+ attr_reader(*DSLS.keys)
86
108
 
109
+ # @private
87
110
  def inherited(subclass)
88
- DSLS.each { |name| subclass.instance_variable_set("@#{name}", instance_variable_get("@#{name}")) }
111
+ super
112
+ DSLS.each_key { |name| subclass.instance_variable_set("@#{name}", instance_variable_get("@#{name}").clone) }
89
113
  end
90
114
 
115
+ # Set multiple attributes at once
116
+ #
117
+ # @param attrs [Array<String, Symbol>]
91
118
  def attributes(*attrs)
92
- attrs.each { |attr_name| @_attributes[attr_name] = Attribute.new(name: attr_name, method: attr_name) }
119
+ attrs.each { |attr_name| @_attributes[attr_name.to_sym] = attr_name.to_sym }
93
120
  end
94
121
 
122
+ # Set an attribute with the given block
123
+ #
124
+ # @param name [String, Symbol] key name
125
+ # @param block [Block] the block called during serialization
126
+ # @raise [ArgumentError] if block is absent
95
127
  def attribute(name, &block)
96
128
  raise ArgumentError, 'No block given in attribute method' unless block
97
129
 
98
- @_attributes[name] = Attribute.new(name: name, method: block)
130
+ @_attributes[name.to_sym] = block
99
131
  end
100
132
 
101
- def one(name, resource: nil, &block)
102
- @_attributes[name.to_sym] = One.new(name: name, resource: resource, &block)
133
+ # Set One association
134
+ #
135
+ # @param name [String, Symbol]
136
+ # @param condition [Proc]
137
+ # @param resource [Class<Alba::Resource>]
138
+ # @param key [String, Symbol] used as key when given
139
+ # @param block [Block]
140
+ # @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)
103
143
  end
104
144
 
105
- def many(name, resource: nil, &block)
106
- @_attributes[name.to_sym] = Many.new(name: name, resource: resource, &block)
145
+ # Set Many association
146
+ #
147
+ # @param name [String, Symbol]
148
+ # @param condition [Proc]
149
+ # @param resource [Class<Alba::Resource>]
150
+ # @param key [String, Symbol] used as key when given
151
+ # @param block [Block]
152
+ # @see Alba::Many#initialize
153
+ def many(name, condition = nil, resource: nil, key: nil, &block)
154
+ @_attributes[key&.to_sym || name.to_sym] = Many.new(name: name, condition: condition, resource: resource, &block)
107
155
  end
108
156
 
157
+ # Set serializer for the resource
158
+ #
159
+ # @param name [Alba::Serializer]
109
160
  def serializer(name)
110
161
  @_serializer = name <= Alba::Serializer ? name : nil
111
162
  end
112
163
 
164
+ # Set key
165
+ #
166
+ # @param key [String, Symbol]
113
167
  def key(key)
114
168
  @_key = key.to_sym
115
169
  end
170
+
171
+ # Delete attributes
172
+ # Use this DSL in child class to ignore certain attributes
173
+ #
174
+ # @param attributes [Array<String, Symbol>]
175
+ def ignoring(*attributes)
176
+ attributes.each do |attr_name|
177
+ @_attributes.delete(attr_name.to_sym)
178
+ end
179
+ end
116
180
  end
117
181
  end
118
182
  end
@@ -1,45 +1,48 @@
1
1
  module Alba
2
2
  # This module represents how a resource should be serialized.
3
3
  module Serializer
4
+ # @!parse include InstanceMethods
5
+ # @!parse extend ClassMethods
6
+
7
+ # @private
4
8
  def self.included(base)
9
+ super
10
+ base.class_eval do
11
+ @_opts = {} unless instance_variable_defined?('@_opts')
12
+ @_metadata = {} unless instance_variable_defined?('@_metadata')
13
+ end
5
14
  base.include InstanceMethods
6
15
  base.extend ClassMethods
7
16
  end
8
17
 
9
18
  # Instance methods
10
19
  module InstanceMethods
20
+ # @param resource [Alba::Resource]
11
21
  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)
22
+ @resource = resource
23
+ @hash = resource.serializable_hash
22
24
  @hash = {key.to_sym => @hash} if key
23
25
  # @hash is either Hash or Array
24
- @hash.is_a?(Hash) ? @hash.merge!(@_metadata.to_h) : @hash << @_metadata
26
+ @hash.is_a?(Hash) ? @hash.merge!(metadata.to_h) : @hash << metadata
25
27
  end
26
28
 
29
+ # Use real encoder to actually serialize to JSON
30
+ #
31
+ # @return [String] JSON string
27
32
  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
40
- else
41
- fallback
42
- end.call
33
+ Alba.encoder.call(@hash)
34
+ end
35
+
36
+ private
37
+
38
+ def key
39
+ opts = self.class._opts
40
+ opts[:key] == true ? @resource.key : opts[:key]
41
+ end
42
+
43
+ def metadata
44
+ metadata = self.class._metadata
45
+ metadata.transform_values { |block| block.call(@resource.object) }
43
46
  end
44
47
  end
45
48
 
@@ -47,18 +50,25 @@ module Alba
47
50
  module ClassMethods
48
51
  attr_reader :_opts, :_metadata
49
52
 
53
+ # @private
50
54
  def inherited(subclass)
51
- %w[_opts _metadata].each { |name| subclass.instance_variable_set("@#{name}", instance_variable_get("@#{name}")) }
55
+ super
56
+ %w[_opts _metadata].each { |name| subclass.instance_variable_set("@#{name}", public_send(name).clone) }
52
57
  end
53
58
 
59
+ # Set options, currently key only
60
+ #
61
+ # @param key [Boolean, Symbol]
54
62
  def set(key: false)
55
- @_opts ||= {}
56
63
  @_opts[:key] = key
57
64
  end
58
65
 
66
+ # Set metadata
67
+ #
68
+ # @param name [String, Symbol] key for the metadata
69
+ # @param block [Block] the content of the metadata
59
70
  def metadata(name, &block)
60
- @_metadata ||= {}
61
- @_metadata[name] = block
71
+ @_metadata[name.to_sym] = block
62
72
  end
63
73
  end
64
74
  end
@@ -1,3 +1,3 @@
1
1
  module Alba
2
- VERSION = '0.8.0'.freeze
2
+ VERSION = '0.11.1'.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.11.1
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-09-02 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
@@ -22,6 +22,7 @@ files:
22
22
  - ".gitignore"
23
23
  - ".rubocop.yml"
24
24
  - ".travis.yml"
25
+ - ".yardopts"
25
26
  - CODE_OF_CONDUCT.md
26
27
  - Gemfile
27
28
  - Gemfile.lock
@@ -32,13 +33,11 @@ files:
32
33
  - bin/console
33
34
  - bin/setup
34
35
  - lib/alba.rb
35
- - lib/alba/attribute.rb
36
+ - lib/alba/association.rb
36
37
  - lib/alba/many.rb
37
38
  - lib/alba/one.rb
38
39
  - lib/alba/resource.rb
39
- - lib/alba/resources/default_resource.rb
40
40
  - lib/alba/serializer.rb
41
- - lib/alba/serializers/default_serializer.rb
42
41
  - lib/alba/version.rb
43
42
  - sider.yml
44
43
  homepage: https://github.com/okuramasafumi/alba
@@ -56,7 +55,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
56
55
  requirements:
57
56
  - - ">="
58
57
  - !ruby/object:Gem::Version
59
- version: 2.5.8
58
+ version: 2.5.7
60
59
  required_rubygems_version: !ruby/object:Gem::Requirement
61
60
  requirements:
62
61
  - - ">="
@@ -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
@@ -1,9 +0,0 @@
1
- module Alba
2
- module Resources
3
- # Empty resource class, use this with `class_eval` for
4
- # inline associations and serializations.
5
- class DefaultResource
6
- include ::Alba::Resource
7
- end
8
- end
9
- end
@@ -1,11 +0,0 @@
1
- require 'alba/serializer'
2
-
3
- module Alba
4
- module Serializers
5
- # DefaultSerializer class is used when a user doesn't specify serializer opt.
6
- # It's basically an alias of Alba::Serializer, but since it's a module this class simply include it.
7
- class DefaultSerializer
8
- include Alba::Serializer
9
- end
10
- end
11
- end