alba 0.3.0 → 0.8.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: b4622052b8c6338ea135b98dd3376c8fdd51765b74fa51113edbfdc5afd17d58
4
- data.tar.gz: eaddf9d21c440dc8385a5afa425b9c02b327e9537544c7772216898af48d6603
3
+ metadata.gz: 592412974accb17b359ab871e293e2c839519f7cfffb31a04485bfe261b88202
4
+ data.tar.gz: d8fbb2ec751eac8e7dbe278e8993da6641eaa88f302b3438055b2062c08bd229
5
5
  SHA512:
6
- metadata.gz: cf931c28818461efff56c8e7b72a83157952bd464695e037ca3214a7aeb758c89b48b7ffe612016305c3087194a98c08b91944e4d993c0aa28ce6d5907208da6
7
- data.tar.gz: f30ffac69df4a39fcbcabe838165cff348dbe01c1d8f8f8c48930d0c3898ac5b60574d80f891434f3a92438d4e079043288d9f5d1b425b867adbb6e371ee55f1
6
+ metadata.gz: 97c3fc639d1d6b7fa588c78fa84f4be7318982ebf3154b9037e1b9c6ac38d1d9a0528bcf45618a5d1b1dc009df30441589eb27d1bdfbd8afaca1e61a0708e31e
7
+ data.tar.gz: 7abd201ba80bdfff652840e948e8aede24e90d0cfc26722f08044916f239387fd0bada0a1e9a0d71cf252ced9400d1549124349a1070ea7bd8c8e5a7be0ebc38
@@ -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:
@@ -18,10 +19,5 @@ Layout/SpaceInsideHashLiteralBraces:
18
19
  Metrics/MethodLength:
19
20
  Max: 20
20
21
 
21
- Naming/PredicateName:
22
- AllowedMethods:
23
- - 'has_one'
24
- - 'has_many'
25
-
26
22
  Style/FrozenStringLiteralComment:
27
23
  Enabled: false
@@ -2,5 +2,7 @@
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
6
8
  before_install: gem install bundler -v 2.1.4
data/Gemfile CHANGED
@@ -5,7 +5,9 @@ gemspec
5
5
 
6
6
  gem 'coveralls', require: false
7
7
  gem 'minitest', '~> 5.0'
8
+ gem 'oj', '~> 3.10'
8
9
  gem 'rake', '~> 13.0'
9
10
  gem 'rubocop', '>= 0.79.0', require: false
11
+ gem 'rubocop-minitest', '~> 0.10.1', require: false
10
12
  gem 'rubocop-performance', '~> 1.7.1', require: false
