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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 18d601273dc48757785a2356db0353254bf692deb46ed7df9e128aa329eef928
|
4
|
+
data.tar.gz: 20bdde30e93d282483c88beb6de145fb198e332a3cf73379fa0f96d176a99895
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bec9aaebea9985002b24625c3d99316137344794cbab43a2e04d8fa685eda6c46a10f7dc072b37cbfef2428e6a3a0abbb5a7223823feb0ca546fd31128aab7a2
|
7
|
+
data.tar.gz: 9c47eb33373325982ab09eeba0aae2dacf963bdf27f0291d79301ea3f5cf1495369d9c7d9f808288cc6dcf596e4dcab409481255909e6dcc266acdf4e43b6c2b
|
data/.rubocop.yml
CHANGED
@@ -2,6 +2,12 @@ AllCops:
|
|
2
2
|
TargetRubyVersion: 2.2
|
3
3
|
Exclude:
|
4
4
|
- './gemfiles/*gemfile'
|
5
|
+
- './tmp/*'
|
6
|
+
|
7
|
+
Documentation:
|
8
|
+
Exclude:
|
9
|
+
- benchmarks/*rb
|
10
|
+
- spec/**/*rb
|
5
11
|
|
6
12
|
# Layout
|
7
13
|
|
@@ -51,6 +57,7 @@ Lint/RescueType:
|
|
51
57
|
Metrics/BlockLength:
|
52
58
|
Exclude:
|
53
59
|
- spec/**/*rb
|
60
|
+
- benchmarks
|
54
61
|
|
55
62
|
Metrics/CyclomaticComplexity:
|
56
63
|
Enabled: false
|
@@ -59,6 +66,7 @@ Metrics/MethodLength:
|
|
59
66
|
Max: 15
|
60
67
|
|
61
68
|
Metrics/LineLength:
|
69
|
+
Exclude: [benchmarks/*rb]
|
62
70
|
Max: 110
|
63
71
|
|
64
72
|
Metrics/PerceivedComplexity:
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
# 1.0.0
|
2
|
+
|
3
|
+
## Added
|
4
|
+
* `#build_schema` for collections from `Surrealist::Serializer` (@nesaulov) #74
|
5
|
+
* Oj dependency
|
6
|
+
* Multiple serializers API (@nulldef) #66
|
7
|
+
|
8
|
+
## Miscellaneous
|
9
|
+
* Benchmarks for Surrealist vs AMS
|
10
|
+
* A lot of memory & performance optimizations (@nesaulov) #64
|
11
|
+
|
1
12
|
# 0.4.0
|
2
13
|
|
3
14
|
## Added
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
[](https://coveralls.io/github/nesaulov/surrealist?branch=master)
|
4
4
|
[](http://inch-ci.org/github/nesaulov/surrealist)
|
5
5
|
[](https://rubygems.org/gems/surrealist)
|
6
|
+
[](https://www.codetriage.com/nesaulov/surrealist)
|
6
7
|
|
7
8
|

|
8
9
|
|
@@ -22,15 +23,21 @@ to serialize nested objects and structures. [Introductory blogpost.](https://med
|
|
22
23
|
* [Simple example](#simple-example)
|
23
24
|
* [Nested structures](#nested-structures)
|
24
25
|
* [Nested objects](#nested-objects)
|
25
|
-
* [
|
26
|
-
* [Usage with Dry::Types](#usage-with-drytypes)
|
26
|
+
* [Collection Surrealization](#collection-surrealization)
|
27
27
|
* [Defining custom serializers](#defining-custom-serializers)
|
28
|
+
* [Multiple serializers](#multiple-serializers)
|
28
29
|
* [Build schema](#build-schema)
|
29
|
-
* [
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
* [
|
30
|
+
* [Working with ORMs](#working-with-orms)
|
31
|
+
* [ActiveRecord](#activerecord)
|
32
|
+
* [ROM](#rom)
|
33
|
+
* [Sequel](#sequel)
|
34
|
+
* [Usage with Dry::Types](#usage-with-drytypes)
|
35
|
+
* [Delegating Surrealization](#delegating-surrealization)
|
36
|
+
* [Optional arguments](#optional-arguments)
|
37
|
+
* [Camelization](#camelization)
|
38
|
+
* [Include root](#include-root)
|
39
|
+
* [Root](#root)
|
40
|
+
* [Include namespaces](#include-namespaces)
|
34
41
|
* [Bool and Any](#bool-and-any)
|
35
42
|
* [Type errors](#type-errors)
|
36
43
|
* [Undefined methods in schema](#undefined-methods-in-schema)
|
@@ -38,6 +45,7 @@ to serialize nested objects and structures. [Introductory blogpost.](https://med
|
|
38
45
|
* [Roadmap](#roadmap)
|
39
46
|
* [Contributing](#contributing)
|
40
47
|
* [Credits](#credits)
|
48
|
+
* [Authors](#authors)
|
41
49
|
* [License](#license)
|
42
50
|
|
43
51
|
|
@@ -59,7 +67,7 @@ Or install it yourself as:
|
|
59
67
|
|
60
68
|
## Usage
|
61
69
|
Schema should be defined with a block that contains a hash. Every key of the schema should be
|
62
|
-
either a name of a method of the surrealizable object (or it's
|
70
|
+
either a name of a method of the surrealizable object (or it's ancestors/mixins),
|
63
71
|
or - in case you want to build json structure independently from object's structure - a symbol.
|
64
72
|
Every value of the hash should be a constant that represents a Ruby class,
|
65
73
|
that will be used for type-checks.
|
@@ -151,101 +159,33 @@ User.new.surrealize
|
|
151
159
|
|
152
160
|
```
|
153
161
|
|
154
|
-
###
|
155
|
-
|
162
|
+
### Collection Surrealization
|
163
|
+
Since 0.2.0 Surrealist has API for collection serialization. Example for ActiveRecord:
|
156
164
|
``` ruby
|
157
|
-
class
|
165
|
+
class User < ActiveRecord::Base
|
158
166
|
include Surrealist
|
159
167
|
|
160
168
|
json_schema do
|
161
|
-
{ name: String }
|
162
|
-
end
|
163
|
-
|
164
|
-
def name
|
165
|
-
'Host'
|
169
|
+
{ name: String, age: Integer }
|
166
170
|
end
|
167
171
|
end
|
168
|
-
|
169
|
-
class Guest
|
170
|
-
delegate_surrealization_to Host
|
171
172
|
|
172
|
-
|
173
|
-
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
Host.new.surrealize
|
178
|
-
# => '{ "name": "Host" }'
|
179
|
-
Guest.new.surrealize
|
180
|
-
# => '{ "name": "Guest" }'
|
181
|
-
```
|
182
|
-
Schema delegation works without inheritance as well, so if you wish you can
|
183
|
-
delegate surrealization not only to parent classes, but to any class. Please note that
|
184
|
-
in this case you have to `include Surrealist` in class that delegates schema as well.
|
185
|
-
``` ruby
|
186
|
-
class Potato
|
187
|
-
include Surrealist
|
188
|
-
delegate_surrealization_to Host
|
173
|
+
users = User.all
|
174
|
+
# => [#<User:0x007fa1485de878 id: 1, name: "Nikita", age: 23>, #<User:0x007fa1485de5f8 id: 2, name: "Alessandro", age: 24>]
|
189
175
|
|
190
|
-
|
191
|
-
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
Potato.new.surrealize
|
196
|
-
# => '{ "name": "Potato" }'
|
176
|
+
Surrealist.surrealize_collection(users)
|
177
|
+
# => '[{ "name": "Nikita", "age": 23 }, { "name": "Alessandro", "age": 24 }]'
|
197
178
|
```
|
179
|
+
You can find motivation behind introducing new API versus monkey-patching [here](https://alessandrominali.github.io/monkey_patching_real_example).
|
180
|
+
`#surrealize_collection` works for all data structures that respond to `#each`. All ActiveRecord
|
181
|
+
features (like associations, inheritance etc) are supported and covered. Further reading: [working with ORMs](#working-with-orms).
|
182
|
+
All optional arguments (`camelize`, `include_root` etc) are also supported.
|
198
183
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
your data will be coerced if it is able to, otherwise you will get a TypeError.
|
205
|
-
Assuming that you have defined module called `Types`:
|
206
|
-
|
207
|
-
``` ruby
|
208
|
-
require 'dry-types'
|
209
|
-
|
210
|
-
class Car
|
211
|
-
include Surrealist
|
212
|
-
|
213
|
-
json_schema do
|
214
|
-
{
|
215
|
-
age: Types::Coercible::Int,
|
216
|
-
brand: Types::Coercible::String,
|
217
|
-
doors: Types::Int.optional,
|
218
|
-
horsepower: Types::Strict::Int.constrained(gteq: 20),
|
219
|
-
fuel_system: Types::Any,
|
220
|
-
previous_owner: Types::String,
|
221
|
-
}
|
222
|
-
end
|
223
|
-
|
224
|
-
def age;
|
225
|
-
'7'
|
226
|
-
end
|
227
|
-
|
228
|
-
def previous_owner;
|
229
|
-
'John Doe'
|
230
|
-
end
|
231
|
-
|
232
|
-
def horsepower;
|
233
|
-
140
|
234
|
-
end
|
235
|
-
|
236
|
-
def brand;
|
237
|
-
'Toyota'
|
238
|
-
end
|
239
|
-
|
240
|
-
def doors; end
|
241
|
-
|
242
|
-
def fuel_system;
|
243
|
-
'Direct injection'
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
|
-
Car.new.surrealize
|
248
|
-
# => '{ "age": 7, "brand": "Toyota", "doors": null, "horsepower": 140, "fuel_system": "Direct injection", "previous_owner": "John Doe" }'
|
184
|
+
An additional and unique argument for `#surrealize_collection` is `raw` which is evaluated as a Boolean.
|
185
|
+
If this option is 'truthy' then the results will be an array of surrealized hashes (i.e. NOT a JSON string).
|
186
|
+
```
|
187
|
+
Surrealist.surrealize_collection(users, raw: true)
|
188
|
+
# => [{ "name": "Nikita", "age": 23 }, { "name": "Alessandro", "age": 24 }]
|
249
189
|
```
|
250
190
|
|
251
191
|
### Defining custom serializers
|
@@ -314,6 +254,43 @@ IncomeSerializer.new(income, current_user: User.find(3)).surrealize
|
|
314
254
|
# => '{ "amount": 200 }'
|
315
255
|
```
|
316
256
|
|
257
|
+
### Multiple serializers
|
258
|
+
|
259
|
+
You can define several custom serializers for one object and use it in different cases. Just mark it with a tag:
|
260
|
+
|
261
|
+
``` ruby
|
262
|
+
class PostSerializer < Surrealist::Serializer
|
263
|
+
json_schema { { id: Integer, title: String, author: { name: String } } }
|
264
|
+
end
|
265
|
+
|
266
|
+
class PreviewSerializer < Surrealist::Serializer
|
267
|
+
json_schema { { id: Integer, title: String } }
|
268
|
+
end
|
269
|
+
|
270
|
+
class Post
|
271
|
+
include Surrealist
|
272
|
+
|
273
|
+
surrealize_with PostSerializer
|
274
|
+
surrealize_with PreviewSerializer, tag: :preview
|
275
|
+
|
276
|
+
attr_reader :id, :title, :author
|
277
|
+
end
|
278
|
+
```
|
279
|
+
|
280
|
+
And then specify serializer's tag with `for` argument:
|
281
|
+
``` ruby
|
282
|
+
author = Struct.new(:name).new("John")
|
283
|
+
post = Post.new(1, "Ruby is awesome", author)
|
284
|
+
post.surrealize # => '{ "id": 1, "title": "Ruby is awesome", author: { name: "John" } }'
|
285
|
+
|
286
|
+
post.surrealize(for: :preview) # => '{ "id": 1, "title": "Ruby is awesome" }'
|
287
|
+
```
|
288
|
+
Or specify serializer explicitly with `serializer` argument:
|
289
|
+
|
290
|
+
``` ruby
|
291
|
+
post.surrealize(serializer: PreviewSerializer) # => '{ "id": 1, "title": "Ruby is awesome" }'
|
292
|
+
```
|
293
|
+
|
317
294
|
### Build schema
|
318
295
|
If you don't need to dump the hash to json, you can use `#build_schema`
|
319
296
|
method on the instance. It calculates values and checks types, but returns
|
@@ -324,7 +301,256 @@ Car.new.build_schema
|
|
324
301
|
# => { age: 7, brand: "Toyota", doors: nil, horsepower: 140, fuel_system: "Direct injection", previous_owner: "John Doe" }
|
325
302
|
```
|
326
303
|
|
327
|
-
###
|
304
|
+
### Working with ORMs
|
305
|
+
|
306
|
+
There are two kinds of return values of ORM methods: some return collections of objects, while others return instances.
|
307
|
+
For the first ones one should use `instance#surrealize`, whereas for the second ones `Surrealist.surrealize_collection(collection)`
|
308
|
+
Please keep in mind that if your serialization logic is [kept in a separate class](#defining-custom-serializers) which is inherited from
|
309
|
+
`Surrealist::Serializer`, than usage boils down to `YourSerializer.new(instance || collection).surrealize`.
|
310
|
+
|
311
|
+
#### ActiveRecord
|
312
|
+
All associations work as expected: `.has_many`, `.has_and_belongs_to_many` return collections,
|
313
|
+
`.has_one`, `.belongs_to` return instances.
|
314
|
+
|
315
|
+
Methods that return instances:
|
316
|
+
``` ruby
|
317
|
+
.find
|
318
|
+
.find_by
|
319
|
+
.find_by!
|
320
|
+
.take!
|
321
|
+
.first
|
322
|
+
.first!
|
323
|
+
.second
|
324
|
+
.second!
|
325
|
+
.third
|
326
|
+
.third!
|
327
|
+
.fourth
|
328
|
+
.fourth!
|
329
|
+
.fifth
|
330
|
+
.fifth!
|
331
|
+
.forty_two
|
332
|
+
.forty_two!
|
333
|
+
.last
|
334
|
+
.last!
|
335
|
+
.third_to_last
|
336
|
+
.third_to_last!
|
337
|
+
.second_to_last
|
338
|
+
.second_to_last!
|
339
|
+
```
|
340
|
+
Methods that return collections:
|
341
|
+
``` ruby
|
342
|
+
.all
|
343
|
+
.where
|
344
|
+
.where_not
|
345
|
+
.order
|
346
|
+
.take
|
347
|
+
.limit
|
348
|
+
.offset
|
349
|
+
.lock
|
350
|
+
.readonly
|
351
|
+
.reorder
|
352
|
+
.distinct
|
353
|
+
.find_each
|
354
|
+
.select
|
355
|
+
.group
|
356
|
+
.order
|
357
|
+
.except
|
358
|
+
.extending
|
359
|
+
.having
|
360
|
+
.references
|
361
|
+
.includes
|
362
|
+
.joins
|
363
|
+
```
|
364
|
+
|
365
|
+
#### ROM
|
366
|
+
|
367
|
+
For detailed usage example (covering ROM 3.x and ROM 4.x) please see `spec/orms/rom/`.
|
368
|
+
Under the hood ROM uses Sequel, and Sequel returns instances only on `.first`, `.last`, `.[]` and `.with_pk!`.
|
369
|
+
Collections are returned for all other methods.
|
370
|
+
``` ruby
|
371
|
+
container = ROM.container(:sql, ['sqlite::memory']) do |conf|
|
372
|
+
conf.default.create_table(:users) do
|
373
|
+
primary_key :id
|
374
|
+
column :name, String, null: false
|
375
|
+
column :email, String, null: false
|
376
|
+
end
|
377
|
+
# ...
|
378
|
+
end
|
379
|
+
|
380
|
+
users = UserRepo.new(container).users
|
381
|
+
# => #<ROM::Relation[Users] name=ROM::Relation::Name(users) dataset=#<Sequel::SQLite::Dataset: "SELECT `users`.`id`, `users`.`name`, `users`.`email` FROM `users` ORDER BY `users`.`id`">>
|
382
|
+
```
|
383
|
+
Basically, there are several ways to fetch/represent data in ROM:
|
384
|
+
``` ruby
|
385
|
+
# With json_schema defined in ROM::Struct::User
|
386
|
+
class ROM::Struct::User < ROM::Struct
|
387
|
+
include Surrealist
|
388
|
+
|
389
|
+
json_schema { { name: String } }
|
390
|
+
end
|
391
|
+
|
392
|
+
users.to_a.first # => #<ROM::Struct::User id=1 name="Jane Struct" email="jane@struct.rom">
|
393
|
+
users.to_a.first.surrealize # => "{\"name\":\"Jane Struct\"}"
|
394
|
+
|
395
|
+
users.where(id: 1).first # => #<ROM::Struct::User id=1 name="Jane Struct" email="jane@struct.rom">
|
396
|
+
users.where(id: 1).first.surrealize # => "{\"name\":\"Jane Struct\"}"
|
397
|
+
|
398
|
+
Surrealist.surrealize_collection(users.to_a) # => "[{\"name\":\"Jane Struct\"},{\"name\":\"Dane As\"},{\"name\":\"Jack Mapper\"}]"
|
399
|
+
|
400
|
+
# using ROM::Struct::Model#as(Representative) with json_schema defined in representative
|
401
|
+
class RomUser < Dry::Struct
|
402
|
+
include Surrealist
|
403
|
+
|
404
|
+
attribute :name, String
|
405
|
+
attribute :email, String
|
406
|
+
|
407
|
+
json_schema { { email: String } }
|
408
|
+
end
|
409
|
+
|
410
|
+
# ROM 3.x
|
411
|
+
rom_users = users.as(RomUser).to_a
|
412
|
+
|
413
|
+
# ROM 4.x
|
414
|
+
rom_users = users.map_to(RomUser).to_a
|
415
|
+
|
416
|
+
rom_users[1].surrealize # => "{\"email\":\"dane@as.rom\"}"
|
417
|
+
Surrealist.surrealize_collection(rom_users) # => "[{\"email\":\"jane@struct.rom\"},{\"email\":\"dane@as.rom\"},{\"email\":\"jack@mapper.rom\"}]"
|
418
|
+
|
419
|
+
# using Mappers
|
420
|
+
class UserModel
|
421
|
+
include Surrealist
|
422
|
+
|
423
|
+
json_schema { { id: Integer, email: String } }
|
424
|
+
|
425
|
+
attr_reader :id, :name, :email
|
426
|
+
|
427
|
+
def initialize(attributes)
|
428
|
+
@id, @name, @email = attributes.values_at(:id, :name, :email)
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
class UsersMapper < ROM::Mapper
|
433
|
+
register_as :user_obj
|
434
|
+
relation :users
|
435
|
+
model UserModel
|
436
|
+
end
|
437
|
+
|
438
|
+
# ROM 3.x
|
439
|
+
mapped = users.as(:user_obj)
|
440
|
+
# ROM 4.x
|
441
|
+
mapped = users.map_with(:user_obj)
|
442
|
+
|
443
|
+
mapped.to_a[2] # => #<UserModel:0x00007f8ec19fb3c8 @email="jack@mapper.rom", @id=3, @name="Jack Mapper">
|
444
|
+
mapped.where(id: 3).first # => #<UserModel:0x00007f8ec19fb3c8 @email="jack@mapper.rom", @id=3, @name="Jack Mapper">
|
445
|
+
mapped.to_a[2].surrealize # => "{\"id\":3,\"email\":\"jack@mapper.rom\"}"
|
446
|
+
Surrealist.surrealize_collection(mapped.to_a) # => "[{\"email\":\"jane@struct.rom\"},{\"email\":\"dane@as.rom\"},{\"email\":\"jack@mapper.rom\"}]"
|
447
|
+
Surrealist.surrealize_collection(mapped.where { id < 4 }.to_a) # => "[{\"email\":\"jane@struct.rom\"},{\"email\":\"dane@as.rom\"},{\"email\":\"jack@mapper.rom\"}]"
|
448
|
+
```
|
449
|
+
|
450
|
+
#### Sequel
|
451
|
+
Basically, Sequel returns instances only on `.first`, `.last`, `.[]` and `.with_pk!`. Collections are returned for all other methods.
|
452
|
+
Most of them are covered in `spec/orms/sequel` specs, please refer to them for code examples.
|
453
|
+
Associations serialization works the same way as it does with ActiveRecord.
|
454
|
+
|
455
|
+
### Usage with Dry::Types
|
456
|
+
You can use `Dry::Types` for type checking. Note that Surrealist does not ship
|
457
|
+
with dry-types by default, so you should do the [installation and configuration](http://dry-rb.org/gems/dry-types/)
|
458
|
+
by yourself. All built-in features of dry-types work, so if you use, say, `Types::Coercible::String`,
|
459
|
+
your data will be coerced if it is able to, otherwise you will get a TypeError.
|
460
|
+
Assuming that you have defined module called `Types`:
|
461
|
+
|
462
|
+
``` ruby
|
463
|
+
require 'dry-types'
|
464
|
+
|
465
|
+
class Car
|
466
|
+
include Surrealist
|
467
|
+
|
468
|
+
json_schema do
|
469
|
+
{
|
470
|
+
age: Types::Coercible::Int,
|
471
|
+
brand: Types::Coercible::String,
|
472
|
+
doors: Types::Int.optional,
|
473
|
+
horsepower: Types::Strict::Int.constrained(gteq: 20),
|
474
|
+
fuel_system: Types::Any,
|
475
|
+
previous_owner: Types::String,
|
476
|
+
}
|
477
|
+
end
|
478
|
+
|
479
|
+
def age;
|
480
|
+
'7'
|
481
|
+
end
|
482
|
+
|
483
|
+
def previous_owner;
|
484
|
+
'John Doe'
|
485
|
+
end
|
486
|
+
|
487
|
+
def horsepower;
|
488
|
+
140
|
489
|
+
end
|
490
|
+
|
491
|
+
def brand;
|
492
|
+
'Toyota'
|
493
|
+
end
|
494
|
+
|
495
|
+
def doors; end
|
496
|
+
|
497
|
+
def fuel_system;
|
498
|
+
'Direct injection'
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
Car.new.surrealize
|
503
|
+
# => '{ "age": 7, "brand": "Toyota", "doors": null, "horsepower": 140, "fuel_system": "Direct injection", "previous_owner": "John Doe" }'
|
504
|
+
```
|
505
|
+
|
506
|
+
### Delegating surrealization
|
507
|
+
You can share the `json_schema` between classes:
|
508
|
+
``` ruby
|
509
|
+
class Host
|
510
|
+
include Surrealist
|
511
|
+
|
512
|
+
json_schema do
|
513
|
+
{ name: String }
|
514
|
+
end
|
515
|
+
|
516
|
+
def name
|
517
|
+
'Host'
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
class Guest
|
522
|
+
delegate_surrealization_to Host
|
523
|
+
|
524
|
+
def name
|
525
|
+
'Guest'
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
Host.new.surrealize
|
530
|
+
# => '{ "name": "Host" }'
|
531
|
+
Guest.new.surrealize
|
532
|
+
# => '{ "name": "Guest" }'
|
533
|
+
```
|
534
|
+
Schema delegation works without inheritance as well, so if you wish you can
|
535
|
+
delegate surrealization not only to parent classes, but to any class. Please note that
|
536
|
+
in this case you have to `include Surrealist` in class that delegates schema as well.
|
537
|
+
``` ruby
|
538
|
+
class Potato
|
539
|
+
include Surrealist
|
540
|
+
delegate_surrealization_to Host
|
541
|
+
|
542
|
+
def name
|
543
|
+
'Potato'
|
544
|
+
end
|
545
|
+
end
|
546
|
+
|
547
|
+
Potato.new.surrealize
|
548
|
+
# => '{ "name": "Potato" }'
|
549
|
+
```
|
550
|
+
|
551
|
+
### Optional arguments
|
552
|
+
|
553
|
+
#### Camelization
|
328
554
|
If you need to have keys in camelBack, you can pass optional `camelize` argument
|
329
555
|
to `#surrealize or #build_schema`. From the previous example:
|
330
556
|
|
@@ -333,7 +559,7 @@ Car.new.surrealize(camelize: true)
|
|
333
559
|
# => '{ "age": 7, "brand": "Toyota", "doors": null, "horsepower": 140, "fuelSystem": "Direct injection", "previousOwner": "John Doe" }'
|
334
560
|
```
|
335
561
|
|
336
|
-
|
562
|
+
#### Include root
|
337
563
|
If you want to wrap the resulting JSON into a root key, you can pass optional `include_root` argument
|
338
564
|
to `#surrealize` or `#build_schema`. The root key in this case will be taken from the class name of the
|
339
565
|
surrealizable object.
|
@@ -373,62 +599,7 @@ Animal::Dog.new.surrealize(include_root: true)
|
|
373
599
|
# => '{ "dog": { "breed": "Collie" } }'
|
374
600
|
```
|
375
601
|
|
376
|
-
|
377
|
-
You can build wrap schema into a nested hash from namespaces of the object's class.
|
378
|
-
``` ruby
|
379
|
-
class BusinessSystem::Cashout::ReportSystem::Withdraws
|
380
|
-
include Surrealist
|
381
|
-
|
382
|
-
json_schema do
|
383
|
-
{ withdraws_amount: Integer }
|
384
|
-
end
|
385
|
-
|
386
|
-
def withdraws_amount
|
387
|
-
34
|
388
|
-
end
|
389
|
-
end
|
390
|
-
|
391
|
-
withdraws = BusinessSystem::Cashout::ReportSystem::Withdraws.new
|
392
|
-
|
393
|
-
withdraws.surrealize(include_namespaces: true)
|
394
|
-
# => '{ "business_system": { "cashout": { "report_system": { "withdraws": { "withdraws_amount": 34 } } } } }'
|
395
|
-
```
|
396
|
-
By default all namespaces will be taken. If you want you can explicitly specify the level of nesting:
|
397
|
-
``` ruby
|
398
|
-
withdraws.surrealize(include_namespaces: true, namespaces_nesting_level: 2)
|
399
|
-
# => '{ "report_system": { "withdraws": { "withdraws_amount": 34 } } }'
|
400
|
-
```
|
401
|
-
|
402
|
-
### Collection Surrealization
|
403
|
-
Since 0.2.0 Surrealist has API for collection serialization. Example for ActiveRecord:
|
404
|
-
``` ruby
|
405
|
-
class User < ActiveRecord::Base
|
406
|
-
include Surrealist
|
407
|
-
|
408
|
-
json_schema do
|
409
|
-
{ name: String, age: Integer }
|
410
|
-
end
|
411
|
-
end
|
412
|
-
|
413
|
-
users = User.all
|
414
|
-
# => [#<User:0x007fa1485de878 id: 1, name: "Nikita", age: 23>, #<User:0x007fa1485de5f8 id: 2, name: "Alessandro", age: 24>]
|
415
|
-
|
416
|
-
Surrealist.surrealize_collection(users)
|
417
|
-
# => '[{ "name": "Nikita", "age": 23 }, { "name": "Alessandro", "age": 24 }]'
|
418
|
-
```
|
419
|
-
You can find motivation behind introducing new API versus monkey-patching [here](https://alessandrominali.github.io/monkey_patching_real_example).
|
420
|
-
`#surrealize_collection` works for all data structures that respond to `#each`. All ActiveRecord
|
421
|
-
features (like associations, inheritance etc) are supported and covered. Other ORMs should work without
|
422
|
-
issues as well, tests are in progress. All optional arguments (`camelize`, `include_root` etc) are also supported.
|
423
|
-
|
424
|
-
An additional and unique arguement for `#surrealize_collection` is `raw` which is evalauted as a Boolean. If this option is 'truthy' then the results will be an array of surrealized hashes (ie. NOT a JSON string).
|
425
|
-
```
|
426
|
-
Surrealist.surrealize_collection(users, raw: true)
|
427
|
-
# => [{ "name": "Nikita", "age": 23 }, { "name": "Alessandro", "age": 24 }]
|
428
|
-
```
|
429
|
-
Guides on where to use `#surrealize_collection` vs `#surrealize` for all ORMs are coming.
|
430
|
-
|
431
|
-
### Root
|
602
|
+
#### Root
|
432
603
|
If you want to wrap the resulting JSON into a specified root key, you can pass optional `root` argument
|
433
604
|
to `#surrealize` or `#build_schema`. The `root` argument will be stripped of whitespaces.
|
434
605
|
``` ruby
|
@@ -457,6 +628,32 @@ Animal::Cat.new.surrealize(include_namespaces: true, root: 'kitten')
|
|
457
628
|
# => '{ "kitten": { "weight": "3 kilos" } }'
|
458
629
|
```
|
459
630
|
|
631
|
+
#### Include namespaces
|
632
|
+
You can build wrap schema into a nested hash from namespaces of the object's class.
|
633
|
+
``` ruby
|
634
|
+
class BusinessSystem::Cashout::ReportSystem::Withdraws
|
635
|
+
include Surrealist
|
636
|
+
|
637
|
+
json_schema do
|
638
|
+
{ withdraws_amount: Integer }
|
639
|
+
end
|
640
|
+
|
641
|
+
def withdraws_amount
|
642
|
+
34
|
643
|
+
end
|
644
|
+
end
|
645
|
+
|
646
|
+
withdraws = BusinessSystem::Cashout::ReportSystem::Withdraws.new
|
647
|
+
|
648
|
+
withdraws.surrealize(include_namespaces: true)
|
649
|
+
# => '{ "business_system": { "cashout": { "report_system": { "withdraws": { "withdraws_amount": 34 } } } } }'
|
650
|
+
```
|
651
|
+
By default all namespaces will be taken. If you want you can explicitly specify the level of nesting:
|
652
|
+
``` ruby
|
653
|
+
withdraws.surrealize(include_namespaces: true, namespaces_nesting_level: 2)
|
654
|
+
# => '{ "report_system": { "withdraws": { "withdraws_amount": 34 } } }'
|
655
|
+
```
|
656
|
+
|
460
657
|
### Bool and Any
|
461
658
|
If you have a parameter that is of boolean type, or if you don't care about the type, you
|
462
659
|
can use `Bool` and `Any` respectively.
|
@@ -518,7 +715,9 @@ type check will be passed. If you want to be strict about `nil`s consider using
|
|
518
715
|
|
519
716
|
## Roadmap
|
520
717
|
Here is a list of features that are not implemented yet (contributions are welcome):
|
521
|
-
* [
|
718
|
+
* [Having a config that would keep serialization parameters](https://github.com/nesaulov/surrealist/issues/76)
|
719
|
+
* [DSL for serializer contexts](https://github.com/nesaulov/surrealist/issues/67)
|
720
|
+
* Memoization/caching
|
522
721
|
|
523
722
|
## Contributing
|
524
723
|
Bug reports and pull requests are welcome on GitHub at https://github.com/nesaulov/surrealist.
|
@@ -528,5 +727,12 @@ to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of
|
|
528
727
|
## Credits
|
529
728
|
The icon was created by [Simon Child from Noun Project](https://thenounproject.com/term/salvador-dali/124566/) and is published under [Creative Commons License](https://creativecommons.org/licenses/by/3.0/us/)
|
530
729
|
|
730
|
+
## Authors
|
731
|
+
Created by [Nikita Esaulov](https://github.com/nesaulov) with help from [Alessandro Minali](https://github.com/AlessandroMinali) and [Alexey Bespalov](https://github.com/nulldef).
|
732
|
+
|
733
|
+
<a href="https://github.com/umbrellio/">
|
734
|
+
<img style="float: left;" src="https://umbrellio.github.io/Umbrellio/supported_by_umbrellio.svg" alt="Supported by Umbrellio" width="439" height="72">
|
735
|
+
</a>
|
736
|
+
|
531
737
|
## License
|
532
738
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|