surrealist 0.4.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +2 -0
- data/README.md +358 -152
- data/benchmarks/surrealist_vs_ams.rb +243 -0
- data/lib/surrealist/builder.rb +7 -11
- data/lib/surrealist/carrier.rb +10 -5
- data/lib/surrealist/class_methods.rb +2 -2
- data/lib/surrealist/copier.rb +11 -11
- data/lib/surrealist/exception_raiser.rb +13 -0
- data/lib/surrealist/instance_methods.rb +16 -2
- data/lib/surrealist/schema_definer.rb +51 -12
- data/lib/surrealist/serializer.rb +10 -1
- data/lib/surrealist/string_utils.rb +2 -2
- data/lib/surrealist/type_helper.rb +5 -5
- data/lib/surrealist/value_assigner.rb +13 -13
- data/lib/surrealist/vars_helper.rb +17 -6
- data/lib/surrealist/version.rb +1 -1
- data/lib/surrealist.rb +9 -12
- data/surrealist.gemspec +2 -0
- metadata +17 -2
@@ -0,0 +1,243 @@
|
|
1
|
+
require_relative '../lib/surrealist'
|
2
|
+
require 'benchmark/ips'
|
3
|
+
require 'active_record'
|
4
|
+
require 'active_model'
|
5
|
+
require 'active_model_serializers'
|
6
|
+
|
7
|
+
ActiveRecord::Base.establish_connection(
|
8
|
+
adapter: 'sqlite3',
|
9
|
+
database: ':memory:',
|
10
|
+
)
|
11
|
+
|
12
|
+
ActiveRecord::Migration.verbose = false
|
13
|
+
ActiveRecord::Schema.define do
|
14
|
+
create_table :users do |table|
|
15
|
+
table.column :name, :string
|
16
|
+
table.column :email, :string
|
17
|
+
end
|
18
|
+
|
19
|
+
create_table :authors do |table|
|
20
|
+
table.column :name, :string
|
21
|
+
table.column :last_name, :string
|
22
|
+
table.column :age, :int
|
23
|
+
end
|
24
|
+
|
25
|
+
create_table :books do |table|
|
26
|
+
table.column :title, :string
|
27
|
+
table.column :year, :string
|
28
|
+
table.belongs_to :author
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
ActiveModelSerializers.config.adapter = :json
|
33
|
+
|
34
|
+
def random_name
|
35
|
+
('a'..'z').to_a.shuffle.join('').first(10).capitalize
|
36
|
+
end
|
37
|
+
|
38
|
+
class User < ActiveRecord::Base
|
39
|
+
include Surrealist
|
40
|
+
|
41
|
+
json_schema { { name: String, email: String } }
|
42
|
+
end
|
43
|
+
|
44
|
+
class UserSerializer < ActiveModel::Serializer
|
45
|
+
attributes :name, :email
|
46
|
+
end
|
47
|
+
|
48
|
+
class UserSurrealistSerializer < Surrealist::Serializer
|
49
|
+
json_schema { { name: String, email: String } }
|
50
|
+
end
|
51
|
+
|
52
|
+
### Associations ###
|
53
|
+
|
54
|
+
class AuthorSurrealistSerializer < Surrealist::Serializer
|
55
|
+
json_schema do
|
56
|
+
{ name: String, last_name: String, full_name: String, age: Integer, books: Object }
|
57
|
+
end
|
58
|
+
|
59
|
+
def full_name
|
60
|
+
"#{object.name} #{object.last_name}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class BookSurrealistSerializer < Surrealist::Serializer
|
65
|
+
json_schema { { title: String, year: String } }
|
66
|
+
end
|
67
|
+
|
68
|
+
class Author < ActiveRecord::Base
|
69
|
+
include Surrealist
|
70
|
+
surrealize_with AuthorSurrealistSerializer
|
71
|
+
|
72
|
+
has_many :books
|
73
|
+
end
|
74
|
+
|
75
|
+
class Book < ActiveRecord::Base
|
76
|
+
include Surrealist
|
77
|
+
surrealize_with BookSurrealistSerializer
|
78
|
+
|
79
|
+
belongs_to :author
|
80
|
+
end
|
81
|
+
|
82
|
+
class AuthorSerializer < ActiveModel::Serializer
|
83
|
+
attributes :name, :last_name, :age
|
84
|
+
|
85
|
+
attribute :full_name do
|
86
|
+
"#{object.name} #{object.last_name}"
|
87
|
+
end
|
88
|
+
|
89
|
+
has_many :books
|
90
|
+
end
|
91
|
+
|
92
|
+
class BookSerializer < ActiveModel::Serializer
|
93
|
+
attributes :title, :year
|
94
|
+
end
|
95
|
+
|
96
|
+
N = 3000
|
97
|
+
N.times { User.create!(name: random_name, email: "#{random_name}@test.com") }
|
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))
|
103
|
+
|
104
|
+
Benchmark.ips do |x|
|
105
|
+
x.config(time: 5, warmup: 2)
|
106
|
+
|
107
|
+
x.report("AMS#{ams_arg}: instance") do
|
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
|
118
|
+
|
119
|
+
x.compare!
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def benchmark_collection(ams_arg = '')
|
124
|
+
users = User.all
|
125
|
+
|
126
|
+
Benchmark.ips do |x|
|
127
|
+
x.config(time: 5, warmup: 2)
|
128
|
+
|
129
|
+
x.report("AMS#{ams_arg}: collection") do
|
130
|
+
ActiveModelSerializers::SerializableResource.new(users).to_json
|
131
|
+
end
|
132
|
+
|
133
|
+
x.report('Surrealist: collection through Surrealist.surrealize_collection()') do
|
134
|
+
Surrealist.surrealize_collection(users)
|
135
|
+
end
|
136
|
+
|
137
|
+
x.report('Surrealist: collection through Surrealist::Serializer') do
|
138
|
+
UserSurrealistSerializer.new(users).surrealize
|
139
|
+
end
|
140
|
+
|
141
|
+
x.compare!
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def benchmark_associations_instance
|
146
|
+
instance = Author.find(rand((1..(N / 2))))
|
147
|
+
|
148
|
+
Benchmark.ips do |x|
|
149
|
+
x.config(time: 5, warmup: 2)
|
150
|
+
|
151
|
+
x.report('AMS (associations): instance') do
|
152
|
+
ActiveModelSerializers::SerializableResource.new(instance).to_json
|
153
|
+
end
|
154
|
+
|
155
|
+
x.report('Surrealist (associations): instance through .surrealize') do
|
156
|
+
instance.surrealize
|
157
|
+
end
|
158
|
+
|
159
|
+
x.report('Surrealist (associations): instance through Surrealist::Serializer') do
|
160
|
+
AuthorSurrealistSerializer.new(instance).surrealize
|
161
|
+
end
|
162
|
+
|
163
|
+
x.compare!
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def benchmark_associations_collection
|
168
|
+
collection = Author.all
|
169
|
+
|
170
|
+
Benchmark.ips do |x|
|
171
|
+
x.config(time: 5, warmup: 2)
|
172
|
+
|
173
|
+
x.report('AMS (associations): collection') do
|
174
|
+
ActiveModelSerializers::SerializableResource.new(collection).to_json
|
175
|
+
end
|
176
|
+
|
177
|
+
x.report('Surrealist (associations): collection through Surrealist.surrealize_collection()') do
|
178
|
+
Surrealist.surrealize_collection(collection)
|
179
|
+
end
|
180
|
+
|
181
|
+
x.report('Surrealist (associations): collection through Surrealist::Serializer') do
|
182
|
+
AuthorSurrealistSerializer.new(collection).surrealize
|
183
|
+
end
|
184
|
+
|
185
|
+
x.compare!
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Default configuration
|
190
|
+
benchmark_instance
|
191
|
+
benchmark_collection
|
192
|
+
|
193
|
+
# With AMS logger turned off
|
194
|
+
puts "\n------- Turning off AMS logger -------\n"
|
195
|
+
ActiveModelSerializers.logger.level = Logger::Severity::UNKNOWN
|
196
|
+
|
197
|
+
benchmark_instance('(without logging)')
|
198
|
+
benchmark_collection('(without logging)')
|
199
|
+
|
200
|
+
# Associations
|
201
|
+
benchmark_associations_instance
|
202
|
+
benchmark_associations_collection
|
203
|
+
|
204
|
+
# ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-darwin16]
|
205
|
+
# -- Instance --
|
206
|
+
# Comparison:
|
207
|
+
# Surrealist: instance through .surrealize: 39599.6 i/s
|
208
|
+
# Surrealist: instance through Surrealist::Serializer: 36452.5 i/s - same-ish: difference falls within error
|
209
|
+
# AMS: instance: 1938.9 i/s - 20.42x slower
|
210
|
+
#
|
211
|
+
# -- Collection --
|
212
|
+
# Comparison:
|
213
|
+
# Surrealist: collection through Surrealist.surrealize_collection(): 15.0 i/s
|
214
|
+
# Surrealist: collection through Surrealist::Serializer: 12.8 i/s - 1.17x slower
|
215
|
+
# AMS: collection: 6.1 i/s - 2.44x slower
|
216
|
+
#
|
217
|
+
# --- Without AMS logging (which is turned on by default) ---
|
218
|
+
#
|
219
|
+
# -- Instance --
|
220
|
+
# Comparison:
|
221
|
+
# Surrealist: instance through .surrealize: 40401.4 i/s
|
222
|
+
# Surrealist: instance through Surrealist::Serializer: 29488.3 i/s - 1.37x slower
|
223
|
+
# AMS(without logging): instance: 4571.7 i/s - 8.84x slower
|
224
|
+
#
|
225
|
+
# -- Collection --
|
226
|
+
# Comparison:
|
227
|
+
# Surrealist: collection through Surrealist.surrealize_collection(): 15.2 i/s
|
228
|
+
# Surrealist: collection through Surrealist::Serializer: 12.0 i/s - 1.27x slower
|
229
|
+
# AMS(without logging): collection: 6.1 i/s - 2.50x slower
|
230
|
+
#
|
231
|
+
# --- Associations ---
|
232
|
+
#
|
233
|
+
# -- Instance --
|
234
|
+
# Comparison:
|
235
|
+
# Surrealist (associations): instance through Surrealist::Serializer: 4016.3 i/s
|
236
|
+
# Surrealist (associations): instance through .surrealize: 4004.6 i/s - same-ish: difference falls within error
|
237
|
+
# AMS (associations): instance: 1303.0 i/s - 3.08x slower
|
238
|
+
#
|
239
|
+
# -- Collection --
|
240
|
+
# Comparison:
|
241
|
+
# Surrealist (associations): collection through Surrealist.surrealize_collection(): 2.4 i/s
|
242
|
+
# Surrealist (associations): collection through Surrealist::Serializer: 2.4 i/s - 1.03x slower
|
243
|
+
# AMS (associations): collection: 1.5 i/s - 1.60x slower
|
data/lib/surrealist/builder.rb
CHANGED
@@ -9,7 +9,7 @@ module Surrealist
|
|
9
9
|
# @param [Carrier] carrier instance of Surrealist::Carrier
|
10
10
|
# @param [Hash] schema the schema defined in the object's class.
|
11
11
|
# @param [Object] instance the instance of the object which methods from the schema are called on.
|
12
|
-
def initialize(carrier
|
12
|
+
def initialize(carrier, schema, instance)
|
13
13
|
@carrier = carrier
|
14
14
|
@schema = schema
|
15
15
|
@instance = instance
|
@@ -24,13 +24,13 @@ module Surrealist
|
|
24
24
|
# does not have a corresponding method on the object.
|
25
25
|
#
|
26
26
|
# @return [Hash] a hash that will be dumped into JSON.
|
27
|
-
def call(schema
|
27
|
+
def call(schema = @schema, instance = @instance)
|
28
28
|
schema.each do |schema_key, schema_value|
|
29
29
|
if schema_value.is_a?(Hash)
|
30
30
|
check_for_ar(schema, instance, schema_key, schema_value)
|
31
31
|
else
|
32
|
-
ValueAssigner.assign(
|
33
|
-
instance
|
32
|
+
ValueAssigner.assign(Schema.new(schema_key, schema_value),
|
33
|
+
instance) { |coerced_value| schema[schema_key] = coerced_value }
|
34
34
|
end
|
35
35
|
end
|
36
36
|
rescue NoMethodError => e
|
@@ -53,8 +53,7 @@ module Surrealist
|
|
53
53
|
if ar_collection?(instance, key)
|
54
54
|
construct_collection(schema, instance, key, value)
|
55
55
|
else
|
56
|
-
call(
|
57
|
-
instance: instance.respond_to?(key) ? instance.send(key) : instance)
|
56
|
+
call(value, instance.respond_to?(key) ? instance.send(key) : instance)
|
58
57
|
end
|
59
58
|
end
|
60
59
|
|
@@ -81,11 +80,8 @@ module Surrealist
|
|
81
80
|
# @return [Hash] the schema hash
|
82
81
|
def construct_collection(schema, instance, key, value)
|
83
82
|
schema[key] = []
|
84
|
-
instance.send(key).each do |
|
85
|
-
schema[key]
|
86
|
-
schema: Copier.deep_copy(hash: value, carrier: carrier),
|
87
|
-
instance: i,
|
88
|
-
)
|
83
|
+
instance.send(key).each do |inst|
|
84
|
+
schema[key].push(call(Copier.deep_copy(value, carrier), inst))
|
89
85
|
end
|
90
86
|
end
|
91
87
|
end
|
data/lib/surrealist/carrier.rb
CHANGED
@@ -43,6 +43,10 @@ module Surrealist
|
|
43
43
|
self
|
44
44
|
end
|
45
45
|
|
46
|
+
def no_args_provided?
|
47
|
+
@no_args ||= no_args
|
48
|
+
end
|
49
|
+
|
46
50
|
private
|
47
51
|
|
48
52
|
# Checks all boolean arguments
|
@@ -63,11 +67,7 @@ module Surrealist
|
|
63
67
|
# Checks if +namespaces_nesting_level+ is a positive integer
|
64
68
|
# @raise ArgumentError
|
65
69
|
def check_namespaces_nesting!
|
66
|
-
|
67
|
-
Surrealist::ExceptionRaiser.raise_invalid_nesting!(namespaces_nesting_level)
|
68
|
-
end
|
69
|
-
|
70
|
-
if namespaces_nesting_level <= 0
|
70
|
+
if !namespaces_nesting_level.is_a?(Integer) || namespaces_nesting_level <= 0
|
71
71
|
Surrealist::ExceptionRaiser.raise_invalid_nesting!(namespaces_nesting_level)
|
72
72
|
end
|
73
73
|
end
|
@@ -84,5 +84,10 @@ module Surrealist
|
|
84
84
|
def strip_root!
|
85
85
|
root.is_a?(String) && @root = root.strip
|
86
86
|
end
|
87
|
+
|
88
|
+
def no_args
|
89
|
+
!camelize && !include_root && !include_namespaces && root.nil? &&
|
90
|
+
namespaces_nesting_level == DEFAULT_NESTING_LEVEL
|
91
|
+
end
|
87
92
|
end
|
88
93
|
end
|
@@ -98,9 +98,9 @@ module Surrealist
|
|
98
98
|
# @param [Class] klass a class that should inherit form Surrealist::Serializer
|
99
99
|
#
|
100
100
|
# @raise ArgumentError if Surrealist::Serializer is not found in the ancestors chain
|
101
|
-
def surrealize_with(klass)
|
101
|
+
def surrealize_with(klass, tag: Surrealist::VarsHelper::DEFAULT_TAG)
|
102
102
|
if klass < Surrealist::Serializer
|
103
|
-
Surrealist::VarsHelper.
|
103
|
+
Surrealist::VarsHelper.add_serializer(self, klass, tag: tag)
|
104
104
|
else
|
105
105
|
raise ArgumentError, "#{klass} should be inherited from Surrealist::Serializer"
|
106
106
|
end
|
data/lib/surrealist/copier.rb
CHANGED
@@ -13,7 +13,7 @@ module Surrealist
|
|
13
13
|
# @param [Object] carrier instance of Carrier class that carries arguments passed to +surrealize+
|
14
14
|
#
|
15
15
|
# @return [Hash] a copied hash.
|
16
|
-
def deep_copy(hash
|
16
|
+
def deep_copy(hash, carrier, klass = false)
|
17
17
|
namespaces_condition = carrier.include_namespaces || carrier.namespaces_nesting_level != DEFAULT_NESTING_LEVEL # rubocop:disable Metrics/LineLength
|
18
18
|
|
19
19
|
if !klass && (carrier.include_root || namespaces_condition)
|
@@ -34,13 +34,15 @@ module Surrealist
|
|
34
34
|
#
|
35
35
|
# @return [Hash] deeply copied hash, possibly wrapped.
|
36
36
|
def copied_and_possibly_wrapped_hash(hash, klass, carrier, namespaces_condition)
|
37
|
+
return copy_hash(hash) if carrier.no_args_provided?
|
38
|
+
|
37
39
|
if carrier.root
|
38
|
-
wrap_schema_into_root(
|
40
|
+
wrap_schema_into_root(hash, carrier, carrier.root.to_s)
|
39
41
|
elsif namespaces_condition
|
40
|
-
wrap_schema_into_namespace(
|
42
|
+
wrap_schema_into_namespace(hash, carrier, klass)
|
41
43
|
elsif carrier.include_root
|
42
44
|
actual_class = Surrealist::StringUtils.extract_class(klass)
|
43
|
-
wrap_schema_into_root(
|
45
|
+
wrap_schema_into_root(hash, carrier, actual_class)
|
44
46
|
else
|
45
47
|
copy_hash(hash)
|
46
48
|
end
|
@@ -52,7 +54,7 @@ module Surrealist
|
|
52
54
|
# @param [Hash] wrapper the wrapper of the resulting hash.
|
53
55
|
#
|
54
56
|
# @return [Hash] deeply copied hash.
|
55
|
-
def copy_hash(hash, wrapper
|
57
|
+
def copy_hash(hash, wrapper = {})
|
56
58
|
hash.each_with_object(wrapper) do |(key, value), new|
|
57
59
|
new[key] = value.is_a?(Hash) ? copy_hash(value) : value
|
58
60
|
end
|
@@ -65,14 +67,14 @@ module Surrealist
|
|
65
67
|
# @param [String] root what the schema will be wrapped into
|
66
68
|
#
|
67
69
|
# @return [Hash] a hash with schema wrapped inside a root key.
|
68
|
-
def wrap_schema_into_root(schema
|
70
|
+
def wrap_schema_into_root(schema, carrier, root)
|
69
71
|
root_key = if carrier.camelize
|
70
72
|
Surrealist::StringUtils.camelize(root, false).to_sym
|
71
73
|
else
|
72
74
|
Surrealist::StringUtils.underscore(root).to_sym
|
73
75
|
end
|
74
76
|
result = Hash[root_key => {}]
|
75
|
-
copy_hash(schema,
|
77
|
+
copy_hash(schema, result[root_key])
|
76
78
|
|
77
79
|
result
|
78
80
|
end
|
@@ -84,11 +86,9 @@ module Surrealist
|
|
84
86
|
# @param [Object] carrier instance of Carrier class that carries arguments passed to +surrealize+
|
85
87
|
#
|
86
88
|
# @return [Hash] nested hash (see +inject_schema+)
|
87
|
-
def wrap_schema_into_namespace(schema
|
89
|
+
def wrap_schema_into_namespace(schema, carrier, klass)
|
88
90
|
nested_hash = Surrealist::StringUtils.break_namespaces(
|
89
|
-
klass,
|
90
|
-
camelize: carrier.camelize,
|
91
|
-
nesting_level: carrier.namespaces_nesting_level,
|
91
|
+
klass, carrier.camelize, carrier.namespaces_nesting_level
|
92
92
|
)
|
93
93
|
|
94
94
|
inject_schema(nested_hash, copy_hash(schema))
|
@@ -25,6 +25,9 @@ module Surrealist
|
|
25
25
|
# Error class for cases where +namespaces_nesting_level+ is set to 0.
|
26
26
|
class InvalidNestingLevel < ArgumentError; end
|
27
27
|
|
28
|
+
# Error class for unknown tag passed
|
29
|
+
class UnknownTagError < ArgumentError; end
|
30
|
+
|
28
31
|
# A class that raises all Surrealist exceptions
|
29
32
|
module ExceptionRaiser
|
30
33
|
CLASS_NAME_NOT_PASSED = "Can't wrap schema in root key - class name was not passed".freeze
|
@@ -89,6 +92,16 @@ module Surrealist
|
|
89
92
|
"#{e.message}. You have probably defined a key " \
|
90
93
|
"in the schema that doesn't have a corresponding method."
|
91
94
|
end
|
95
|
+
|
96
|
+
# Raises ArgumentError if a tag has no corresponding serializer
|
97
|
+
#
|
98
|
+
# @param [String] tag Wrong tag
|
99
|
+
#
|
100
|
+
# @raise Surrealist::UnknownTagError
|
101
|
+
def raise_unknown_tag!(tag)
|
102
|
+
raise Surrealist::UnknownTagError,
|
103
|
+
"The tag specified (#{tag}) has no corresponding serializer"
|
104
|
+
end
|
92
105
|
end
|
93
106
|
end
|
94
107
|
end
|
@@ -49,16 +49,30 @@ module Surrealist
|
|
49
49
|
# # => "{\"name\":\"Nikita\",\"age\":23}"
|
50
50
|
# # For more examples see README
|
51
51
|
def surrealize(**args)
|
52
|
-
|
52
|
+
return args[:serializer].new(self).surrealize(args) if args[:serializer]
|
53
|
+
|
54
|
+
if (serializer = find_serializer(args[:for]))
|
53
55
|
return serializer.new(self).surrealize(args)
|
54
56
|
end
|
55
57
|
|
56
|
-
|
58
|
+
Oj.dump(Surrealist.build_schema(instance: self, **args), mode: :compat)
|
57
59
|
end
|
58
60
|
|
59
61
|
# Invokes +Surrealist+'s class method +build_schema+
|
60
62
|
def build_schema(**args)
|
63
|
+
return args[:serializer].new(self).build_schema(args) if args[:serializer]
|
64
|
+
|
65
|
+
if (serializer = find_serializer(args[:for]))
|
66
|
+
return serializer.new(self).build_schema(args)
|
67
|
+
end
|
68
|
+
|
61
69
|
Surrealist.build_schema(instance: self, **args)
|
62
70
|
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def find_serializer(tag)
|
75
|
+
Surrealist::VarsHelper.find_serializer(self.class, tag: tag)
|
76
|
+
end
|
63
77
|
end
|
64
78
|
end
|
@@ -4,19 +4,58 @@ module Surrealist
|
|
4
4
|
# A class that defines a method on the object that stores the schema.
|
5
5
|
module SchemaDefiner
|
6
6
|
SCHEMA_TYPE_ERROR = 'Schema should be defined as a hash'.freeze
|
7
|
-
# Defines an instance variable on the object that stores the schema.
|
8
|
-
#
|
9
|
-
# @param [Object] klass class of the object that needs to be surrealized.
|
10
|
-
#
|
11
|
-
# @param [Hash] hash the schema defined in the object's class.
|
12
|
-
#
|
13
|
-
# @return [Hash] +@__surrealist_schema+ variable that stores the schema of the object.
|
14
|
-
#
|
15
|
-
# @raise +Surrealist::InvalidSchemaError+ if schema was defined not through a hash.
|
16
|
-
def self.call(klass, hash)
|
17
|
-
raise Surrealist::InvalidSchemaError, SCHEMA_TYPE_ERROR unless hash.is_a?(Hash)
|
18
7
|
|
19
|
-
|
8
|
+
class << self
|
9
|
+
# Defines an instance variable on the object that stores the schema.
|
10
|
+
#
|
11
|
+
# @param [Object] klass class of the object that needs to be surrealized.
|
12
|
+
#
|
13
|
+
# @param [Hash] hash the schema defined in the object's class.
|
14
|
+
#
|
15
|
+
# @return [Hash] +@__surrealist_schema+ variable that stores the schema of the object.
|
16
|
+
#
|
17
|
+
# @raise +Surrealist::InvalidSchemaError+ if schema was defined not through a hash.
|
18
|
+
def call(klass, hash)
|
19
|
+
raise Surrealist::InvalidSchemaError, SCHEMA_TYPE_ERROR unless hash.is_a?(Hash)
|
20
|
+
|
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
|
+
end
|
20
59
|
end
|
21
60
|
end
|
22
61
|
end
|
@@ -50,13 +50,22 @@ module Surrealist
|
|
50
50
|
|
51
51
|
# Passes build_schema to Surrealist
|
52
52
|
def build_schema(**args)
|
53
|
-
|
53
|
+
if object.respond_to?(:each)
|
54
|
+
build_collection_schema(args)
|
55
|
+
else
|
56
|
+
Surrealist.build_schema(instance: self, **args)
|
57
|
+
end
|
54
58
|
end
|
55
59
|
|
56
60
|
private
|
57
61
|
|
58
62
|
attr_reader :object, :context
|
59
63
|
|
64
|
+
# Maps collection and builds schema for each instance.
|
65
|
+
def build_collection_schema(**args)
|
66
|
+
object.map { |object| self.class.new(object, context).build_schema(args) }
|
67
|
+
end
|
68
|
+
|
60
69
|
# Methods not found inside serializer will be invoked on the object
|
61
70
|
def method_missing(method, *args, &block)
|
62
71
|
object.public_send(method, *args, &block)
|
@@ -37,7 +37,7 @@ module Surrealist
|
|
37
37
|
snake_string.to_s.gsub(UNDERSCORE_REGEXP) { Regexp.last_match[1].capitalize }
|
38
38
|
else
|
39
39
|
parts = snake_string.split(UNDERSCORE, 2)
|
40
|
-
parts[0]
|
40
|
+
parts[0].concat(camelize(parts[1])) if parts.size > 1
|
41
41
|
parts[0] || EMPTY_STRING
|
42
42
|
end
|
43
43
|
end
|
@@ -68,7 +68,7 @@ module Surrealist
|
|
68
68
|
# @raise Surrealist::InvalidNestingLevel if nesting level is specified as 0.
|
69
69
|
#
|
70
70
|
# @return [Hash] a nested hash.
|
71
|
-
def break_namespaces(klass, camelize
|
71
|
+
def break_namespaces(klass, camelize, nesting_level)
|
72
72
|
Surrealist::ExceptionRaiser.raise_invalid_nesting!(nesting_level) unless nesting_level > 0
|
73
73
|
|
74
74
|
klass.split(NAMESPACES_SEPARATOR).last(nesting_level).reverse.inject({}) do |a, n|
|
@@ -4,7 +4,7 @@ module Surrealist
|
|
4
4
|
# Service class for type checking
|
5
5
|
module TypeHelper
|
6
6
|
# Dry-types class matcher
|
7
|
-
DRY_TYPE_CLASS =
|
7
|
+
DRY_TYPE_CLASS = /Dry::Types/
|
8
8
|
|
9
9
|
class << self
|
10
10
|
# Checks if value returned from a method is an instance of type class specified
|
@@ -14,12 +14,12 @@ module Surrealist
|
|
14
14
|
# @param [Class] type class representing data type.
|
15
15
|
#
|
16
16
|
# @return [boolean]
|
17
|
-
def valid_type?(value
|
17
|
+
def valid_type?(value, type)
|
18
18
|
return true if type == Any
|
19
19
|
|
20
20
|
if type == Bool
|
21
21
|
Surrealist::Carrier::BOOLEANS.include?(value)
|
22
|
-
elsif dry_type?(type)
|
22
|
+
elsif defined?(Dry::Types) && dry_type?(type)
|
23
23
|
type.try(value).success?
|
24
24
|
else
|
25
25
|
value.nil? || value.is_a?(type)
|
@@ -32,7 +32,7 @@ module Surrealist
|
|
32
32
|
# @param [Class] type class representing data type
|
33
33
|
#
|
34
34
|
# @return [any] coerced value
|
35
|
-
def coerce(value
|
35
|
+
def coerce(value, type)
|
36
36
|
return value unless dry_type?(type)
|
37
37
|
return value if type.try(value).input == value
|
38
38
|
|
@@ -50,7 +50,7 @@ module Surrealist
|
|
50
50
|
if type.respond_to?(:primitive) || type.class.name.nil?
|
51
51
|
true
|
52
52
|
else
|
53
|
-
type.class.name
|
53
|
+
type.class.name =~ DRY_TYPE_CLASS
|
54
54
|
end
|
55
55
|
end
|
56
56
|
end
|