11
13
  gem 'rubocop-sensible', '~> 0.3.0', require: false
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- alba (0.3.0)
4
+ alba (0.8.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -16,6 +16,7 @@ GEM
16
16
  docile (1.3.2)
17
17
  json (2.3.1)
18
18
  minitest (5.14.1)
19
+ oj (3.10.8)
19
20
  parallel (1.19.2)
20
21
  parser (2.7.1.4)
21
22
  ast (~> 2.4.1)
@@ -34,6 +35,8 @@ GEM
34
35
  unicode-display_width (>= 1.4.0, < 2.0)
35
36
  rubocop-ast (0.2.0)
36
37
  parser (>= 2.7.0.1)
38
+ rubocop-minitest (0.10.1)
39
+ rubocop (>= 0.87)
37
40
  rubocop-performance (1.7.1)
38
41
  rubocop (>= 0.82.0)
39
42
  rubocop-sensible (0.3.0)
@@ -59,8 +62,10 @@ DEPENDENCIES
59
62
  alba!
60
63
  coveralls
61
64
  minitest (~> 5.0)
65
+ oj (~> 3.10)
62
66
  rake (~> 13.0)
63
67
  rubocop (>= 0.79.0)
68
+ rubocop-minitest (~> 0.10.1)
64
69
  rubocop-performance (~> 1.7.1)
65
70
  rubocop-sensible (~> 0.3.0)
66
71
 
data/README.md CHANGED
@@ -1,10 +1,11 @@
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 fast and flexible JSON serializer.
8
+ `Alba` is the fastest JSON serializer for Ruby.
8
9
 
9
10
  ## Installation
10
11
 
@@ -24,6 +25,8 @@ Or install it yourself as:
24
25
 
25
26
  ## Usage
26
27
 
28
+ ### Simple serialization with key
29
+
27
30
  ```ruby
28
31
  class User
29
32
  attr_accessor :id, :name, :email, :created_at, :updated_at
@@ -57,6 +60,92 @@ UserResource.new(user).serialize
57
60
  # => "{\"id\":1,\"name\":\"Masafumi OKURA\",\"name_with_email\":\"Masafumi OKURA: masafumi@example.com\"}"
58
61
  ```
59
62
 
63
+ ### Serialization with associations
64
+
65
+ ```ruby
66
+ class User
67
+ attr_reader :id, :created_at, :updated_at
68
+ attr_accessor :articles
69
+
70
+ def initialize(id)
71
+ @id = id
72
+ @created_at = Time.now
73
+ @updated_at = Time.now
74
+ @articles = []
75
+ end
76
+ end
77
+
78
+ class Article
79
+ attr_accessor :user_id, :title, :body
80
+
81
+ def initialize(user_id, title, body)
82
+ @user_id = user_id
83
+ @title = title
84
+ @body = body
85
+ end
86
+ end
87
+
88
+ class ArticleResource
89
+ include Alba::Resource
90
+
91
+ attributes :title
92
+ end
93
+
94
+ class UserResource1
95
+ include Alba::Resource
96
+
97
+ attributes :id
98
+
99
+ many :articles, resource: ArticleResource
100
+ end
101
+
102
+ user = User.new(1)
103
+ article1 = Article.new(1, 'Hello World!', 'Hello World!!!')
104
+ user.articles << article1
105
+ article2 = Article.new(2, 'Super nice', 'Really nice!')
106
+ user.articles << article2
107
+
108
+ UserResource1.new(user).serialize
109
+ # => '{"id":1,"articles":[{"title":"Hello World!"},{"title":"Super nice"}]}'
110
+ ```
111
+
112
+ ### Inline definition with `Alba.serialize`
113
+
114
+ `Alba.serialize` method is a shortcut to define everything inline.
115
+
116
+ ```ruby
117
+ Alba.serialize(user, with: proc { set key: :foo }) do
118
+ attributes :id
119
+ many :articles do
120
+ attributes :title, :body
121
+ end
122
+ end
123
+ # => '{"foo":{"id":1,"articles":[{"title":"Hello World!","body":"Hello World!!!"},{"title":"Super nice","body":"Really nice!"}]}}'
124
+ ```
125
+
126
+ Although this might be useful sometimes, it's generally recommended to define a class for both Resource and Serializer.
127
+
128
+ ## Comparison
129
+
130
+ Alba is faster than alternatives.
131
+ For a performance benchmark, see https://gist.github.com/okuramasafumi/4e375525bd3a28e4ca812d2a3b3e5829.
132
+
133
+ ## Why named "Alba"?
134
+
135
+ 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.
136
+
137
+ ## Alba internals
138
+
139
+ Alba has three component, `Serializer`, `Resource` and `Value` (`Value` is conceptual and not implemented directly).
140
+
141
+ `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`.
142
+
143
+ `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
+
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`.
146
+
147
+ The main `Alba` module holds config values and one convenience method, `.serialize`.
148
+
60
149
  ## Development
61
150
 
62
151
  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
@@ -1,6 +1,8 @@
1
1
  require 'alba/version'
2
+ require 'alba/serializers/default_serializer'
3
+ require 'alba/serializer'
2
4
  require 'alba/resource'
3
- require 'json'
5
+ require 'alba/resources/default_resource'
4
6
 
5
7
  # Core module
6
8
  module Alba
@@ -8,24 +10,25 @@ module Alba
8
10
 
9
11
  class << self
10
12
  attr_reader :backend
11
- end
13
+ attr_accessor :default_serializer
12
14
 
13
- def self.backend=(backend)
14
- @backend = backend&.to_sym
15
- end
15
+ def backend=(backend)
16
+ @backend = backend&.to_sym
17
+ end
18
+
19
+ def serialize(object, with: nil, &block)
20
+ raise ArgumentError, 'Block required' unless block
21
+
22
+ resource_class.class_eval(&block)
23
+ resource = resource_class.new(object)
24
+ with ||= @default_serializer
25
+ resource.serialize(with: with)
26
+ end
27
+
28
+ private
16
29
 
17
- def self.serialize(object)
18
- fallback = ->(resource) { resource.to_json }
19
- case backend
20
- when :oj
21
- begin
22
- require 'oj'
23
- ->(resource) { Oj.dump(resource) }
24
- rescue LoadError
25
- fallback
26
- end
27
- else
28
- fallback
29
- end.call(object)
30
+ def resource_class
31
+ ::Alba::Resources::DefaultResource.clone
32
+ end
30
33
  end
31
34
  end
@@ -7,12 +7,17 @@ require 'alba/serializers/default_serializer'
7
7
  module Alba
8
8
  # This module represents what should be serialized
9
9
  module Resource
10
- DSLS = [:_attributes, :_serializer].freeze
10
+ DSLS = [:_attributes, :_serializer, :_key].freeze
11
11
  def self.included(base)
12
12
  base.class_eval do
13
13
  # Initialize
14
14
  DSLS.each do |name|
15
- initial = name == :_serializer ? nil : {}
15
+ initial = case name
16
+ when :_attributes
17
+ {}
18
+ when :_serializer, :_name
19
+ nil
20
+ end
16
21
  instance_variable_set("@#{name}", initial) unless instance_variable_defined?("@#{name}")
17
22
  end
18
23
  end
@@ -22,8 +27,10 @@ module Alba
22
27
 
23
28
  # Instance methods
24
29
  module InstanceMethods
25
- def initialize(resource)
26
- @_resource = resource
30
+ attr_reader :_object
31
+
32
+ def initialize(object)
33
+ @_object = object
27
34
  DSLS.each { |name| instance_variable_set("@#{name}", self.class.public_send(name)) }
28
35
  end
29
36
 
@@ -33,28 +40,52 @@ module Alba
33
40
  @_serializer || Alba::Serializers::DefaultSerializer
34
41
  when ->(obj) { obj.is_a?(Class) && obj <= Alba::Serializer }
35
42
  with
36
- when Symbol
37
- const_get(with.to_s.capitalize)
38
- when String
39
- const_get(with)
43
+ when Proc
44
+ inline_extended_serializer(with)
45
+ else
46
+ raise ArgumentError, 'Unexpected type for with, possible types are Class or Proc'
40
47
  end
41
- serializer.new(serializable_hash).serialize
48
+ serializer.new(self).serialize
42
49
  end
43
50
 
44
- def serializable_hash
45
- @_attributes.transform_values do |attribute|
46
- attribute.to_hash(@_resource)
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
47
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
48
63
  end
49
64
  alias to_hash serializable_hash
65
+
66
+ def key
67
+ @_key || self.class.name.delete_suffix('Resource').downcase.gsub(/:{2}/, '_').to_sym
68
+ end
69
+
70
+ private
71
+
72
+ def inline_extended_serializer(with)
73
+ klass = ::Alba::Serializers::DefaultSerializer.clone
74
+ klass.class_eval(&with)
75
+ klass
76
+ end
77
+
78
+ def collection?
79
+ @_object.is_a?(Enumerable)
80
+ end
50
81
  end
51
82
 
52
83
  # Class methods
53
84
  module ClassMethods
54
- attr_accessor(*DSLS)
85
+ attr_reader(*DSLS)
55
86
 
56
87
  def inherited(subclass)
57
- DSLS.each { |name| subclass.public_send("#{name}=", instance_variable_get("@#{name}")) }
88
+ DSLS.each { |name| subclass.instance_variable_set("@#{name}", instance_variable_get("@#{name}")) }
58
89
  end
59
90
 
60
91
  def attributes(*attrs)
@@ -78,6 +109,10 @@ module Alba
78
109
  def serializer(name)
79
110
  @_serializer = name <= Alba::Serializer ? name : nil
80
111
  end
112
+
113
+ def key(key)
114
+ @_key = key.to_sym
115
+ end
81
116
  end
82
117
  end
83
118
  end
@@ -0,0 +1,9 @@
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,6 +1,5 @@
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)
6
5
  base.include InstanceMethods
