alba 0.5.0 → 0.10.0

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: 89b4df7ce18a63e20cee9e87bd2855ef9c4735c4f6e4e1ef55433c5ba0a2fbd0
4
- data.tar.gz: 63fa94fa17cdf3f5bfeef5f2e7b0d6efeec1fd06ad8d5386fb389fea3db0343a
3
+ metadata.gz: e8427c3365aa48ba76f97799757e5e32ae2f746d3b63c67d58e7c7ddadd17bae
4
+ data.tar.gz: c995206962d4a041032b76113a2f053e48f50f787af12c491437ff98c55d4d2e
5
5
  SHA512:
6
- metadata.gz: 063d913715faa3917df5658d798ceb1619b9a2aef1dc0875c92c1592b3d3f9025bec60a364ebe0d54fb0769e18ba78f5786caa288159d304dfeca7ab00149d2b
7
- data.tar.gz: 66aed62d355cd6fd8527ad4da27c53c9c42bf98606586a562b84dc377e6041125653d9669cf77c14427b4a1796b6c7abd7e6b2861323bac51fde28bfe05a7e7a
6
+ metadata.gz: b513dea8cfa68bf09e76114c3d6e14bd77a381f94ad9342c8692034069240c114b9eac95bf071662905de14efe269ac606a7cb2b50ec4107fa393391ed5ddf9d
7
+ data.tar.gz: e44bc0f25be1342ea9424c3919c62cd40b1917eac62f6bf612cd7979ead6db5521200eae4d7ef9a795ee373145c4fb5c846886bc1869916fd744596ac57aecbc
@@ -4,6 +4,7 @@ inherit_gem:
4
4
  rubocop-sensible: 'config/rubocop.yml'
5
5
 
6
6
  require:
7
+ - rubocop-minitest
7
8
  - rubocop-performance
8
9
 
9
10
  AllCops:
@@ -11,17 +12,45 @@ AllCops:
11
12
  - 'Rakefile'
12
13
  - 'alba.gemspec'
13
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
20
+
21
+ Layout/ClassStructure:
22
+ Enabled: true
14
23
 
15
24
  Layout/SpaceInsideHashLiteralBraces:
16
25
  EnforcedStyle: no_space
17
26
 
27
+ Layout/MultilineAssignmentLayout:
28
+ EnforcedStyle: same_line
29
+
30
+ Lint/ConstantResolution:
31
+ Enabled: false
32
+
33
+ Metrics/ClassLength:
34
+ Exclude:
35
+ - 'test/alba_test.rb'
36
+
18
37
  Metrics/MethodLength:
19
- Max: 20
38
+ Max: 15
20
39
 
21
- Naming/PredicateName:
22
- AllowedMethods:
23
- - 'has_one'
24
- - 'has_many'
40
+ Style/ConstantVisibility:
41
+ Enabled: false
42
+
43
+ Style/Copyright:
44
+ Enabled: false
45
+
46
+ Style/DocumentationMethod:
47
+ Enabled: false
25
48
 
26
49
  Style/FrozenStringLiteralComment:
27
50
  Enabled: false
51
+
52
+ Style/InlineComment:
53
+ Enabled: false
54
+
55
+ Style/MethodCallWithArgsParentheses:
56
+ Enabled: false
@@ -2,5 +2,8 @@
2
2
  language: ruby
3
3
  cache: bundler
4
4
  rvm:
5
- - 2.6.5
5
+ - 2.5.8
6
+ - 2.6.6
7
+ - 2.7.1
8
+ - truffleruby
6
9
  before_install: gem install bundler -v 2.1.4
