o-serializer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 20456000ece5bd3ea6f400d9d1c8dbdaab293a35b935af7f3b0212a1717f826a
4
+ data.tar.gz: 94bac97ed93fddaead4b3fccf450d0aed4cbd6913bb99533a82ffa362ead40bf
5
+ SHA512:
6
+ metadata.gz: 5b50edf922a3461ae00288f3c669f9774aeff476165a4bcb4e4d257ea75aec404dcf4726d9a5a86eb5e7d1344deaad9ffd8490c852620ce2021381489c285b7a
7
+ data.tar.gz: 837b7bad46345a621847f7a9a98ba5aa761f2964e69bd4cc6cde2abe74d6da4212c80e956ca1c28c423fc70318d7d627f50eee0c863d5b6fb100453f7a0401bc
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.16.0
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in o-serializer.gemspec
6
+ gemspec
7
+
8
+ gem 'pry'
9
+ gem 'active_model_serializers'
10
+ gem 'fast_jsonapi'
11
+ gem 'benchmark-ips'
@@ -0,0 +1,109 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ o-serializer (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ actionpack (5.1.4)
10
+ actionview (= 5.1.4)
11
+ activesupport (= 5.1.4)
12
+ rack (~> 2.0)
13
+ rack-test (>= 0.6.3)
14
+ rails-dom-testing (~> 2.0)
15
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
16
+ actionview (5.1.4)
17
+ activesupport (= 5.1.4)
18
+ builder (~> 3.1)
19
+ erubi (~> 1.4)
20
+ rails-dom-testing (~> 2.0)
21
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
22
+ active_model_serializers (0.10.7)
23
+ actionpack (>= 4.1, < 6)
24
+ activemodel (>= 4.1, < 6)
25
+ case_transform (>= 0.2)
26
+ jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
27
+ activemodel (5.1.4)
28
+ activesupport (= 5.1.4)
29
+ activerecord (5.1.4)
30
+ activemodel (= 5.1.4)
31
+ activesupport (= 5.1.4)
32
+ arel (~> 8.0)
33
+ activesupport (5.1.4)
34
+ concurrent-ruby (~> 1.0, >= 1.0.2)
35
+ i18n (~> 0.7)
36
+ minitest (~> 5.1)
37
+ tzinfo (~> 1.1)
38
+ arel (8.0.0)
39
+ benchmark-ips (2.7.2)
40
+ builder (3.2.3)
41
+ case_transform (0.2)
42
+ activesupport
43
+ coderay (1.1.2)
44
+ concurrent-ruby (1.0.5)
45
+ crass (1.0.3)
46
+ diff-lcs (1.3)
47
+ erubi (1.7.0)
48
+ fast_jsonapi (1.0.17)
49
+ activerecord (~> 5.0)
50
+ activesupport (~> 5.0)
51
+ multi_json (~> 1.12)
52
+ oj (~> 3.3)
53
+ i18n (0.9.4)
54
+ concurrent-ruby (~> 1.0)
55
+ jsonapi-renderer (0.2.0)
56
+ loofah (2.1.1)
57
+ crass (~> 1.0.2)
58
+ nokogiri (>= 1.5.9)
59
+ method_source (0.9.0)
60
+ mini_portile2 (2.3.0)
61
+ minitest (5.11.3)
62
+ multi_json (1.13.1)
63
+ nokogiri (1.8.2)
64
+ mini_portile2 (~> 2.3.0)
65
+ oj (3.4.0)
66
+ pry (0.11.3)
67
+ coderay (~> 1.1.0)
68
+ method_source (~> 0.9.0)
69
+ rack (2.0.4)
70
+ rack-test (0.8.2)
71
+ rack (>= 1.0, < 3)
72
+ rails-dom-testing (2.0.3)
73
+ activesupport (>= 4.2.0)
74
+ nokogiri (>= 1.6)
75
+ rails-html-sanitizer (1.0.3)
76
+ loofah (~> 2.0)
77
+ rake (10.5.0)
78
+ rspec (3.7.0)
79
+ rspec-core (~> 3.7.0)
80
+ rspec-expectations (~> 3.7.0)
81
+ rspec-mocks (~> 3.7.0)
82
+ rspec-core (3.7.1)
83
+ rspec-support (~> 3.7.0)
84
+ rspec-expectations (3.7.0)
85
+ diff-lcs (>= 1.2.0, < 2.0)
86
+ rspec-support (~> 3.7.0)
87
+ rspec-mocks (3.7.0)
88
+ diff-lcs (>= 1.2.0, < 2.0)
89
+ rspec-support (~> 3.7.0)
90
+ rspec-support (3.7.1)
91
+ thread_safe (0.3.6)
92
+ tzinfo (1.2.5)
93
+ thread_safe (~> 0.1)
94
+
95
+ PLATFORMS
96
+ ruby
97
+
98
+ DEPENDENCIES
99
+ active_model_serializers
100
+ benchmark-ips
101
+ bundler (~> 1.16)
102
+ fast_jsonapi
103
+ o-serializer!
104
+ pry
105
+ rake (~> 10.0)
106
+ rspec (~> 3.0)
107
+
108
+ BUNDLED WITH
109
+ 1.16.1
@@ -0,0 +1,121 @@
1
+ # O::Serializer
2
+
3
+ OO-based data serialization library
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'o-serializer'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install o-serializer
20
+
21
+ ## Usage
22
+
23
+ Every `O` object responds to `.call`, that's a convention.
24
+
25
+ 1. Basic Usage
26
+
27
+ ``` ruby
28
+ User = Struct.new(:email, :password)
29
+
30
+ UserSerializer = O::Serializer[
31
+ email: O::Field[:email],
32
+ password: O::Field[:password]
33
+ ]
34
+
35
+ UserSerializer.call(User.new('one', 'two'))
36
+ # => { email: 'one', password: 'two' }
37
+ ```
38
+
39
+ 2. Shortcut `O::PlainFields`
40
+
41
+ ``` ruby
42
+ UserSerializer = O::Serializer[
43
+ **O::PlainFields[:email, :password]
44
+ ]
45
+ # => exactly the same behavior, works through 'to_hash' method
46
+ ```
47
+
48
+ 3. Collections
49
+
50
+ ``` ruby
51
+ ProfileSerializer = O::Serializer[
52
+ name: O::Field[:name]
53
+ ]
54
+
55
+ profiles = [...]
56
+
57
+ O::Many[ProfileSerializer].call(users)
58
+ # => [ { ... }, { ... }, ...]
59
+ ```
60
+
61
+ `O::Many` can be use to define associations.
62
+
63
+ ``` ruby
64
+ UserSerializer = O::Serializer[
65
+ tags: O::Many[TagSerializer]
66
+ ]
67
+ ```
68
+
69
+ 4. Keys transformation
70
+
71
+ ``` ruby
72
+ require 'ostruct'
73
+
74
+ user = OpenStruct.new(
75
+ :active? => true,
76
+ __tags: [OpenStruct.new(value: 'tag1')]
77
+ )
78
+
79
+ TagSerializer = O::Serializer[**O::PlainFields[:value]]
80
+
81
+ UserSerializer = O::Serializer[
82
+ is_active: O::Field[:active?],
83
+ tags: O::From[:__tags, O::Many[TagSerializer]]
84
+ ]
85
+
86
+ UserSerializer.call(user)
87
+ # => {:is_active=>true, :tags=>[{:value=>"tag1"}]}
88
+ ```
89
+
90
+ ## Benchmarks
91
+
92
+ See `benchmark/run.rb`
93
+
94
+ ```
95
+ Warming up --------------------------------------
96
+ #to_hash 12.722k i/100ms
97
+ O::Serializer 4.425k i/100ms
98
+ AMS 330.000 i/100ms
99
+ fast_jsonapi 4.323k i/100ms
100
+ Calculating -------------------------------------
101
+ #to_hash 139.382k (± 6.5%) i/s - 699.710k in 5.042120s
102
+ O::Serializer 46.346k (± 4.5%) i/s - 234.525k in 5.071141s
103
+ AMS 3.359k (± 3.3%) i/s - 16.830k in 5.015951s
104
+ fast_jsonapi 44.541k (± 4.5%) i/s - 224.796k in 5.057236s
105
+
106
+ Comparison:
107
+ #to_hash: 139381.5 i/s
108
+ O::Serializer: 46346.3 i/s - 3.01x slower
109
+ fast_jsonapi: 44540.7 i/s - 3.13x slower
110
+ AMS: 3359.0 i/s - 41.49x slower
111
+ ```
112
+
113
+ ## Development
114
+
115
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
116
+
117
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
118
+
119
+ ## Contributing
120
+
121
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/o-serializer.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,177 @@
1
+ require 'bundler/setup'
2
+
3
+ require 'o/serializer'
4
+ require 'active_model_serializers'
5
+ require 'fast_jsonapi'
6
+ require 'benchmark/ips'
7
+
8
+ class User
9
+ include ActiveModel::Model
10
+ include ActiveModel::Serialization
11
+
12
+ attr_accessor :id, :email, :password, :profile, :tags
13
+
14
+ def active?
15
+ true
16
+ end
17
+
18
+ def to_hash
19
+ {
20
+ id: id,
21
+ is_active: active?,
22
+ email: email,
23
+ profile: profile ? profile.to_hash : nil,
24
+ tags: tags.map(&:to_hash)
25
+ }
26
+ end
27
+
28
+ # FastJsonapi doesn't support custom methods
29
+ def is_active
30
+ active?
31
+ end
32
+
33
+ # FastJsonapi uses FKs for optimizations
34
+ def profile_id
35
+ profile.id if profile
36
+ end
37
+
38
+ def tag_ids
39
+ tags.map(&:id)
40
+ end
41
+ end
42
+
43
+ class Profile
44
+ include ActiveModel::Model
45
+ include ActiveModel::Serialization
46
+
47
+ attr_accessor :id, :first_name, :last_name
48
+
49
+ def avatar
50
+ "https://example.com/#{first_name}-#{last_name}.png"
51
+ end
52
+
53
+ def to_hash
54
+ {
55
+ first_name: first_name,
56
+ last_name: last_name,
57
+ avatar: avatar
58
+ }
59
+ end
60
+ end
61
+
62
+ class Tag
63
+ include ActiveModel::Model
64
+ include ActiveModel::Serialization
65
+
66
+ attr_accessor :id, :name
67
+
68
+ def to_hash
69
+ {
70
+ name: name
71
+ }
72
+ end
73
+ end
74
+
75
+ tag1 = Tag.new(id: 1, name: 'tag1')
76
+ tag2 = Tag.new(id: 2, name: 'tag2')
77
+ tag3 = Tag.new(id: 3, name: 'tag3')
78
+
79
+ profile1 = Profile.new(id: 1, first_name: 'fname1', last_name: 'lname1')
80
+ profile2 = Profile.new(id: 2, first_name: 'fname2')
81
+
82
+ user1 = User.new(email: 'email1', profile: profile1, tags: [tag1, tag2])
83
+ user2 = User.new(email: 'email2', profile: profile2, tags: [tag2, tag3])
84
+ user3 = User.new(email: 'email3', profile: nil, tags: [], id: 'ID')
85
+
86
+ users = [user1, user2, user3]
87
+
88
+ module O
89
+ TagSerializer = O::Serializer.new(
90
+ name: O::Field[:name]
91
+ )
92
+
93
+ ProfileSerializer = O::Serializer.new(
94
+ **O::PlainFields[
95
+ :first_name,
96
+ :last_name,
97
+ :avatar
98
+ ]
99
+ )
100
+
101
+ UserSerializer = O::Serializer.new(
102
+ id: ->(user) { user.id },
103
+ is_active: O::Field[:active?],
104
+ email: O::Field[:email],
105
+ profile: O::From[:profile, ProfileSerializer],
106
+ tags: O::From[:tags, O::Many[TagSerializer]]
107
+ )
108
+ end
109
+
110
+ module AMS
111
+ class TagSerializer < ActiveModel::Serializer
112
+ attributes :name
113
+ end
114
+
115
+ class ProfileSerializer < ActiveModel::Serializer
116
+ attributes :first_name, :last_name, :avatar
117
+ end
118
+
119
+ class UserSerializer < ActiveModel::Serializer
120
+ attributes :id, :is_active, :email
121
+
122
+ has_one :profile, serializer: ProfileSerializer
123
+ has_many :tags, serializer: TagSerializer
124
+
125
+ def is_active
126
+ object.active?
127
+ end
128
+ end
129
+ end
130
+
131
+ module FastJsonApi
132
+ class TagSerializer
133
+ include FastJsonapi::ObjectSerializer
134
+ attributes :name
135
+ end
136
+
137
+ class ProfileSerializer
138
+ include FastJsonapi::ObjectSerializer
139
+ attributes :first_name, :last_name, :avatar
140
+ end
141
+
142
+ class UserSerializer
143
+ include FastJsonapi::ObjectSerializer
144
+
145
+ attributes :id, :is_active, :email
146
+
147
+ has_one :profile, serializer: ProfileSerializer
148
+ has_many :tags, serializer: TagSerializer
149
+ end
150
+ end
151
+
152
+ Benchmark.ips do |x|
153
+ x.config time: 5, warmup: 2
154
+
155
+ x.report '#to_hash' do
156
+ users.map(&:to_hash)
157
+ end
158
+
159
+ x.report 'O::Serializer' do
160
+ O::Many[O::UserSerializer].call(users)
161
+ end
162
+
163
+ x.report 'AMS' do
164
+ ActiveModel::Serializer::CollectionSerializer.new(users, serializer: AMS::UserSerializer).as_json
165
+ end
166
+
167
+ x.report 'fast_jsonapi' do
168
+ FastJsonApi::UserSerializer.new(users).serializable_hash
169
+ end
170
+
171
+ x.compare!
172
+ end
173
+
174
+ # p users.map(&:to_hash)
175
+ # p O::Many[O::UserSerializer].call(users)
176
+ # p ActiveModel::Serializer::CollectionSerializer.new(users, serializer: AMS::UserSerializer).as_json
177
+ # p FastJsonApi::UserSerializer.new(users).serializable_hash
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "o/serializer"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,83 @@
1
+ require "o/serializer/version"
2
+
3
+ module O
4
+ module ArefShortcut
5
+ def [](*args)
6
+ new(*args)
7
+ end
8
+ end
9
+
10
+ READ = ->(object, key) {
11
+ object.respond_to?(:read_attribute_for_serialization) ?
12
+ object.read_attribute_for_serialization(key) :
13
+ object.public_send(key)
14
+ }
15
+
16
+ class Serializer
17
+ extend ArefShortcut
18
+
19
+ def initialize(fields)
20
+ @fields = fields
21
+ end
22
+
23
+ def call(object)
24
+ return nil if object.nil?
25
+ @fields
26
+ .map { |key, field| [key, field.call(object)] }
27
+ .to_h
28
+ end
29
+ end
30
+
31
+ class Field
32
+ extend ArefShortcut
33
+
34
+ def initialize(key)
35
+ @key = key
36
+ end
37
+
38
+ def call(object)
39
+ return nil if object.nil?
40
+ READ[object, @key]
41
+ end
42
+ end
43
+
44
+ class Many
45
+ extend ArefShortcut
46
+
47
+ def initialize(serializer)
48
+ @serializer = serializer
49
+ end
50
+
51
+ def call(collection)
52
+ collection.map { |item| @serializer.call(item) }
53
+ end
54
+ end
55
+
56
+ class PlainFields
57
+ extend ArefShortcut
58
+
59
+ def initialize(*keys)
60
+ @keys = keys
61
+ end
62
+
63
+ def to_hash
64
+ @keys
65
+ .map { |key| [key, Field[key]] }
66
+ .to_h
67
+ end
68
+ end
69
+
70
+ class From
71
+ extend ArefShortcut
72
+
73
+ def initialize(key, serializer)
74
+ @key = key
75
+ @serializer = serializer
76
+ end
77
+
78
+ def call(object)
79
+ return nil if object.nil?
80
+ @serializer.call(READ[object, @key])
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,5 @@
1
+ module O
2
+ class Serializer
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,26 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "o/serializer/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "o-serializer"
8
+ spec.version = O::Serializer::VERSION
9
+ spec.authors = ["Ilya Bylich"]
10
+ spec.email = ["ibylich@gmail.com"]
11
+
12
+ spec.summary = %q{o-serializer}
13
+ spec.description = %q{o-serializer}
14
+ spec.homepage = "https://github.com/iliabylich/o-serializer"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.16"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rspec", "~> 3.0"
26
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: o-serializer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ilya Bylich
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-02-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: o-serializer
56
+ email:
57
+ - ibylich@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".travis.yml"
64
+ - Gemfile
65
+ - Gemfile.lock
66
+ - README.md
67
+ - Rakefile
68
+ - benchmark/run.rb
69
+ - bin/console
70
+ - bin/setup
71
+ - lib/o/serializer.rb
72
+ - lib/o/serializer/version.rb
73
+ - o-serializer.gemspec
74
+ homepage: https://github.com/iliabylich/o-serializer
75
+ licenses: []
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 2.7.3
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: o-serializer
97
+ test_files: []