surrealist 1.1.1 → 1.1.2
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.
- 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
|