surrealist 0.1.2 → 0.1.4
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/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
|
[](http://inch-ci.org/github/nesaulov/surrealist)
|
5
5
|
[](https://rubygems.org/gems/surrealist)
|
6
6
|
|
7
|
+

|
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
|