@@ -10,19 +9,31 @@ module Alba
10
9
  # Instance methods
11
10
  module InstanceMethods
12
11
  def initialize(resource)
13
- @_resource = resource
14
12
  @_opts = self.class._opts || {}
15
- key = @_opts[:key]
16
- @_resource = {key.to_sym => @_resource} if key
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
+ @hash = {key.to_sym => @hash} if key
23
+ # @hash is either Hash or Array
24
+ @hash.is_a?(Hash) ? @hash.merge!(@_metadata.to_h) : @hash << @_metadata
17
25
  end
18
26
 
19
27
  def serialize
20
- fallback = -> { @_resource.to_json }
28
+ fallback = lambda do
29
+ require 'json'
30
+ JSON.dump(@hash)
31
+ end
21
32
  case Alba.backend
22
33
  when :oj
23
34
  begin
24
35
  require 'oj'
25
- -> { Oj.dump(@_resource) }
36
+ -> { Oj.dump(@hash, mode: :strict) }
26
37
  rescue LoadError
27
38
  fallback
28
39
  end
@@ -34,12 +45,21 @@ module Alba
34
45
 
35
46
  # Class methods
36
47
  module ClassMethods
37
- attr_reader :_opts
48
+ attr_reader :_opts, :_metadata
49
+
50
+ def inherited(subclass)
51
+ %w[_opts _metadata].each { |name| subclass.instance_variable_set("@#{name}", instance_variable_get("@#{name}")) }
52
+ end
38
53
 
