surrealist 0.4.0 → 1.0.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.
- 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
|