data/Gemfile CHANGED
@@ -3,9 +3,12 @@ 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 'rake', '~> 13.0'
9
- gem 'rubocop', '>= 0.79.0', require: false
10
- gem 'rubocop-performance', '~> 1.7.1', require: false
11
- 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.0' # For test
9
+ gem 'oj', '~> 3.10', platforms: :ruby # 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
@@ -1,12 +1,20 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- alba (0.5.0)
4
+ alba (0.10.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
+ bigdecimal (2.0.0)
17
+ concurrent-ruby (1.1.6)
10
18
  coveralls (0.8.23)
11
19
  json (>= 1.8, < 3)
12
20
  simplecov (~> 0.16.1)
@@ -14,8 +22,12 @@ GEM
14
22
  thor (>= 0.19.4, < 2.0)
15
23
  tins (~> 1.6)
16
24
  docile (1.3.2)
25
+ i18n (1.8.5)
26
+ concurrent-ruby (~> 1.0)
17
27
  json (2.3.1)
18
28
  minitest (5.14.1)
29
+ oj (3.10.12)
30
+ bigdecimal (>= 1.0, < 3)
19
31
  parallel (1.19.2)
20
32
  parser (2.7.1.4)
21
33
  ast (~> 2.4.1)
@@ -23,17 +35,19 @@ GEM
23
35
  rake (13.0.1)
24
36
  regexp_parser (1.7.1)
25
37
  rexml (3.2.4)
26
- rubocop (0.88.0)
38
+ rubocop (0.89.1)
27
39
  parallel (~> 1.10)
28
40
  parser (>= 2.7.1.1)
29
41
  rainbow (>= 2.2.2, < 4.0)
30
42
  regexp_parser (>= 1.7)
31
43
  rexml
32
- rubocop-ast (>= 0.1.0, < 1.0)
44
+ rubocop-ast (>= 0.3.0, < 1.0)
33
45
  ruby-progressbar (~> 1.7)
34
46
  unicode-display_width (>= 1.4.0, < 2.0)
35
- rubocop-ast (0.2.0)
36
- parser (>= 2.7.0.1)
47
+ rubocop-ast (0.3.0)
48
+ parser (>= 2.7.1.4)
49
+ rubocop-minitest (0.10.1)
50
+ rubocop (>= 0.87)
37
51
  rubocop-performance (1.7.1)
38
52
  rubocop (>= 0.82.0)
39
53
  rubocop-sensible (0.3.0)
@@ -48,19 +62,26 @@ GEM
48
62
  term-ansicolor (1.7.1)
49
63
  tins (~> 1.0)
50
64
  thor (1.0.1)
65
+ thread_safe (0.3.6)
51
66
  tins (1.25.0)
52
67
  sync
68
+ tzinfo (1.2.7)
69
+ thread_safe (~> 0.1)
53
70
  unicode-display_width (1.7.0)
71
+ zeitwerk (2.4.0)
54
72
 
55
73
  PLATFORMS
56
74
  ruby
57
75
 
58
76
  DEPENDENCIES
77
+ activesupport
59
78
  alba!
60
79
  coveralls
61
80
  minitest (~> 5.0)
81
+ oj (~> 3.10)
62
82
  rake (~> 13.0)
63
83
  rubocop (>= 0.79.0)
84
+ rubocop-minitest (~> 0.10.1)
64
85
  rubocop-performance (~> 1.7.1)
65
86
  rubocop-sensible (~> 0.3.0)
66
87
 
data/README.md CHANGED
@@ -1,10 +1,27 @@
1
+ [![Gem Version](https://badge.fury.io/rb/alba.svg)](https://badge.fury.io/rb/alba)
1
2
  [![Build Status](https://travis-ci.com/okuramasafumi/alba.svg?branch=master)](https://travis-ci.com/okuramasafumi/alba)
2
3
  [![Coverage Status](https://coveralls.io/repos/github/okuramasafumi/alba/badge.svg?branch=master)](https://coveralls.io/github/okuramasafumi/alba?branch=master)
3
4
  [![Maintainability](https://api.codeclimate.com/v1/badges/fdab4cc0de0b9addcfe8/maintainability)](https://codeclimate.com/github/okuramasafumi/alba/maintainability)
4
5
 
5
6
  # Alba
6
7
 
7
- `Alba` is a stupid, fast and easy to use JSON serializer.
8
+ `Alba` is the fastest JSON serializer for Ruby.
9
+
10
+ # Why yet another JSON serializer?
11
+
12
+ We know that there are several other JSON serializers for Ruby around, but none of them made us satisfied.
13
+
14
+ Alba has some advantages over other JSON serializers which we've wanted to have.
15
+
16
+ ## Easy to understand
17
+
18
+ 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.
19
+
20
+ 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!
21
+
22
+ ## Performance
23
+
24
+ Alba is faster than most of the alternatives. We have a [benchmark](https://gist.github.com/okuramasafumi/4e375525bd3a28e4ca812d2a3b3e5829).
8
25
 
9
26
  ## Installation
10
27
 
@@ -24,6 +41,24 @@ Or install it yourself as:
24
41
 
25
42
  ## Usage
26
43
 
44
+ ### Configuration
45
+
46
+ Alba's configuration is fairly simple.
47
+
48
+ #### Backend
49
+
50
+ Backend is the actual part serializing an object into JSON. Alba supports these backends.
51
+
52
+ * Oj, the fastest. Gem installation required.
53
+ * active_support, mostly for Rails. Gem installation required.
54
+ * default or json, with no external dependencies.
55
+
56
+ You can set a backend like this:
57
+
58
+ ```ruby
59
+ Alba.backend = :oj
60
+ ```
61
+
27
62
  ### Simple serialization with key
28
63
 
29
64
  ```ruby
@@ -124,15 +159,65 @@ end
124
159
 
125
160
  Although this might be useful sometimes, it's generally recommended to define a class for both Resource and Serializer.
126
161
 
162
+ ### Inheritance and Ignorance
163
+
164
+ You can `exclude` or `ignore` certain attributes using `ignoring`.
165
+
166
+ ```ruby
167
+ class Foo
168
+ attr_accessor :id, :name, :body
169
+
170
+ def initialize(id, name, body)
171
+ @id = id
172
+ @name = name
173
+ @body = body
174
+ end
175
+ end
176
+
177
+ class GenericFooResource
178
+ include Alba::Resource
179
+
180
+ attributes :id, :name, :body
181
+ end
182
+
183
+ class RestrictedFooResouce < GenericFooResource
184
+ ignoring :id, :body
185
+ end
186
+
187
+ RestrictedFooResouce.new(foo).serialize
188
+ # => '{"name":"my foo"}'
189
+ end
190
+ ```
191
+
127
192
  ## Comparison
128
193
 
129
- Since Alba is intended to be stupid, there are many things Alba can't do while other gems can. However, from the same reason, it's extremely faster than alternatives.
194
+ Alba is faster than alternatives.
130
195
  For a performance benchmark, see https://gist.github.com/okuramasafumi/4e375525bd3a28e4ca812d2a3b3e5829.
131
196
 
197
+ ## Rails
198
+
199
+ When you use Alba in Rails, you can create an initializer file with the line below for compatibility with Rails JSON encoder.
200
+
201
+ ```ruby
202
+ Alba.backend = :active_support
203
+ ```
204
+
132
205
  ## Why named "Alba"?
133
206
 
134
207
  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.
135
208
 
209
+ ## Alba internals
210
+
211
+ Alba has three component, `Serializer`, `Resource` and `Value` (`Value` is conceptual and not implemented directly).
212
+
213
+ `Serializer` is a component responsible for rendering JSON output with `Resource`. `Serializer` can add more data to `Resource` such as `metadata`. Users can define one single `Serializer` and reuse it for all `Resource`s. The main interface is `#serialize`.
214
+
215
+ `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`.
216
+
217
+ `One` and `Many` are the special object fetching other resources and converting them into Hash.
218
+
219
+ The main `Alba` module holds config values and one convenience method, `.serialize`.
220
+
136
221
  ## Development
137
222
 
138
223
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -1,27 +1,27 @@
1
1
  require_relative 'lib/alba/version'
2
2
 
3
3
  Gem::Specification.new do |spec|
4
- spec.name = "alba"
4
+ spec.name = 'alba'
5
5
  spec.version = Alba::VERSION
6
- spec.authors = ["OKURA Masafumi"]
7
- spec.email = ["masafumi.o1988@gmail.com"]
6
+ spec.authors = ['OKURA Masafumi']
7
+ spec.email = ['masafumi.o1988@gmail.com']
8
8
 
9
- spec.summary = "Fast and flexible JSON serializer"
10
- spec.description = "Fast and flexible JSON serializer"
11
- spec.homepage = "https://github.com/okuramasafumi/alba"
12
- spec.license = "MIT"
13
- spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
9
+ spec.summary = 'Alba is the fastest JSON serializer for Ruby.'
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
+ spec.homepage = 'https://github.com/okuramasafumi/alba'
12
+ spec.license = 'MIT'
13
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.5.8')
14
14
 
15
- spec.metadata["homepage_uri"] = spec.homepage
16
- spec.metadata["source_code_uri"] = "https://github.com/okuramasafumi/alba"
17
- spec.metadata["changelog_uri"] = "https://github.com/okuramasafumi/alba/CHANGELOG.md"
15
+ spec.metadata['homepage_uri'] = spec.homepage
16
+ spec.metadata['source_code_uri'] = 'https://github.com/okuramasafumi/alba'
17
+ spec.metadata['changelog_uri'] = 'https://github.com/okuramasafumi/alba/CHANGELOG.md'
18
18
 
19
19
  # Specify which files should be added to the gem when it is released.
20
20
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
21
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
22
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
23
  end
24
- spec.bindir = "exe"
24
+ spec.bindir = 'exe'
25
25
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
- spec.require_paths = ["lib"]
26
+ spec.require_paths = ['lib']
27
27
  end
@@ -7,12 +7,15 @@ require 'alba/resources/default_resource'
7
7
  # Core module
8
8
  module Alba
9
9
  class Error < StandardError; end
10
+ class UnsupportedBackend < Error; end
10
11
 
11
12
  class << self
12
- attr_reader :backend
13
+ attr_reader :backend, :encoder
14
+ attr_accessor :default_serializer
13
15
 
14
16
  def backend=(backend)
15
17
  @backend = backend&.to_sym
18
+ set_encoder
16
19
  end
17
20
 
18
21
  def serialize(object, with: nil, &block)
@@ -20,13 +23,50 @@ module Alba
20
23
 
21
24
  resource_class.class_eval(&block)
22
25
  resource = resource_class.new(object)
26
+ with ||= @default_serializer
23
27
  resource.serialize(with: with)
24
28
  end
25
29
 
26
30
  private
27
31
 
32
+ def set_encoder
33
+ @encoder = case @backend
34
+ when :oj
35
+ try_oj
36
+ when :active_support
37
+ try_active_support
38
+ when nil, :default, :json
39
+ default_encoder
40
+ else
41
+ raise Alba::UnsupportedBackend, "Unsupported backend, #{backend}"
42
+ end
43
+ end
44
+
45
+ def try_oj
46
+ require 'oj'
47
+ ->(hash) { Oj.dump(hash, mode: :strict) }
48
+ rescue LoadError
49
+ default_encoder
50
+ end
51
+
52
+ def try_active_support
53
+ require 'active_support/json'
54
+ ->(hash) { ActiveSupport::JSON.encode(hash) }
55
+ rescue LoadError
56
+ default_encoder
57
+ end
58
+
59
+ def default_encoder
60
+ lambda do |hash|
61
+ require 'json'
62
+ JSON.dump(hash)
63
+ end
64
+ end
65
+
28
66
  def resource_class
29
67
  ::Alba::Resources::DefaultResource.clone
30
68
  end
31
69
  end
70
+
71
+ @encoder = default_encoder
32
72
  end
@@ -0,0 +1,26 @@
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:, condition: nil, resource: nil, &block)
6
+ @name = name
7
+ @condition = condition
8
+ @block = block
9
+ @resource = resource || resource_class
10
+ raise ArgumentError, 'resource or block is required' if @resource.nil? && @block.nil?
11
+ end
12
+
13
+ def to_hash
14
+ :not_implemented
15
+ end
16
+
17
+ private
18
+
19
+ def resource_class
20
+ klass = ::Alba::Resources::DefaultResource.dup
21
+ klass.reset
22
+ klass.class_eval(&@block)
23
+ klass
24
+ end
25
+ end
26
+ 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
-
11
- def to_hash(target)
5
+ class Many < Association
6
+ def to_hash(target, params: {})
12
7
  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
8
+ objects = @condition.call(objects) if @condition
9
+ objects.map { |o| @resource.new(o, params: params).to_hash }
24
10
  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
-
11
- def to_hash(target)
5
+ class One < Association
6
+ def to_hash(target, params: {})
12
7
  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
8
+ object = @condition.call(object) if @condition
9
+ @resource.new(object, params: params).to_hash
24
10
  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,13 +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].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 = name == :_serializer ? nil : {}
16
- 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}")
17
16
  end
18
17
  end
19
18
  base.include InstanceMethods
@@ -22,9 +21,12 @@ module Alba
22
21
 
23
22
  # Instance methods
24
23
  module InstanceMethods
25
- def initialize(resource)
26
- @_resource = resource
27
- DSLS.each { |name| instance_variable_set("@#{name}", self.class.public_send(name)) }
24
+ attr_reader :object, :_key, :params
25
+
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)) }
28
30
  end
29
31
 
30
32
  def serialize(with: nil)
@@ -38,54 +40,89 @@ module Alba
38
40
  else
39
41
  raise ArgumentError, 'Unexpected type for with, possible types are Class or Proc'
40
42
  end
41
- serializer.new(serializable_hash).serialize
43
+ serializer.new(self).serialize
42
44
  end
43
45
 
44
46
  def serializable_hash
45
- @_attributes.transform_values do |attribute|
46
- attribute.to_hash(@_resource)
47
- end
47
+ collection? ? @object.map(&converter) : converter.call(@object)
48
48
  end
49
49
  alias to_hash serializable_hash
50
50
 
51
+ def key
52
+ @_key || self.class.name.delete_suffix('Resource').downcase.gsub(/:{2}/, '_').to_sym
53
+ end
54
+
51
55
  private
52
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, params: params)
67
+ else
68
+ raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
69
+ end
70
+ end
71
+ end
72
+ end
73
+
53
74
  def inline_extended_serializer(with)
54
75
  klass = ::Alba::Serializers::DefaultSerializer.clone
55
76
  klass.class_eval(&with)
56
77
  klass
57
78
  end
79
+
80
+ def collection?
81
+ @object.is_a?(Enumerable)
82
+ end
58
83
  end
59
84
 
60
85
  # Class methods
61
86
  module ClassMethods
62
- attr_accessor(*DSLS)
87
+ attr_reader(*DSLS.keys)
63
88
 
64
89
  def inherited(subclass)
65
- DSLS.each { |name| subclass.public_send("#{name}=", instance_variable_get("@#{name}")) }
90
+ super
91
+ DSLS.each_key { |name| subclass.instance_variable_set("@#{name}", instance_variable_get("@#{name}").clone) }
66
92
  end
67
93
 
68
94
  def attributes(*attrs)
69
- attrs.each { |attr_name| @_attributes[attr_name] = Attribute.new(name: attr_name, method: attr_name) }
95
+ attrs.each { |attr_name| @_attributes[attr_name.to_sym] = attr_name.to_sym }
70
96
  end
71
97
 
72
98
  def attribute(name, &block)
73
99
  raise ArgumentError, 'No block given in attribute method' unless block
74
100
 
75
- @_attributes[name] = Attribute.new(name: name, method: block)
101
+ @_attributes[name.to_sym] = block
76
102
  end
77
103
 
78
- def one(name, resource: nil, &block)
79
- @_attributes[name.to_sym] = One.new(name: name, resource: resource, &block)
104
+ def one(name, condition = nil, resource: nil, key: nil, &block)
105
+ @_attributes[key&.to_sym || name.to_sym] = One.new(name: name, condition: condition, resource: resource, &block)
80
106
  end
81
107
 
82
- def many(name, resource: nil, &block)
83
- @_attributes[name.to_sym] = Many.new(name: name, resource: resource, &block)
108
+ def many(name, condition = nil, resource: nil, key: nil, &block)
109
+ @_attributes[key&.to_sym || name.to_sym] = Many.new(name: name, condition: condition, resource: resource, &block)
84
110
  end
85
111
 
86
112
  def serializer(name)
87
113
  @_serializer = name <= Alba::Serializer ? name : nil
88
114
  end
115
+
116
+ def key(key)
117
+ @_key = key.to_sym
118
+ end
119
+
120
+ # Use this DSL in child class to ignore certain attributes
121
+ def ignoring(*attributes)
122
+ attributes.each do |attr_name|
123
+ @_attributes.delete(attr_name.to_sym)
124
+ end
125
+ end
89
126
  end
90
127
  end
91
128
  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
@@ -1,8 +1,8 @@
1
1
  module Alba
2
2
  # This module represents how a resource should be serialized.
3
- #
4
3
  module Serializer
5
4
  def self.included(base)
5
+ super
6
6
  base.include InstanceMethods
7
7
  base.extend ClassMethods
8
8
  end
@@ -10,39 +10,48 @@ module Alba
10
10
  # Instance methods
11
11
  module InstanceMethods
12
12
  def initialize(resource)
13
- @_resource = resource
14
- @_opts = self.class._opts || {}
15
- key = @_opts[:key]
16
- @_resource = {key.to_sym => @_resource} if key
13
+ @resource = resource
14
+ @hash = resource.serializable_hash
15
+ @hash = {key.to_sym => @hash} if key
16
+ # @hash is either Hash or Array
17
+ @hash.is_a?(Hash) ? @hash.merge!(metadata.to_h) : @hash << metadata
17
18
  end
18
19
 
19
20
  def serialize
20
- fallback = lambda do
21
- require 'json'
22
- JSON.dump(@_resource)
23
- end
24
- case Alba.backend
25
- when :oj
26
- begin
27
- require 'oj'
28
- -> { Oj.dump(@_resource, mode: :strict) }
29
- rescue LoadError
30
- fallback
31
- end
32
- else
33
- fallback
34
- end.call
21
+ Alba.encoder.call(@hash)
22
+ end
23
+
24
+ private
25
+
26
+ def key
27
+ opts = self.class._opts || {}
28
+ opts[:key] == true ? @resource.key : opts[:key]
29
+ end
30
+
31
+ def metadata
32
+ metadata = self.class._metadata || {}
33
+ metadata.transform_values { |block| block.call(@resource.object) }
35
34
  end
36
35
  end
37
36
 
38
37
  # Class methods
39
38
  module ClassMethods
40
- attr_reader :_opts
39
+ attr_reader :_opts, :_metadata
40
+
41
+ def inherited(subclass)
42
+ super
43
+ %w[_opts _metadata].each { |name| subclass.instance_variable_set("@#{name}", instance_variable_get("@#{name}")) }
44
+ end
41
45
 
42
46
  def set(key: false)
43
47
  @_opts ||= {}
44
48
  @_opts[:key] = key
45
49
  end
50
+
51
+ def metadata(name, &block)
52
+ @_metadata ||= {}
53
+ @_metadata[name] = block
54
+ end
46
55
  end
47
56
  end
48
57
  end
@@ -1,3 +1,3 @@
1
1
  module Alba
2
- VERSION = '0.5.0'.freeze
2
+ VERSION = '0.10.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,16 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alba
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.10.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-07-30 00:00:00.000000000 Z
11
+ date: 2020-08-23 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: Fast and flexible JSON serializer
13
+ description: Alba is designed to be a simple, easy to use and fast alternative to
14
+ existing JSON serializers. Its performance is better than almost all gems which
15
+ do similar things. The internal is so simple that it's easy to hack and maintain.
14
16
  email:
15
17
  - masafumi.o1988@gmail.com
16
18
  executables: []
@@ -30,7 +32,7 @@ files:
30
32
  - bin/console
31
33
  - bin/setup
32
34
  - lib/alba.rb
33
- - lib/alba/attribute.rb
35
+ - lib/alba/association.rb
34
36
  - lib/alba/many.rb
35
37
  - lib/alba/one.rb
36
38
  - lib/alba/resource.rb
@@ -54,7 +56,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
54
56
  requirements:
55
57
  - - ">="
56
58
  - !ruby/object:Gem::Version
57
- version: 2.3.0
59
+ version: 2.5.8
58
60
  required_rubygems_version: !ruby/object:Gem::Requirement
59
61
  requirements:
60
62
  - - ">="
@@ -64,5 +66,5 @@ requirements: []
64
66
  rubygems_version: 3.1.4
65
67
  signing_key:
66
68
  specification_version: 4
67
- summary: Fast and flexible JSON serializer
69
+ summary: Alba is the fastest JSON serializer for Ruby.
68
70
  test_files: []
@@ -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