surrealist 1.1.1 → 1.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/Gemfile +1 -0
- data/benchmarks/surrealist_vs_ams.rb +142 -80
- data/lib/surrealist/schema_definer.rb +0 -36
- data/lib/surrealist/version.rb +1 -1
- data/lib/surrealist.rb +1 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a430c1d28309d9ef1c1626e64fa3e0f482abef0215307a8ac8ea19083a44eb8b
|
4
|
+
data.tar.gz: dc45cc49c8b1af7676a9ad6738c42c83e10397587d7d21908d5345ee74248ff4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f2ae0819199d9c5ef9d0e221f3a743612fb18c1b392922788c9741eb6ed0172c2ba9aeef566d408b30bfd4a19df2a468edf98cef025923c70e0e2a30fb17d39c
|
7
|
+
data.tar.gz: e958a7d97360166bf852d8cb69b294b452176b0f981537df80478e1c5daaa817964a3d877746307d86d0018dc808ec89d58635eee594a7180c5af7a8b6070ba2
|
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
# rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
1
2
|
require_relative '../lib/surrealist'
|
2
3
|
require 'benchmark/ips'
|
3
4
|
require 'active_record'
|
4
5
|
require 'active_model'
|
5
6
|
require 'active_model_serializers'
|
7
|
+
require 'blueprinter'
|
6
8
|
|
7
9
|
ActiveRecord::Base.establish_connection(
|
8
10
|
adapter: 'sqlite3',
|
@@ -25,7 +27,7 @@ ActiveRecord::Schema.define do
|
|
25
27
|
create_table :books do |table|
|
26
28
|
table.column :title, :string
|
27
29
|
table.column :year, :string
|
28
|
-
table.belongs_to :author
|
30
|
+
table.belongs_to :author, foreign_key: true
|
29
31
|
end
|
30
32
|
end
|
31
33
|
|
@@ -49,11 +51,23 @@ class UserSurrealistSerializer < Surrealist::Serializer
|
|
49
51
|
json_schema { { name: String, email: String } }
|
50
52
|
end
|
51
53
|
|
54
|
+
class UserAMSSerializer < ActiveModel::Serializer
|
55
|
+
attributes :name, :email
|
56
|
+
end
|
57
|
+
|
58
|
+
class UserBlueprint < Blueprinter::Base
|
59
|
+
fields :name, :email
|
60
|
+
end
|
61
|
+
|
52
62
|
### Associations ###
|
53
63
|
|
54
64
|
class AuthorSurrealistSerializer < Surrealist::Serializer
|
55
65
|
json_schema do
|
56
|
-
{ name: String, last_name: String, full_name: String, age: Integer, books:
|
66
|
+
{ name: String, last_name: String, full_name: String, age: Integer, books: Array }
|
67
|
+
end
|
68
|
+
|
69
|
+
def books
|
70
|
+
object.books.to_a
|
57
71
|
end
|
58
72
|
|
59
73
|
def full_name
|
@@ -65,125 +79,159 @@ class BookSurrealistSerializer < Surrealist::Serializer
|
|
65
79
|
json_schema { { title: String, year: String } }
|
66
80
|
end
|
67
81
|
|
82
|
+
class BookAMSSerializer < ActiveModel::Serializer
|
83
|
+
attributes :title, :year
|
84
|
+
end
|
85
|
+
|
86
|
+
class BookBlueprint < Blueprinter::Base
|
87
|
+
fields :title, :year
|
88
|
+
end
|
89
|
+
|
90
|
+
class AuthorAMSSerializer < ActiveModel::Serializer
|
91
|
+
attributes :name, :last_name, :full_name, :age
|
92
|
+
has_many :books, serializer: BookAMSSerializer
|
93
|
+
end
|
94
|
+
|
95
|
+
class AuthorBlueprint < Blueprinter::Base
|
96
|
+
fields :name, :last_name, :age
|
97
|
+
field :full_name do |author|
|
98
|
+
"#{author.name} #{author.last_name}"
|
99
|
+
end
|
100
|
+
association :books, blueprint: BookBlueprint
|
101
|
+
end
|
102
|
+
|
68
103
|
class Author < ActiveRecord::Base
|
69
104
|
include Surrealist
|
70
105
|
surrealize_with AuthorSurrealistSerializer
|
71
106
|
|
72
107
|
has_many :books
|
108
|
+
|
109
|
+
def full_name
|
110
|
+
"#{name} #{last_name}"
|
111
|
+
end
|
73
112
|
end
|
74
113
|
|
75
114
|
class Book < ActiveRecord::Base
|
76
115
|
include Surrealist
|
77
116
|
surrealize_with BookSurrealistSerializer
|
78
117
|
|
79
|
-
belongs_to :author
|
118
|
+
belongs_to :author, required: true
|
80
119
|
end
|
81
120
|
|
82
|
-
|
83
|
-
|
121
|
+
N = 3000
|
122
|
+
N.times { User.create!(name: random_name, email: "#{random_name}@test.com") }
|
123
|
+
(N / 2).times { Author.create!(name: random_name, last_name: random_name, age: rand(80)) }
|
124
|
+
N.times { Book.create!(title: random_name, year: "19#{rand(10..99)}", author_id: rand(1..N / 2)) }
|
84
125
|
|
85
|
-
|
86
|
-
|
126
|
+
def sort(obj)
|
127
|
+
case obj
|
128
|
+
when Array then obj.map { |el| sort(el) }
|
129
|
+
when Hash then obj.transform_values { |v| sort(v) }
|
130
|
+
else obj
|
87
131
|
end
|
88
|
-
|
89
|
-
has_many :books
|
90
132
|
end
|
91
133
|
|
92
|
-
|
93
|
-
|
134
|
+
def check_correctness(serializers)
|
135
|
+
results = serializers.map(&:call).map { |r| sort(JSON.parse(r)) }
|
136
|
+
raise 'Results are not the same' if results.uniq.size > 1
|
94
137
|
end
|
95
138
|
|
96
|
-
|
97
|
-
|
98
|
-
(N / 2).times { Author.create!(name: random_name, last_name: random_name, age: rand(80)) }
|
99
|
-
N.times { Book.create!(title: random_name, year: "19#{rand(10..99)}", author_id: rand(1..N)) }
|
100
|
-
|
101
|
-
def benchmark_instance(ams_arg = '')
|
102
|
-
user = User.find(rand(1..N))
|
139
|
+
def benchmark(names, serializers)
|
140
|
+
check_correctness(serializers)
|
103
141
|
|
104
142
|
Benchmark.ips do |x|
|
105
143
|
x.config(time: 5, warmup: 2)
|
106
144
|
|
107
|
-
x.report(
|
108
|
-
ActiveModelSerializers::SerializableResource.new(user).to_json
|
109
|
-
end
|
110
|
-
|
111
|
-
x.report('Surrealist: instance through .surrealize') do
|
112
|
-
user.surrealize
|
113
|
-
end
|
114
|
-
|
115
|
-
x.report('Surrealist: instance through Surrealist::Serializer') do
|
116
|
-
UserSurrealistSerializer.new(user).surrealize
|
117
|
-
end
|
145
|
+
names.zip(serializers).each { |name, proc| x.report(name, &proc) }
|
118
146
|
|
119
147
|
x.compare!
|
120
148
|
end
|
121
149
|
end
|
122
150
|
|
123
|
-
def
|
124
|
-
|
151
|
+
def benchmark_instance(ams_arg: '', oj_arg: '')
|
152
|
+
user = User.find(rand(1..N))
|
125
153
|
|
126
|
-
|
127
|
-
|
154
|
+
names = ["AMS#{[ams_arg, oj_arg].join(' ')}: instance",
|
155
|
+
'Surrealist: instance through .surrealize',
|
156
|
+
'Surrealist: instance through Surrealist::Serializer',
|
157
|
+
"ActiveModel::Serializers::JSON#{oj_arg} instance",
|
158
|
+
"Blueprinter#{oj_arg}"]
|
128
159
|
|
129
|
-
|
130
|
-
|
131
|
-
|
160
|
+
serializers = [-> { UserAMSSerializer.new(user).to_json },
|
161
|
+
-> { user.surrealize },
|
162
|
+
-> { UserSurrealistSerializer.new(user).surrealize },
|
163
|
+
-> { user.to_json(only: %i[name email]) },
|
164
|
+
-> { UserBlueprint.render(user) }]
|
132
165
|
|
133
|
-
|
134
|
-
|
135
|
-
end
|
166
|
+
benchmark(names, serializers)
|
167
|
+
end
|
136
168
|
|
137
|
-
|
138
|
-
|
139
|
-
end
|
169
|
+
def benchmark_collection(ams_arg: '', oj_arg: '')
|
170
|
+
users = User.all
|
140
171
|
|
141
|
-
|
142
|
-
|
172
|
+
names = ["AMS#{[ams_arg, oj_arg].join(' ')}: collection",
|
173
|
+
'Surrealist: collection through Surrealist.surrealize_collection()',
|
174
|
+
'Surrealist: collection through Surrealist::Serializer',
|
175
|
+
"ActiveModel::Serializers::JSON#{oj_arg} collection",
|
176
|
+
"Blueprinter collection#{oj_arg}"]
|
177
|
+
|
178
|
+
serializers = [lambda do
|
179
|
+
ActiveModel::Serializer::CollectionSerializer.new(
|
180
|
+
users, root: nil, serializer: UserAMSSerializer
|
181
|
+
).to_json
|
182
|
+
end,
|
183
|
+
-> { Surrealist.surrealize_collection(users) },
|
184
|
+
-> { UserSurrealistSerializer.new(users).surrealize },
|
185
|
+
-> { users.to_json(only: %i[name email]) },
|
186
|
+
-> { UserBlueprint.render(users) }]
|
187
|
+
|
188
|
+
benchmark(names, serializers)
|
143
189
|
end
|
144
190
|
|
145
191
|
def benchmark_associations_instance
|
146
192
|
instance = Author.find(rand((1..(N / 2))))
|
147
193
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
end
|
194
|
+
names = ['AMS (associations): instance',
|
195
|
+
'Surrealist (associations): instance through .surrealize',
|
196
|
+
'Surrealist (associations): instance through Surrealist::Serializer',
|
197
|
+
'ActiveModel::Serializers::JSON (associations)',
|
198
|
+
'Blueprinter (associations)']
|
199
|
+
|
200
|
+
serializers = [-> { AuthorAMSSerializer.new(instance).to_json },
|
201
|
+
-> { instance.surrealize },
|
202
|
+
-> { AuthorSurrealistSerializer.new(instance).surrealize },
|
203
|
+
lambda do
|
204
|
+
instance.to_json(only: %i[name last_name age], methods: %i[full_name],
|
205
|
+
include: { books: { only: %i[title year] } })
|
206
|
+
end,
|
207
|
+
-> { AuthorBlueprint.render(instance) }]
|
208
|
+
|
209
|
+
benchmark(names, serializers)
|
165
210
|
end
|
166
211
|
|
167
212
|
def benchmark_associations_collection
|
168
213
|
collection = Author.all
|
169
214
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
215
|
+
names = ['AMS (associations): collection',
|
216
|
+
'Surrealist (associations): collection through Surrealist.surrealize_collection()',
|
217
|
+
'Surrealist (associations): collection through Surrealist::Serializer',
|
218
|
+
'ActiveModel::Serializers::JSON (associations): collection',
|
219
|
+
'Blueprinter (associations): collection']
|
220
|
+
|
221
|
+
serializers = [lambda do
|
222
|
+
ActiveModel::Serializer::CollectionSerializer.new(
|
223
|
+
collection, root: nil, serializer: AuthorAMSSerializer
|
224
|
+
).to_json
|
225
|
+
end,
|
226
|
+
-> { Surrealist.surrealize_collection(collection) },
|
227
|
+
-> { AuthorSurrealistSerializer.new(collection).surrealize },
|
228
|
+
lambda do
|
229
|
+
collection.to_json(only: %i[name last_name age], methods: %i[full_name],
|
230
|
+
include: { books: { only: %i[title year] } })
|
231
|
+
end,
|
232
|
+
-> { AuthorBlueprint.render(collection) }]
|
233
|
+
|
234
|
+
benchmark(names, serializers)
|
187
235
|
end
|
188
236
|
|
189
237
|
# Default configuration
|
@@ -194,8 +242,21 @@ benchmark_collection
|
|
194
242
|
puts "\n------- Turning off AMS logger -------\n"
|
195
243
|
ActiveModelSerializers.logger.level = Logger::Severity::UNKNOWN
|
196
244
|
|
197
|
-
benchmark_instance('(without logging)')
|
198
|
-
benchmark_collection('(without logging)')
|
245
|
+
benchmark_instance(ams_arg: '(without logging)')
|
246
|
+
benchmark_collection(ams_arg: '(without logging)')
|
247
|
+
|
248
|
+
# Associations
|
249
|
+
benchmark_associations_instance
|
250
|
+
benchmark_associations_collection
|
251
|
+
|
252
|
+
puts "\n------- Enabling Oj.optimize_rails() & Blueprinter config.generator = Oj -------\n"
|
253
|
+
Oj.optimize_rails
|
254
|
+
Blueprinter.configure do |config|
|
255
|
+
config.generator = Oj
|
256
|
+
end
|
257
|
+
|
258
|
+
benchmark_instance(ams_arg: '(without logging)', oj_arg: '(with Oj)')
|
259
|
+
benchmark_collection(ams_arg: '(without logging)', oj_arg: '(with Oj)')
|
199
260
|
|
200
261
|
# Associations
|
201
262
|
benchmark_associations_instance
|
@@ -241,3 +302,4 @@ benchmark_associations_collection
|
|
241
302
|
# Surrealist (associations): collection through Surrealist.surrealize_collection(): 2.4 i/s
|
242
303
|
# Surrealist (associations): collection through Surrealist::Serializer: 2.4 i/s - 1.03x slower
|
243
304
|
# AMS (associations): collection: 1.5 i/s - 1.60x slower
|
305
|
+
# rubocop:enable Metrics/AbcSize,Metrics/MethodLength
|
@@ -19,42 +19,6 @@ module Surrealist
|
|
19
19
|
raise Surrealist::InvalidSchemaError, SCHEMA_TYPE_ERROR unless hash.is_a?(Hash)
|
20
20
|
|
21
21
|
Surrealist::VarsHelper.set_schema(klass, hash)
|
22
|
-
define_missing_methods(klass, hash) if klass < Surrealist::Serializer
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
# Defines all methods from the json_schema on Serializer instance in order to increase
|
28
|
-
# performance (comparing to using method_missing)
|
29
|
-
#
|
30
|
-
# @param [Object] klass class of the object where methods will be defined
|
31
|
-
#
|
32
|
-
# @param [Hash] hash the schema hash
|
33
|
-
def define_missing_methods(klass, hash)
|
34
|
-
methods = find_methods(hash)
|
35
|
-
klass.include(Module.new do
|
36
|
-
instance_exec do
|
37
|
-
methods.each do |method|
|
38
|
-
define_method method do
|
39
|
-
if (object = instance_variable_get('@object'))
|
40
|
-
object.public_send(method)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end)
|
46
|
-
end
|
47
|
-
|
48
|
-
# Takes out all keys from a hash
|
49
|
-
#
|
50
|
-
# @param [Hash] hash a hash to take keys from
|
51
|
-
#
|
52
|
-
# @return [Array] an array of keys
|
53
|
-
def find_methods(hash)
|
54
|
-
hash.each_with_object([]) do |(k, v), keys|
|
55
|
-
keys.push(k)
|
56
|
-
keys.concat(find_methods(v)) if v.is_a? Hash
|
57
|
-
end
|
58
22
|
end
|
59
23
|
end
|
60
24
|
end
|
data/lib/surrealist/version.rb
CHANGED
data/lib/surrealist.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: surrealist
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nikita Esaulov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-04-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: oj
|