39
54
  def set(key: false)
40
55
  @_opts ||= {}
41
56
  @_opts[:key] = key
42
57
  end
58
+
59
+ def metadata(name, &block)
60
+ @_metadata ||= {}
61
+ @_metadata[name] = block
62
+ end
43
63
  end
44
64
  end
45
65
  end
@@ -1,3 +1,5 @@
1
+ require 'alba/serializer'
2
+
1
3
  module Alba
2
4
  module Serializers
3
5
  # DefaultSerializer class is used when a user doesn't specify serializer opt.
@@ -1,3 +1,3 @@
1
1
  module Alba
2
- VERSION = '0.3.0'.freeze
2
+ VERSION = '0.8.0'.freeze
3
3
  end
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.3.0
4
+ version: 0.8.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-26 00:00:00.000000000 Z
11
+ date: 2020-08-06 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: []
@@ -34,6 +36,7 @@ files:
34
36
  - lib/alba/many.rb
35
37
  - lib/alba/one.rb
36
38
  - lib/alba/resource.rb
39
+ - lib/alba/resources/default_resource.rb
37
40
  - lib/alba/serializer.rb
38
41
  - lib/alba/serializers/default_serializer.rb
39
42
  - lib/alba/version.rb
@@ -53,7 +56,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
53
56
  requirements:
54
57
  - - ">="
55
58
  - !ruby/object:Gem::Version
56
- version: 2.3.0
59
+ version: 2.5.8
57
60
  required_rubygems_version: !ruby/object:Gem::Requirement
58
61
  requirements:
59
62
  - - ">="
@@ -63,5 +66,5 @@ requirements: []
63
66
  rubygems_version: 3.1.4
64
67
  signing_key:
65
68
  specification_version: 4
66
- summary: Fast and flexible JSON serializer
69
+ summary: Alba is the fastest JSON serializer for Ruby.
67
70
  test_files: []