surrealist 0.1.2 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -3
- data/README.md +71 -20
- data/lib/surrealist/hash_utils.rb +131 -0
- data/lib/surrealist/instance_methods.rb +4 -4
- data/lib/surrealist/type_helper.rb +1 -0
- data/lib/surrealist/version.rb +1 -1
- data/lib/surrealist.rb +117 -93
- data/surrealist-icon.png +0 -0
- metadata +4 -3
- data/lib/surrealist/utils.rb +0 -69
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 712012011eaaceb220f8811228de4b07c6e08460
|
4
|
+
data.tar.gz: 5ed1ef86483b6dd864a8769359443c7f760e1ae2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b15fda40fbc90382e61703564c4e7a386c521036b60227e902df8e6fd1216c933d1281ac4dc5305f0b641475ebc83187b61354550a603e086026bc0de94d0330
|
7
|
+
data.tar.gz: dc9d2a1a70f6696f5dec95214125b7fe9677ff5c19e6aa9561318127f05b30ea60b0e0dcc694436ac9313879d9fd98e4e23824532ed1f9723534dedc3daa1734
|
data/CHANGELOG.md
CHANGED
@@ -1,11 +1,19 @@
|
|
1
|
+
# 0.1.4
|
2
|
+
## Added
|
3
|
+
* Optional `include_root` argument to wrap schema in a root key. [#15](https://github.com/nesaulov/surrealist/pull/15)
|
4
|
+
## Fixed
|
5
|
+
* Performance of schema cloning.
|
6
|
+
## Changed
|
7
|
+
* `Boolean` module renamed to `Bool`.
|
8
|
+
|
1
9
|
# 0.1.2
|
2
10
|
## Added
|
3
|
-
* `Any` module for skipping type checks
|
4
|
-
* Optional `camelize` argument to convert keys to camelBacks
|
11
|
+
* `Any` module for skipping type checks.
|
12
|
+
* Optional `camelize` argument to convert keys to camelBacks.
|
5
13
|
|
6
14
|
# 0.1.0
|
7
15
|
## Fixed
|
8
|
-
* Fix schema mutability issue
|
16
|
+
* Fix schema mutability issue.
|
9
17
|
## Changed
|
10
18
|
* Change `schema` class method to `json_schema` due to compatibility issues with other gems.
|
11
19
|
|
data/README.md
CHANGED
@@ -4,6 +4,8 @@
|
|
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
6
|
|
7
|
+
![Surrealist](surrealist-icon.png)
|
8
|
+
|
7
9
|
A gem that provides DSL for serialization of plain old Ruby objects to JSON in a declarative style
|
8
10
|
by defining a `json_schema`. It also provides a trivial type checking in the runtime before serialization.
|
9
11
|
[Yard documentation](http://www.rubydoc.info/github/nesaulov/surrealist/master)
|
@@ -13,7 +15,7 @@ by defining a `json_schema`. It also provides a trivial type checking in the run
|
|
13
15
|
A typical use case for this gem could be, for example, serializing a (decorated) object outside
|
14
16
|
of the view context. The schema is described through a hash, so you can build the structure
|
15
17
|
of serialized object independently of its methods and attributes, while also having possibility
|
16
|
-
to serialize nested objects and structures.
|
18
|
+
to serialize nested objects and structures. [Introductory blogpost.](https://medium.com/@billikota/introducing-surrealist-a-gem-to-serialize-ruby-objects-according-to-a-defined-schema-6ca7e550628d)
|
17
19
|
|
18
20
|
* [Installation](#installation)
|
19
21
|
* [Usage](#usage)
|
@@ -23,11 +25,14 @@ to serialize nested objects and structures.
|
|
23
25
|
* [Usage with Dry::Types](#usage-with-drytypes)
|
24
26
|
* [Build schema](#build-schema)
|
25
27
|
* [Camelization](#camelization)
|
28
|
+
* [Include root](#include-root)
|
26
29
|
* [Bool and Any](#bool-and-any)
|
27
30
|
* [Type errors](#type-errors)
|
28
31
|
* [Undefined methods in schema](#undefined-methods-in-schema)
|
29
32
|
* [Other notes](#other-notes)
|
33
|
+
* [Roadmap](#roadmap)
|
30
34
|
* [Contributing](#contributing)
|
35
|
+
* [Credits](#credits)
|
31
36
|
* [License](#license)
|
32
37
|
|
33
38
|
|
@@ -63,17 +68,14 @@ class Person
|
|
63
68
|
include Surrealist
|
64
69
|
|
65
70
|
json_schema do
|
66
|
-
{
|
67
|
-
foo: String,
|
68
|
-
bar: Integer,
|
69
|
-
}
|
71
|
+
{ name: String, age: Integer }
|
70
72
|
end
|
71
73
|
|
72
|
-
def
|
73
|
-
'
|
74
|
+
def name
|
75
|
+
'John Doe'
|
74
76
|
end
|
75
77
|
|
76
|
-
def
|
78
|
+
def age
|
77
79
|
42
|
78
80
|
end
|
79
81
|
end
|
@@ -83,7 +85,7 @@ end
|
|
83
85
|
|
84
86
|
``` ruby
|
85
87
|
Person.new.surrealize
|
86
|
-
# => '{ "
|
88
|
+
# => '{ "name": "John Doe", "age": 42 }'
|
87
89
|
```
|
88
90
|
|
89
91
|
### Nested structures
|
@@ -140,7 +142,7 @@ class User
|
|
140
142
|
end
|
141
143
|
|
142
144
|
User.new.surrealize
|
143
|
-
# => '{ "name": "John Doe", "credit_card": { "number"
|
145
|
+
# => '{ "name": "John Doe", "credit_card": { "number": 1234, "cvv": 322 } }'
|
144
146
|
|
145
147
|
```
|
146
148
|
|
@@ -149,7 +151,7 @@ You can use `Dry::Types` for type checking. Note that Surrealist does not ship
|
|
149
151
|
with dry-types by default, so you should do the [installation and configuration](http://dry-rb.org/gems/dry-types/)
|
150
152
|
by yourself. All built-in features of dry-types work, so if you use, say, `Types::Coercible::String`,
|
151
153
|
your data will be coerced if it is able to, otherwise you will get a TypeError.
|
152
|
-
Assuming
|
154
|
+
Assuming that you have defined module called `Types`:
|
153
155
|
|
154
156
|
``` ruby
|
155
157
|
require 'dry-types'
|
@@ -169,25 +171,25 @@ class Car
|
|
169
171
|
end
|
170
172
|
|
171
173
|
def age;
|
172
|
-
'7'
|
174
|
+
'7'
|
173
175
|
end
|
174
176
|
|
175
177
|
def previous_owner;
|
176
|
-
'John Doe'
|
178
|
+
'John Doe'
|
177
179
|
end
|
178
180
|
|
179
181
|
def horsepower;
|
180
|
-
140
|
182
|
+
140
|
181
183
|
end
|
182
184
|
|
183
185
|
def brand;
|
184
|
-
'Toyota'
|
186
|
+
'Toyota'
|
185
187
|
end
|
186
188
|
|
187
189
|
def doors; end
|
188
190
|
|
189
191
|
def fuel_system;
|
190
|
-
'Direct injection'
|
192
|
+
'Direct injection'
|
191
193
|
end
|
192
194
|
end
|
193
195
|
|
@@ -207,13 +209,54 @@ Car.new.build_schema
|
|
207
209
|
|
208
210
|
### Camelization
|
209
211
|
If you need to have keys in camelBack, you can pass optional `camelize` argument
|
210
|
-
to `#surrealize`. From the previous example:
|
212
|
+
to `#surrealize or #build_schema`. From the previous example:
|
211
213
|
|
212
214
|
``` ruby
|
213
215
|
Car.new.surrealize(camelize: true)
|
214
216
|
# => '{ "age": 7, "brand": "Toyota", "doors": null, "horsepower": 140, "fuelSystem": "Direct injection", "previousOwner": "John Doe" }'
|
215
217
|
```
|
216
218
|
|
219
|
+
### Include root
|
220
|
+
If you want to wrap the resulting JSON into a root key, you can pass optional `include_root` argument
|
221
|
+
to `#surrealize` or `#build_schema`. The root key in this case will be taken from the class name of the
|
222
|
+
surrealizable object.
|
223
|
+
``` ruby
|
224
|
+
class Cat
|
225
|
+
include Surrealist
|
226
|
+
|
227
|
+
json_schema do
|
228
|
+
{ weight: String }
|
229
|
+
end
|
230
|
+
|
231
|
+
def weight
|
232
|
+
'3 kilos'
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
Cat.new.surrealize(include_root: true)
|
237
|
+
# => '{ "cat": { "weight": "3 kilos" } }'
|
238
|
+
```
|
239
|
+
With nested classes the last namespace will be taken as root key:
|
240
|
+
``` ruby
|
241
|
+
class Animal
|
242
|
+
class Dog
|
243
|
+
include Surrealist
|
244
|
+
|
245
|
+
json_schema do
|
246
|
+
{ breed: String }
|
247
|
+
end
|
248
|
+
|
249
|
+
def breed
|
250
|
+
'Collie'
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
Animal::Dog.new.surrealize(include_root: true)
|
256
|
+
# => '{ "dog": { "breed": "Collie" } }'
|
257
|
+
```
|
258
|
+
Nesting namespaces are [yet to be implemented.](https://github.com/nesaulov/surrealist/issues/14)
|
259
|
+
|
217
260
|
### Bool and Any
|
218
261
|
If you have a parameter that is of boolean type, or if you don't care about the type, you
|
219
262
|
can use `Bool` and `Any` respectively.
|
@@ -274,14 +317,22 @@ Car.new.surrealize
|
|
274
317
|
### Other notes
|
275
318
|
* nil values are allowed by default, so if you have, say, `age: String`, but the actual value is nil,
|
276
319
|
type check will be passed. If you want to be strict about `nil`s consider using `Dry::Types`.
|
277
|
-
* Surrealist requires ruby of version 2.2 and higher.
|
320
|
+
* Surrealist requires MRI ruby of version 2.2 and higher.
|
278
321
|
|
279
|
-
##
|
322
|
+
## Roadmap
|
323
|
+
Here is a list of features that are not implemented yet (contributions are welcome):
|
324
|
+
* [ActiveRecord_Relation serialization](https://github.com/nesaulov/surrealist/issues/13)
|
325
|
+
* [Collection serialization](https://github.com/nesaulov/surrealist/issues/12)
|
326
|
+
* [Delegating serialization to parent class](https://github.com/nesaulov/surrealist/issues/11)
|
327
|
+
* [Having nested namespaces being surrealized](https://github.com/nesaulov/surrealist/issues/14)
|
280
328
|
|
329
|
+
## Contributing
|
281
330
|
Bug reports and pull requests are welcome on GitHub at https://github.com/nesaulov/surrealist.
|
282
331
|
This project is intended to be a safe, welcoming space for collaboration, and contributors are expected
|
283
332
|
to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
284
333
|
|
285
|
-
##
|
334
|
+
## Credits
|
335
|
+
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/)
|
286
336
|
|
337
|
+
## License
|
287
338
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Surrealist
|
4
|
+
# A helper class to camelize, wrap and deeply copy hashes.
|
5
|
+
class HashUtils
|
6
|
+
class << self
|
7
|
+
# Deeply copies the schema hash and wraps it if there is a need to.
|
8
|
+
#
|
9
|
+
# @param [Object] hash object to be copied.
|
10
|
+
# @param [Boolean] include_root optional argument for including root in the hash.
|
11
|
+
# @param [Boolean] camelize optional argument for camelizing the root key of the hash.
|
12
|
+
# @param [String] klass instance's class name.
|
13
|
+
#
|
14
|
+
# @return [Object] a copied object
|
15
|
+
def deep_copy(hash:, include_root: false, camelize: false, klass: false)
|
16
|
+
return copy_hash(hash) unless include_root
|
17
|
+
raise_unknown_root! if include_root && !klass
|
18
|
+
|
19
|
+
actual_class = extract_class(klass)
|
20
|
+
root_key = camelize ? camelize(actual_class, false).to_sym : underscore(actual_class).to_sym
|
21
|
+
object = Hash[root_key => {}]
|
22
|
+
copy_hash(hash, wrapper: object[root_key])
|
23
|
+
|
24
|
+
object
|
25
|
+
end
|
26
|
+
|
27
|
+
# Converts hash's keys to camelBack keys.
|
28
|
+
#
|
29
|
+
# @param [Hash] hash a hash to be camelized.
|
30
|
+
#
|
31
|
+
# @return [Hash] camelized hash.
|
32
|
+
def camelize_hash(hash)
|
33
|
+
if hash.is_a?(Hash)
|
34
|
+
Hash[hash.map { |k, v| [camelize_key(k, false), camelize_hash(v)] }]
|
35
|
+
else
|
36
|
+
hash
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# Goes through the hash recursively and deeply copies it.
|
43
|
+
#
|
44
|
+
# @param [Hash] hash the hash to be copied.
|
45
|
+
# @param [Hash] wrapper the wrapper of the resulting hash.
|
46
|
+
#
|
47
|
+
# @return [Hash] deeply copied hash.
|
48
|
+
def copy_hash(hash, wrapper: {})
|
49
|
+
hash.each_with_object(wrapper) do |(key, value), new|
|
50
|
+
new[key] = value.is_a?(Hash) ? copy_hash(value) : value
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Converts symbol to string and camelizes it.
|
55
|
+
#
|
56
|
+
# @param [String | Symbol] key a key to be camelized.
|
57
|
+
# @param [Boolean] first_upper should the first letter be capitalized.
|
58
|
+
#
|
59
|
+
# @return [String | Symbol] camelized key of a hash.
|
60
|
+
def camelize_key(key, first_upper = true)
|
61
|
+
if key.is_a? Symbol
|
62
|
+
camelize(key.to_s, first_upper).to_sym
|
63
|
+
elsif key.is_a? String
|
64
|
+
camelize(key, first_upper)
|
65
|
+
else
|
66
|
+
key
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Camelizes a string.
|
71
|
+
#
|
72
|
+
# @param [String] snake_string a string to be camelized.
|
73
|
+
# @param [Boolean] first_upper should the first letter be capitalized.
|
74
|
+
#
|
75
|
+
# @return [String] camelized string.
|
76
|
+
def camelize(snake_string, first_upper = true)
|
77
|
+
if first_upper
|
78
|
+
snake_string.to_s
|
79
|
+
.gsub(/(?:^|_)([^_\s]+)/) { Regexp.last_match[1].capitalize }
|
80
|
+
else
|
81
|
+
parts = snake_string.split('_', 2)
|
82
|
+
parts[0] << camelize(parts[1]) if parts.size > 1
|
83
|
+
parts[0] || ''
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Clones a string and converts first character to lower case.
|
88
|
+
#
|
89
|
+
# @param [String] string a string to be cloned.
|
90
|
+
#
|
91
|
+
# @return [String] new string with lower cased first character.
|
92
|
+
def uncapitalize(string)
|
93
|
+
str = string.dup
|
94
|
+
str[0] = str[0].downcase
|
95
|
+
str
|
96
|
+
end
|
97
|
+
|
98
|
+
# Converts a string to snake_case.
|
99
|
+
#
|
100
|
+
# @param [String] string a string to be underscored.
|
101
|
+
#
|
102
|
+
# @return [String] underscored string.
|
103
|
+
def underscore(string)
|
104
|
+
string.gsub('::', '_')
|
105
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
106
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
107
|
+
.tr('-', '_')
|
108
|
+
.downcase
|
109
|
+
end
|
110
|
+
|
111
|
+
# Extract last class from a namespace.
|
112
|
+
#
|
113
|
+
# @param [String] string full namespace
|
114
|
+
#
|
115
|
+
# @example Extract class
|
116
|
+
# extract_class('Animal::Dog::Collie') # => 'Collie'
|
117
|
+
#
|
118
|
+
# @return [String] extracted class
|
119
|
+
def extract_class(string)
|
120
|
+
uncapitalize(string.split('::').last)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Raises Surrealist::UnknownRootError if class's name is unknown.
|
124
|
+
#
|
125
|
+
# @raise Surrealist::UnknownRootError
|
126
|
+
def raise_unknown_root!
|
127
|
+
raise Surrealist::UnknownRootError, "Can't wrap schema in root key - class name was not passed"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -4,13 +4,13 @@ module Surrealist
|
|
4
4
|
# Instance methods that are included to the object's class
|
5
5
|
module InstanceMethods
|
6
6
|
# Invokes +Surrealist+'s class method +surrealize+
|
7
|
-
def surrealize(camelize: false)
|
8
|
-
Surrealist.surrealize(self, camelize: camelize)
|
7
|
+
def surrealize(camelize: false, include_root: false)
|
8
|
+
Surrealist.surrealize(instance: self, camelize: camelize, include_root: include_root)
|
9
9
|
end
|
10
10
|
|
11
11
|
# Invokes +Surrealist+'s class method +build_schema+
|
12
|
-
def build_schema(camelize: false)
|
13
|
-
Surrealist.build_schema(self, camelize: camelize)
|
12
|
+
def build_schema(camelize: false, include_root: false)
|
13
|
+
Surrealist.build_schema(instance: self, camelize: camelize, include_root: include_root)
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
data/lib/surrealist/version.rb
CHANGED
data/lib/surrealist.rb
CHANGED
@@ -4,7 +4,7 @@ require 'surrealist/class_methods'
|
|
4
4
|
require 'surrealist/instance_methods'
|
5
5
|
require 'surrealist/bool'
|
6
6
|
require 'surrealist/any'
|
7
|
-
require 'surrealist/
|
7
|
+
require 'surrealist/hash_utils'
|
8
8
|
require 'surrealist/type_helper'
|
9
9
|
require 'json'
|
10
10
|
|
@@ -22,102 +22,126 @@ module Surrealist
|
|
22
22
|
# Error class for failed type-checks.
|
23
23
|
class InvalidTypeError < TypeError; end
|
24
24
|
|
25
|
-
#
|
26
|
-
|
27
|
-
base.extend(Surrealist::ClassMethods)
|
28
|
-
base.include(Surrealist::InstanceMethods)
|
29
|
-
end
|
30
|
-
|
31
|
-
# Dumps the object's methods corresponding to the schema
|
32
|
-
# provided in the object's class and type-checks the values.
|
33
|
-
#
|
34
|
-
# @param [Object] instance of a class that has +Surrealist+ included.
|
35
|
-
#
|
36
|
-
# @return [String] a json-formatted string corresponding to the schema
|
37
|
-
# provided in the object's class. Values will be taken from the return values
|
38
|
-
# of appropriate methods from the object.
|
39
|
-
#
|
40
|
-
# @raise +Surrealist::UnknownSchemaError+ if no schema was provided in the object's class.
|
41
|
-
#
|
42
|
-
# @raise +Surrealist::InvalidTypeError+ if type-check failed at some point.
|
43
|
-
#
|
44
|
-
# @raise +Surrealist::UndefinedMethodError+ if a key defined in the schema
|
45
|
-
# does not have a corresponding method on the object.
|
46
|
-
#
|
47
|
-
# @example Define a schema and surrealize the object
|
48
|
-
# class User
|
49
|
-
# include Surrealist
|
50
|
-
#
|
51
|
-
# json_schema do
|
52
|
-
# {
|
53
|
-
# name: String,
|
54
|
-
# age: Integer,
|
55
|
-
# }
|
56
|
-
# end
|
57
|
-
#
|
58
|
-
# def name
|
59
|
-
# 'Nikita'
|
60
|
-
# end
|
61
|
-
#
|
62
|
-
# def age
|
63
|
-
# 23
|
64
|
-
# end
|
65
|
-
# end
|
66
|
-
#
|
67
|
-
# User.new.surrealize
|
68
|
-
# # => "{\"name\":\"Nikita\",\"age\":23}"
|
69
|
-
# # For more examples see README
|
70
|
-
def self.surrealize(instance, camelize:)
|
71
|
-
::JSON.dump(build_schema(instance, camelize: camelize))
|
72
|
-
end
|
25
|
+
# Error class for undefined root keys for schema wrapping.
|
26
|
+
class UnknownRootError < RuntimeError; end
|
73
27
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
# of appropriate methods from the object.
|
81
|
-
#
|
82
|
-
# @raise +Surrealist::UnknownSchemaError+ if no schema was provided in the object's class.
|
83
|
-
#
|
84
|
-
# @raise +Surrealist::InvalidTypeError+ if type-check failed at some point.
|
85
|
-
#
|
86
|
-
# @raise +Surrealist::UndefinedMethodError+ if a key defined in the schema
|
87
|
-
# does not have a corresponding method on the object.
|
88
|
-
#
|
89
|
-
# @example Define a schema and surrealize the object
|
90
|
-
# class User
|
91
|
-
# include Surrealist
|
92
|
-
#
|
93
|
-
# json_schema do
|
94
|
-
# {
|
95
|
-
# name: String,
|
96
|
-
# age: Integer,
|
97
|
-
# }
|
98
|
-
# end
|
99
|
-
#
|
100
|
-
# def name
|
101
|
-
# 'Nikita'
|
102
|
-
# end
|
103
|
-
#
|
104
|
-
# def age
|
105
|
-
# 23
|
106
|
-
# end
|
107
|
-
# end
|
108
|
-
#
|
109
|
-
# User.new.build_schema
|
110
|
-
# # => { name: 'Nikita', age: 23 }
|
111
|
-
# # For more examples see README
|
112
|
-
def self.build_schema(instance, camelize:)
|
113
|
-
schema = instance.class.instance_variable_get('@__surrealist_schema')
|
28
|
+
class << self
|
29
|
+
# @param [Class] base class to include/extend +Surrealist+.
|
30
|
+
def included(base)
|
31
|
+
base.extend(Surrealist::ClassMethods)
|
32
|
+
base.include(Surrealist::InstanceMethods)
|
33
|
+
end
|
114
34
|
|
115
|
-
|
116
|
-
|
35
|
+
# Dumps the object's methods corresponding to the schema
|
36
|
+
# provided in the object's class and type-checks the values.
|
37
|
+
#
|
38
|
+
# @param [Object] instance of a class that has +Surrealist+ included.
|
39
|
+
# @param [Boolean] camelize optional argument for converting hash to camelBack.
|
40
|
+
# @param [Boolean] include_root optional argument for having the root key of the resulting hash
|
41
|
+
# as instance's class name.
|
42
|
+
#
|
43
|
+
# @return [String] a json-formatted string corresponding to the schema
|
44
|
+
# provided in the object's class. Values will be taken from the return values
|
45
|
+
# of appropriate methods from the object.
|
46
|
+
#
|
47
|
+
# @raise +Surrealist::UnknownSchemaError+ if no schema was provided in the object's class.
|
48
|
+
#
|
49
|
+
# @raise +Surrealist::InvalidTypeError+ if type-check failed at some point.
|
50
|
+
#
|
51
|
+
# @raise +Surrealist::UndefinedMethodError+ if a key defined in the schema
|
52
|
+
# does not have a corresponding method on the object.
|
53
|
+
#
|
54
|
+
# @example Define a schema and surrealize the object
|
55
|
+
# class User
|
56
|
+
# include Surrealist
|
57
|
+
#
|
58
|
+
# json_schema do
|
59
|
+
# {
|
60
|
+
# name: String,
|
61
|
+
# age: Integer,
|
62
|
+
# }
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# def name
|
66
|
+
# 'Nikita'
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# def age
|
70
|
+
# 23
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# User.new.surrealize
|
75
|
+
# # => "{\"name\":\"Nikita\",\"age\":23}"
|
76
|
+
# # For more examples see README
|
77
|
+
def surrealize(instance:, camelize:, include_root:)
|
78
|
+
::JSON.dump(build_schema(instance: instance, camelize: camelize, include_root: include_root))
|
117
79
|
end
|
118
80
|
|
119
|
-
hash
|
81
|
+
# Builds hash from schema provided in the object's class and type-checks the values.
|
82
|
+
#
|
83
|
+
# @param [Object] instance of a class that has +Surrealist+ included.
|
84
|
+
# @param [Boolean] camelize optional argument for converting hash to camelBack.
|
85
|
+
# @param [Boolean] include_root optional argument for having the root key of the resulting hash
|
86
|
+
# as instance's class name.
|
87
|
+
#
|
88
|
+
# @return [Hash] a hash corresponding to the schema
|
89
|
+
# provided in the object's class. Values will be taken from the return values
|
90
|
+
# of appropriate methods from the object.
|
91
|
+
#
|
92
|
+
# @raise +Surrealist::UnknownSchemaError+ if no schema was provided in the object's class.
|
93
|
+
#
|
94
|
+
# @raise +Surrealist::InvalidTypeError+ if type-check failed at some point.
|
95
|
+
#
|
96
|
+
# @raise +Surrealist::UndefinedMethodError+ if a key defined in the schema
|
97
|
+
# does not have a corresponding method on the object.
|
98
|
+
#
|
99
|
+
# @example Define a schema and surrealize the object
|
100
|
+
# class User
|
101
|
+
# include Surrealist
|
102
|
+
#
|
103
|
+
# json_schema do
|
104
|
+
# {
|
105
|
+
# name: String,
|
106
|
+
# age: Integer,
|
107
|
+
# }
|
108
|
+
# end
|
109
|
+
#
|
110
|
+
# def name
|
111
|
+
# 'Nikita'
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
# def age
|
115
|
+
# 23
|
116
|
+
# end
|
117
|
+
# end
|
118
|
+
#
|
119
|
+
# User.new.build_schema
|
120
|
+
# # => { name: 'Nikita', age: 23 }
|
121
|
+
# # For more examples see README
|
122
|
+
def build_schema(instance:, camelize:, include_root:)
|
123
|
+
schema = instance.class.instance_variable_get('@__surrealist_schema')
|
124
|
+
|
125
|
+
raise_unknown_schema!(instance) if schema.nil?
|
120
126
|
|
121
|
-
|
127
|
+
normalized_schema = Surrealist::HashUtils.deep_copy(
|
128
|
+
hash: schema,
|
129
|
+
klass: instance.class.name,
|
130
|
+
camelize: camelize,
|
131
|
+
include_root: include_root,
|
132
|
+
)
|
133
|
+
|
134
|
+
hash = Builder.call(schema: normalized_schema, instance: instance)
|
135
|
+
camelize ? Surrealist::HashUtils.camelize_hash(hash) : hash
|
136
|
+
end
|
137
|
+
|
138
|
+
# Raises Surrealist::UnknownSchemaError
|
139
|
+
#
|
140
|
+
# @param [Object] instance instance of the class without schema defined.
|
141
|
+
#
|
142
|
+
# @raise Surrealist::UnknownSchemaError
|
143
|
+
def raise_unknown_schema!(instance)
|
144
|
+
raise Surrealist::UnknownSchemaError, "Can't serialize #{instance.class} - no schema was provided."
|
145
|
+
end
|
122
146
|
end
|
123
147
|
end
|
data/surrealist-icon.png
ADDED
Binary file
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: surrealist
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nikita Esaulov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-10-
|
11
|
+
date: 2017-10-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -102,11 +102,12 @@ files:
|
|
102
102
|
- lib/surrealist/bool.rb
|
103
103
|
- lib/surrealist/builder.rb
|
104
104
|
- lib/surrealist/class_methods.rb
|
105
|
+
- lib/surrealist/hash_utils.rb
|
105
106
|
- lib/surrealist/instance_methods.rb
|
106
107
|
- lib/surrealist/schema_definer.rb
|
107
108
|
- lib/surrealist/type_helper.rb
|
108
|
-
- lib/surrealist/utils.rb
|
109
109
|
- lib/surrealist/version.rb
|
110
|
+
- surrealist-icon.png
|
110
111
|
- surrealist.gemspec
|
111
112
|
homepage: https://github.com/nesaulov/surrealist
|
112
113
|
licenses:
|
data/lib/surrealist/utils.rb
DELETED
@@ -1,69 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Surrealist
|
4
|
-
# A helper class to camelize hash keys and deep copy objects
|
5
|
-
class Utils
|
6
|
-
class << self
|
7
|
-
# Deep copies the schema hash.
|
8
|
-
#
|
9
|
-
# @param [Object] hash object to be copied
|
10
|
-
#
|
11
|
-
# @return [Object] a copied object
|
12
|
-
def deep_copy(hash)
|
13
|
-
hash.each_with_object({}) do |(key, value), new|
|
14
|
-
new[key] = value.is_a?(Hash) ? deep_copy(value) : value
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
# Converts hash's keys to camelCase
|
19
|
-
#
|
20
|
-
# @param [Hash] hash a hash to be camelized
|
21
|
-
#
|
22
|
-
# @return [Hash] camelized hash
|
23
|
-
def camelize_hash(hash)
|
24
|
-
if hash.is_a?(Hash)
|
25
|
-
Hash[hash.map { |k, v| [camelize_key(k, false), camelize_hash(v)] }]
|
26
|
-
else
|
27
|
-
hash
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
32
|
-
|
33
|
-
# Converts symbol to string and camelizes it
|
34
|
-
#
|
35
|
-
# @param [String | Symbol] key a key to be camelized
|
36
|
-
#
|
37
|
-
# @param [Boolean] first_upper should the first letter be capitalized
|
38
|
-
#
|
39
|
-
# @return [String | Symbol] camelized key of a hash
|
40
|
-
def camelize_key(key, first_upper = true)
|
41
|
-
if key.is_a? Symbol
|
42
|
-
camelize(key.to_s, first_upper).to_sym
|
43
|
-
elsif key.is_a? String
|
44
|
-
camelize(key, first_upper)
|
45
|
-
else
|
46
|
-
key
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
# Camelizes a word
|
51
|
-
#
|
52
|
-
# @param [String] snake_word a word to be camelized
|
53
|
-
#
|
54
|
-
# @param [Boolean] first_upper should the first letter be capitalized
|
55
|
-
#
|
56
|
-
# @return [String] camelized string
|
57
|
-
def camelize(snake_word, first_upper = true)
|
58
|
-
if first_upper
|
59
|
-
snake_word.to_s
|
60
|
-
.gsub(/(?:^|_)([^_\s]+)/) { Regexp.last_match[1].capitalize }
|
61
|
-
else
|
62
|
-
parts = snake_word.split('_', 2)
|
63
|
-
parts[0] << camelize(parts[1]) if parts.size > 1
|
64
|
-
parts[0] || ''
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|