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
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
|
[![Coverage Status](https://coveralls.io/repos/github/nesaulov/surrealist/badge.svg?branch=master)](https://coveralls.io/github/nesaulov/surrealist?branch=master)
|
4
4
|
[![Inline docs](http://inch-ci.org/github/nesaulov/surrealist.svg?branch=master)](http://inch-ci.org/github/nesaulov/surrealist)
|
5
5
|
[![Gem Version](https://badge.fury.io/rb/surrealist.svg)](https://rubygems.org/gems/surrealist)
|
6
|
+
[![Open Source Helpers](https://www.codetriage.com/nesaulov/surrealist/badges/users.svg)](https://www.codetriage.com/nesaulov/surrealist)
|
6
7
|
|
7
8
|
![Surrealist](surrealist-icon.png)
|
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).
|