surrealist 0.1.4 → 0.2.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/.gitignore +0 -1
- data/.rspec +1 -0
- data/.rubocop.yml +133 -0
- data/.travis.yml +3 -4
- data/CHANGELOG.md +7 -0
- data/CONTRIBUTING.md +22 -0
- data/Gemfile +12 -2
- data/README.md +99 -8
- data/Rakefile +8 -0
- data/lib/surrealist/carrier.rb +69 -0
- data/lib/surrealist/class_methods.rb +39 -3
- data/lib/surrealist/copier.rb +99 -0
- data/lib/surrealist/exception_raiser.rb +72 -0
- data/lib/surrealist/hash_utils.rb +3 -95
- data/lib/surrealist/instance_methods.rb +62 -5
- data/lib/surrealist/string_utils.rb +85 -0
- data/lib/surrealist/type_helper.rb +1 -1
- data/lib/surrealist/version.rb +1 -1
- data/lib/surrealist.rb +43 -73
- data/surrealist.gemspec +1 -0
- metadata +24 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c83fe19569dc43edfa87a5a0b8fe4fcbd2f74742
|
4
|
+
data.tar.gz: 8dd0d5fc5e2c1450468bd2d16a026d2a5d6e949e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0a47f6780ffeba32e2b748e588eafb8abf3399d9f464412c0a4e95076809e93b92e346180c2560cb4b1689824accd959d1c2794e8cafa9dad520fe70c6e83243
|
7
|
+
data.tar.gz: afb0df3267229cd475d387449b3246fbe39a5ddb9927de730b229293b82645c946e6f5e3179fe04dcd31533871050453c6dbc1fd33fb86e3b5165626f707f8f7
|
data/.gitignore
CHANGED
data/.rspec
CHANGED
data/.rubocop.yml
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.2
|
3
|
+
|
4
|
+
# Layout
|
5
|
+
|
6
|
+
Layout/AlignParameters:
|
7
|
+
Enabled: true
|
8
|
+
|
9
|
+
Layout/EmptyLineBetweenDefs:
|
10
|
+
Exclude:
|
11
|
+
- spec/**/*rb
|
12
|
+
|
13
|
+
Layout/IndentArray:
|
14
|
+
EnforcedStyle: consistent
|
15
|
+
|
16
|
+
Layout/IndentHash:
|
17
|
+
EnforcedStyle: consistent
|
18
|
+
|
19
|
+
Layout/MultilineMethodCallIndentation:
|
20
|
+
Enabled: false
|
21
|
+
|
22
|
+
Layout/MultilineOperationIndentation:
|
23
|
+
Enabled: false
|
24
|
+
|
25
|
+
Layout/SpaceInLambdaLiteral:
|
26
|
+
EnforcedStyle: require_space
|
27
|
+
|
28
|
+
Layout/SpaceInsideBrackets:
|
29
|
+
Enabled: false
|
30
|
+
|
31
|
+
# Lint
|
32
|
+
Lint/AmbiguousBlockAssociation:
|
33
|
+
Enabled: false
|
34
|
+
|
35
|
+
Lint/AmbiguousOperator:
|
36
|
+
Enabled: false
|
37
|
+
|
38
|
+
Lint/EmptyWhen:
|
39
|
+
Enabled: false
|
40
|
+
|
41
|
+
Lint/NonLocalExitFromIterator:
|
42
|
+
Enabled: false
|
43
|
+
|
44
|
+
Lint/RescueType:
|
45
|
+
Enabled: true
|
46
|
+
|
47
|
+
# Metrics
|
48
|
+
|
49
|
+
Metrics/BlockLength:
|
50
|
+
Exclude:
|
51
|
+
- spec/**/*rb
|
52
|
+
|
53
|
+
Metrics/CyclomaticComplexity:
|
54
|
+
Enabled: false
|
55
|
+
|
56
|
+
Metrics/MethodLength:
|
57
|
+
Max: 15
|
58
|
+
|
59
|
+
Metrics/LineLength:
|
60
|
+
Max: 106
|
61
|
+
|
62
|
+
Metrics/PerceivedComplexity:
|
63
|
+
Enabled: false
|
64
|
+
|
65
|
+
Metrics/ParameterLists:
|
66
|
+
Enabled: false
|
67
|
+
|
68
|
+
# Naming
|
69
|
+
|
70
|
+
Naming/AccessorMethodName:
|
71
|
+
Enabled: false
|
72
|
+
|
73
|
+
Naming/PredicateName:
|
74
|
+
NamePrefixBlacklist:
|
75
|
+
- is_
|
76
|
+
|
77
|
+
# Performance
|
78
|
+
|
79
|
+
Performance/Caller:
|
80
|
+
Enabled: true
|
81
|
+
|
82
|
+
Performance/Casecmp:
|
83
|
+
Enabled: true
|
84
|
+
|
85
|
+
Performance/UnfreezeString:
|
86
|
+
Enabled: true
|
87
|
+
|
88
|
+
# Style
|
89
|
+
|
90
|
+
Style/SingleLineMethods:
|
91
|
+
Exclude:
|
92
|
+
- spec/**/*rb
|
93
|
+
|
94
|
+
Style/ClassAndModuleChildren:
|
95
|
+
Enabled: false
|
96
|
+
|
97
|
+
Style/Dir:
|
98
|
+
Enabled: true
|
99
|
+
|
100
|
+
Style/StringLiterals:
|
101
|
+
EnforcedStyle: single_quotes
|
102
|
+
ConsistentQuotesInMultiline: true
|
103
|
+
|
104
|
+
Style/FormatStringToken:
|
105
|
+
Enabled: true
|
106
|
+
|
107
|
+
Style/IfUnlessModifier:
|
108
|
+
Enabled: true
|
109
|
+
|
110
|
+
Style/GuardClause:
|
111
|
+
Enabled: false
|
112
|
+
|
113
|
+
Style/MultipleComparison:
|
114
|
+
Enabled: true
|
115
|
+
|
116
|
+
Style/PerlBackrefs:
|
117
|
+
Enabled: true
|
118
|
+
|
119
|
+
Style/RedundantConditional:
|
120
|
+
Enabled: true
|
121
|
+
|
122
|
+
Style/RegexpLiteral:
|
123
|
+
EnforcedStyle: mixed
|
124
|
+
Enabled: true
|
125
|
+
|
126
|
+
Style/SignalException:
|
127
|
+
EnforcedStyle: only_raise
|
128
|
+
|
129
|
+
Style/TrailingCommaInArguments:
|
130
|
+
EnforcedStyleForMultiline: comma
|
131
|
+
|
132
|
+
Style/TrailingCommaInLiteral:
|
133
|
+
EnforcedStyleForMultiline: comma
|
data/.travis.yml
CHANGED
@@ -2,13 +2,12 @@ language: ruby
|
|
2
2
|
sudo: false
|
3
3
|
cache: bundler
|
4
4
|
before_install: gem install bundler
|
5
|
-
script: bundle exec
|
5
|
+
script: bundle exec rake
|
6
6
|
rvm:
|
7
7
|
- 2.2.0
|
8
8
|
- 2.2.5
|
9
9
|
- 2.3.1
|
10
|
+
- 2.3.5
|
10
11
|
- 2.4.0
|
12
|
+
- 2.4.2
|
11
13
|
- ruby-head
|
12
|
-
matrix:
|
13
|
-
allow_failures:
|
14
|
-
- rvm: ruby-head
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
# 0.2.0
|
2
|
+
## Added
|
3
|
+
* `delegate_surrealization_to` class method
|
4
|
+
* `include_namespaces` optional argument
|
5
|
+
* `namespaces_nesting_level` optional argument
|
6
|
+
* `Surrealist.surrealize_collection` method for collection serialization
|
7
|
+
|
1
8
|
# 0.1.4
|
2
9
|
## Added
|
3
10
|
* Optional `include_root` argument to wrap schema in a root key. [#15](https://github.com/nesaulov/surrealist/pull/15)
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# Issue Guidelines
|
2
|
+
|
3
|
+
## Reporting bugs
|
4
|
+
If you found a bug, report an issue and describe what's the expected behavior versus what actually happens.
|
5
|
+
If the bug causes a crash, attach a full backtrace. If possible, a reproduction script showing the problem
|
6
|
+
is highly appreciated.
|
7
|
+
|
8
|
+
## Reporting feature requests
|
9
|
+
Please provide a concise description of the feature and the reasons behind the request.
|
10
|
+
|
11
|
+
# Pull Request Guidelines
|
12
|
+
A Pull Request will only be accepted if it addresses a specific issue that was reported previously, or fixes typos, mistakes in documentation etc.
|
13
|
+
|
14
|
+
Other requirements:
|
15
|
+
|
16
|
+
1) Do not open a pull request if you can't provide tests along with it. If you have problems writing tests, ask for help in the related issue.
|
17
|
+
2) Follow the style conventions of the surrounding code. In most cases, this is standard ruby style.
|
18
|
+
3) Add API documentation if it's a new feature
|
19
|
+
4) Update API documentation if it changes an existing feature
|
20
|
+
|
21
|
+
# Asking for help
|
22
|
+
If these guidelines aren't helpful, and you're stuck, don't hesitate to open an issue and ask anything.
|
data/Gemfile
CHANGED
@@ -3,5 +3,15 @@
|
|
3
3
|
source 'https://rubygems.org'
|
4
4
|
gemspec
|
5
5
|
|
6
|
-
|
7
|
-
gem '
|
6
|
+
group :development, :test do
|
7
|
+
gem 'activerecord'
|
8
|
+
gem 'coveralls', require: false
|
9
|
+
gem 'data_mapper'
|
10
|
+
gem 'dm-sqlite-adapter'
|
11
|
+
gem 'pry'
|
12
|
+
gem 'rom'
|
13
|
+
gem 'rom-sql'
|
14
|
+
gem 'sequel'
|
15
|
+
gem 'sqlite3'
|
16
|
+
gem 'yard', require: false unless ENV['TRAVIS']
|
17
|
+
end
|
data/README.md
CHANGED
@@ -22,10 +22,13 @@ to serialize nested objects and structures. [Introductory blogpost.](https://med
|
|
22
22
|
* [Simple example](#simple-example)
|
23
23
|
* [Nested structures](#nested-structures)
|
24
24
|
* [Nested objects](#nested-objects)
|
25
|
+
* [Delegating Surrealization](#delegating-surrealization)
|
25
26
|
* [Usage with Dry::Types](#usage-with-drytypes)
|
26
27
|
* [Build schema](#build-schema)
|
27
28
|
* [Camelization](#camelization)
|
28
29
|
* [Include root](#include-root)
|
30
|
+
* [Include namespaces](#include-namespaces)
|
31
|
+
* [Collection Surrealization](#collection-surrealization)
|
29
32
|
* [Bool and Any](#bool-and-any)
|
30
33
|
* [Type errors](#type-errors)
|
31
34
|
* [Undefined methods in schema](#undefined-methods-in-schema)
|
@@ -146,6 +149,52 @@ User.new.surrealize
|
|
146
149
|
|
147
150
|
```
|
148
151
|
|
152
|
+
### Delegating surrealization
|
153
|
+
You can share the `json_schema` between classes:
|
154
|
+
``` ruby
|
155
|
+
class Host
|
156
|
+
include Surrealist
|
157
|
+
|
158
|
+
json_schema do
|
159
|
+
{ name: String }
|
160
|
+
end
|
161
|
+
|
162
|
+
def name
|
163
|
+
'Host'
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
class Guest
|
168
|
+
delegate_surrealization_to Host
|
169
|
+
|
170
|
+
def name
|
171
|
+
'Guest'
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
Host.new.surrealize
|
176
|
+
# => '{ "name": "Host" }'
|
177
|
+
Guest.new.surrealize
|
178
|
+
# => '{ "name": "Guest" }'
|
179
|
+
```
|
180
|
+
Schema delegation works without inheritance as well, so if you wish you can
|
181
|
+
delegate surrealization not only to parent classes, but to any class. Please note that
|
182
|
+
in this case you have to `include Surrealist` in class that delegates schema as well.
|
183
|
+
``` ruby
|
184
|
+
class Potato
|
185
|
+
include Surrealist
|
186
|
+
delegate_surrealization_to Host
|
187
|
+
|
188
|
+
def name
|
189
|
+
'Potato'
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
Potato.new.surrealize
|
194
|
+
# => '{ "name": "Potato" }'
|
195
|
+
```
|
196
|
+
|
197
|
+
|
149
198
|
### Usage with Dry::Types
|
150
199
|
You can use `Dry::Types` for type checking. Note that Surrealist does not ship
|
151
200
|
with dry-types by default, so you should do the [installation and configuration](http://dry-rb.org/gems/dry-types/)
|
@@ -255,7 +304,55 @@ end
|
|
255
304
|
Animal::Dog.new.surrealize(include_root: true)
|
256
305
|
# => '{ "dog": { "breed": "Collie" } }'
|
257
306
|
```
|
258
|
-
|
307
|
+
|
308
|
+
### Include namespaces
|
309
|
+
You can build wrap schema into a nested hash from namespaces of the object's class.
|
310
|
+
``` ruby
|
311
|
+
class BusinessSystem::Cashout::ReportSystem::Withdraws
|
312
|
+
include Surrealist
|
313
|
+
|
314
|
+
json_schema do
|
315
|
+
{ withdraws_amount: Integer }
|
316
|
+
end
|
317
|
+
|
318
|
+
def withdraws_amount
|
319
|
+
34
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
withdraws = BusinessSystem::Cashout::ReportSystem::Withdraws.new
|
324
|
+
|
325
|
+
withdraws.surrealize(include_namespaces: true)
|
326
|
+
# => '{ "business_system": { "cashout": { "report_system": { "withdraws": { "withdraws_amount": 34 } } } } }'
|
327
|
+
```
|
328
|
+
By default all namespaces will be taken. If you want you can explicitly specify the level of nesting:
|
329
|
+
``` ruby
|
330
|
+
withdraws.surrealize(include_namespaces: true, namespaces_nesting_level: 2)
|
331
|
+
# => '{ "report_system": { "withdraws": { "withdraws_amount": 34 } } }'
|
332
|
+
```
|
333
|
+
|
334
|
+
### Collection Surrealization
|
335
|
+
Since 0.2.0 Surrealist has API for collection serialization. Example for ActiveRecord:
|
336
|
+
``` ruby
|
337
|
+
class User < ActiveRecord::Base
|
338
|
+
include Surrealist
|
339
|
+
|
340
|
+
json_schema do
|
341
|
+
{ name: String, age: Integer }
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
users = User.all
|
346
|
+
# => [#<User:0x007fa1485de878 id: 1, name: "Nikita", age: 23>, #<User:0x007fa1485de5f8 id: 2, name: "Alessandro", age: 24>]
|
347
|
+
|
348
|
+
Surrealist.surrealize_collection(users)
|
349
|
+
# => '[{ "name": "Nikita", "age": 23 }, { "name": "Alessandro", "age": 24 }]'
|
350
|
+
```
|
351
|
+
You can find motivation behind introducing new API versus monkey-patching [here](https://alessandrominali.github.io/monkey_patching_real_example).
|
352
|
+
`#surrealize_collection` works for all data structures that respond to `#each`. All ActiveRecord
|
353
|
+
features (like associations, inheritance etc) are supported and covered. Other ORMs should work without
|
354
|
+
issues as well, tests are in progress. All optional arguments (`camelize`, `include_root` etc) are also supported.
|
355
|
+
Guides on where to use `#surrealize_collection` vs `#surrealize` for all ORMs are coming.
|
259
356
|
|
260
357
|
### Bool and Any
|
261
358
|
If you have a parameter that is of boolean type, or if you don't care about the type, you
|
@@ -274,9 +371,7 @@ class User
|
|
274
371
|
end
|
275
372
|
```
|
276
373
|
|
277
|
-
|
278
374
|
### Type Errors
|
279
|
-
|
280
375
|
`Surrealist::InvalidTypeError` is thrown if types (and dry-types) mismatch.
|
281
376
|
|
282
377
|
``` ruby
|
@@ -297,7 +392,6 @@ CreditCard.new.surrealize
|
|
297
392
|
```
|
298
393
|
|
299
394
|
### Undefined methods in schema
|
300
|
-
|
301
395
|
`Surrealist::UndefinedMethodError` is thrown if a key defined in the schema does not have
|
302
396
|
a corresponding method defined in the object.
|
303
397
|
|
@@ -317,14 +411,11 @@ Car.new.surrealize
|
|
317
411
|
### Other notes
|
318
412
|
* nil values are allowed by default, so if you have, say, `age: String`, but the actual value is nil,
|
319
413
|
type check will be passed. If you want to be strict about `nil`s consider using `Dry::Types`.
|
320
|
-
* Surrealist
|
414
|
+
* Surrealist [officially supports](https://travis-ci.org/nesaulov/surrealist) MRI Ruby 2.2+ but should be working on other platforms as well.
|
321
415
|
|
322
416
|
## Roadmap
|
323
417
|
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
418
|
* [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)
|
328
419
|
|
329
420
|
## Contributing
|
330
421
|
Bug reports and pull requests are welcome on GitHub at https://github.com/nesaulov/surrealist.
|
data/Rakefile
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Surrealist
|
4
|
+
# A data structure to carry arguments across methods.
|
5
|
+
class Carrier
|
6
|
+
# Public wrapper for Carrier.
|
7
|
+
#
|
8
|
+
# @param [Boolean] camelize optional argument for converting hash to camelBack.
|
9
|
+
# @param [Boolean] include_root optional argument for having the root key of the resulting hash
|
10
|
+
# as instance's class name.
|
11
|
+
# @param [Boolean] include_namespaces optional argument for having root key as a nested hash of
|
12
|
+
# instance's namespaces. Animal::Cat.new.surrealize -> (animal: { cat: { weight: '3 kilos' } })
|
13
|
+
# @param [Integer] namespaces_nesting_level level of namespaces nesting.
|
14
|
+
#
|
15
|
+
# @raise ArgumentError if types of arguments are wrong.
|
16
|
+
#
|
17
|
+
# @return [Carrier] self if type checks were passed.
|
18
|
+
def self.call(camelize:, include_root:, include_namespaces:, namespaces_nesting_level:)
|
19
|
+
new(camelize, include_root, include_namespaces, namespaces_nesting_level).sanitize!
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :camelize, :include_root, :include_namespaces, :namespaces_nesting_level
|
23
|
+
|
24
|
+
def initialize(camelize, include_root, include_namespaces, namespaces_nesting_level)
|
25
|
+
@camelize = camelize
|
26
|
+
@include_root = include_root
|
27
|
+
@include_namespaces = include_namespaces
|
28
|
+
@namespaces_nesting_level = namespaces_nesting_level
|
29
|
+
end
|
30
|
+
|
31
|
+
# Performs type checks
|
32
|
+
#
|
33
|
+
# @return [Carrier] self if check were passed
|
34
|
+
def sanitize!
|
35
|
+
check_booleans!
|
36
|
+
check_namespaces_nesting!
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# Checks all boolean arguments
|
43
|
+
# @raise ArgumentError
|
44
|
+
def check_booleans!
|
45
|
+
booleans_hash.each do |key, value|
|
46
|
+
unless [true, false].include?(value)
|
47
|
+
raise ArgumentError, "Expected `#{key}` to be either true or false, got #{value}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Helper hash for all boolean arguments
|
53
|
+
def booleans_hash
|
54
|
+
{ camelize: camelize, include_root: include_root, include_namespaces: include_namespaces }
|
55
|
+
end
|
56
|
+
|
57
|
+
# Checks if +namespaces_nesting_level+ is a positive integer
|
58
|
+
# @raise ArgumentError
|
59
|
+
def check_namespaces_nesting!
|
60
|
+
unless namespaces_nesting_level.is_a?(Integer)
|
61
|
+
Surrealist::ExceptionRaiser.raise_invalid_nesting!(namespaces_nesting_level)
|
62
|
+
end
|
63
|
+
|
64
|
+
if namespaces_nesting_level <= 0
|
65
|
+
Surrealist::ExceptionRaiser.raise_invalid_nesting!(namespaces_nesting_level)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -1,8 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'builder'
|
4
|
-
require_relative 'schema_definer'
|
5
|
-
|
6
3
|
module Surrealist
|
7
4
|
# Class methods that are extended by the object.
|
8
5
|
module ClassMethods
|
@@ -57,5 +54,44 @@ module Surrealist
|
|
57
54
|
def json_schema(&_block)
|
58
55
|
SchemaDefiner.call(self, yield)
|
59
56
|
end
|
57
|
+
|
58
|
+
# A DSL method to delegate schema in a declarative style. Must reference a valid
|
59
|
+
# class that includes Surrealist
|
60
|
+
#
|
61
|
+
# @param [Class] klass
|
62
|
+
#
|
63
|
+
# @example DSL usage example
|
64
|
+
# class Host
|
65
|
+
# include Surrealist
|
66
|
+
#
|
67
|
+
# json_schema do
|
68
|
+
# { name: String }
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# def name
|
72
|
+
# 'Parent'
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# class Guest < Host
|
77
|
+
# delegate_surrealization_to Host
|
78
|
+
#
|
79
|
+
# def name
|
80
|
+
# 'Child'
|
81
|
+
# end
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# Guest.new.surrealize
|
85
|
+
# # => "{\"name\":\"Child\"}"
|
86
|
+
# # For more examples see README
|
87
|
+
def delegate_surrealization_to(klass)
|
88
|
+
raise TypeError, "Expected type of Class got #{klass.class} instead" unless klass.is_a?(Class)
|
89
|
+
|
90
|
+
unless klass.included_modules.include?(Surrealist)
|
91
|
+
Surrealist::ExceptionRaiser.raise_invalid_schema_delegation!
|
92
|
+
end
|
93
|
+
|
94
|
+
instance_variable_set('@__surrealist_schema_parent', klass)
|
95
|
+
end
|
60
96
|
end
|
61
97
|
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Surrealist
|
4
|
+
# A helper class for deep copying and wrapping hashes.
|
5
|
+
class Copier
|
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 [String] klass instance's class name.
|
11
|
+
# @param [Object] carrier instance of Carrier class that carries arguments passed to +surrealize+
|
12
|
+
#
|
13
|
+
# @return [Hash] a copied hash.
|
14
|
+
def deep_copy(hash:, klass: false, carrier:)
|
15
|
+
namespaces_condition = carrier.include_namespaces || carrier.namespaces_nesting_level != DEFAULT_NESTING_LEVEL # rubocop:disable Metrics/LineLength
|
16
|
+
|
17
|
+
return copy_hash(hash) unless carrier.include_root || namespaces_condition
|
18
|
+
|
19
|
+
Surrealist::ExceptionRaiser.raise_unknown_root! unless klass
|
20
|
+
|
21
|
+
if namespaces_condition
|
22
|
+
wrap_schema_into_namespace(schema: hash, klass: klass, carrier: carrier)
|
23
|
+
elsif carrier.include_root
|
24
|
+
wrap_schema_into_root(schema: hash, klass: klass, carrier: carrier)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# Goes through the hash recursively and deeply copies it.
|
31
|
+
#
|
32
|
+
# @param [Hash] hash the hash to be copied.
|
33
|
+
# @param [Hash] wrapper the wrapper of the resulting hash.
|
34
|
+
#
|
35
|
+
# @return [Hash] deeply copied hash.
|
36
|
+
def copy_hash(hash, wrapper: {})
|
37
|
+
hash.each_with_object(wrapper) do |(key, value), new|
|
38
|
+
new[key] = value.is_a?(Hash) ? copy_hash(value) : value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Wraps schema into a root key if `include_root` is passed to Surrealist.
|
43
|
+
#
|
44
|
+
# @param [Hash] schema schema hash.
|
45
|
+
# @param [String] klass name of the class where schema is defined.
|
46
|
+
# @param [Object] carrier instance of Carrier class that carries arguments passed to +surrealize+
|
47
|
+
#
|
48
|
+
# @return [Hash] a hash with schema wrapped inside a root key.
|
49
|
+
def wrap_schema_into_root(schema:, klass:, carrier:)
|
50
|
+
actual_class = Surrealist::StringUtils.extract_class(klass)
|
51
|
+
root_key = if carrier.camelize
|
52
|
+
Surrealist::StringUtils.camelize(actual_class, false).to_sym
|
53
|
+
else
|
54
|
+
Surrealist::StringUtils.underscore(actual_class).to_sym
|
55
|
+
end
|
56
|
+
result = Hash[root_key => {}]
|
57
|
+
copy_hash(schema, wrapper: result[root_key])
|
58
|
+
|
59
|
+
result
|
60
|
+
end
|
61
|
+
|
62
|
+
# Wraps schema into a nested hash of namespaces.
|
63
|
+
#
|
64
|
+
# @param [Hash] schema main schema.
|
65
|
+
# @param [String] klass name of the class where schema is defined.
|
66
|
+
# @param [Object] carrier instance of Carrier class that carries arguments passed to +surrealize+
|
67
|
+
#
|
68
|
+
# @return [Hash] nested hash (see +inject_schema+)
|
69
|
+
def wrap_schema_into_namespace(schema:, klass:, carrier:)
|
70
|
+
nested_hash = Surrealist::StringUtils.break_namespaces(
|
71
|
+
klass,
|
72
|
+
camelize: carrier.camelize,
|
73
|
+
nesting_level: carrier.namespaces_nesting_level,
|
74
|
+
)
|
75
|
+
|
76
|
+
inject_schema(nested_hash, copy_hash(schema))
|
77
|
+
end
|
78
|
+
|
79
|
+
# Injects one hash into another nested hash.
|
80
|
+
#
|
81
|
+
# @param [Hash] hash wrapper-hash.
|
82
|
+
# @param [Hash] sub_hash hash to be injected.
|
83
|
+
#
|
84
|
+
# @example wrapping hash
|
85
|
+
# hash = { one: { two: { three: {} } } }
|
86
|
+
# sub_hash = { four: '4' }
|
87
|
+
#
|
88
|
+
# inject_schema(hash, sub_hash)
|
89
|
+
# # => { one: { two: { three: { four: '4' } } } }
|
90
|
+
#
|
91
|
+
# @return [Hash] resulting hash.
|
92
|
+
def inject_schema(hash, sub_hash)
|
93
|
+
hash.each do |k, v|
|
94
|
+
v == {} ? hash[k] = sub_hash : inject_schema(v, sub_hash)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Surrealist
|
4
|
+
# Error class for classes without defined +schema+.
|
5
|
+
class UnknownSchemaError < RuntimeError; end
|
6
|
+
|
7
|
+
# Error class for classes with +json_schema+ defined not as a hash.
|
8
|
+
class InvalidSchemaError < RuntimeError; end
|
9
|
+
|
10
|
+
# Error class for +NoMethodError+.
|
11
|
+
class UndefinedMethodError < RuntimeError; end
|
12
|
+
|
13
|
+
# Error class for failed type-checks.
|
14
|
+
class InvalidTypeError < TypeError; end
|
15
|
+
|
16
|
+
# Error class for undefined root keys for schema wrapping.
|
17
|
+
class UnknownRootError < RuntimeError; end
|
18
|
+
|
19
|
+
# Error class for undefined class to delegate schema.
|
20
|
+
class InvalidSchemaDelegation < RuntimeError; end
|
21
|
+
|
22
|
+
# Error class for invalid object given to iteratively apply surrealize.
|
23
|
+
class InvalidCollectionError < ArgumentError; end
|
24
|
+
|
25
|
+
# Error class for cases where +namespaces_nesting_level+ is set to 0.
|
26
|
+
class InvalidNestingLevel < RuntimeError; end
|
27
|
+
|
28
|
+
# A class that raises all Surrealist exceptions
|
29
|
+
class ExceptionRaiser
|
30
|
+
class << self
|
31
|
+
# Raises Surrealist::InvalidSchemaDelegation if destination of delegation does not
|
32
|
+
# include Surrealist.
|
33
|
+
#
|
34
|
+
# @raise Surrealist::InvalidSchemaDelegation
|
35
|
+
def raise_invalid_schema_delegation!
|
36
|
+
raise Surrealist::InvalidSchemaDelegation, 'Class does not include Surrealist'
|
37
|
+
end
|
38
|
+
|
39
|
+
# Raises Surrealist::UnknownSchemaError
|
40
|
+
#
|
41
|
+
# @param [Object] instance instance of the class without schema defined.
|
42
|
+
#
|
43
|
+
# @raise Surrealist::UnknownSchemaError
|
44
|
+
def raise_unknown_schema!(instance)
|
45
|
+
raise Surrealist::UnknownSchemaError,
|
46
|
+
"Can't serialize #{instance.class} - no schema was provided."
|
47
|
+
end
|
48
|
+
|
49
|
+
# Raises Surrealist::UnknownRootError if class's name is unknown.
|
50
|
+
#
|
51
|
+
# @raise Surrealist::UnknownRootError
|
52
|
+
def raise_unknown_root!
|
53
|
+
raise Surrealist::UnknownRootError, "Can't wrap schema in root key - class name was not passed"
|
54
|
+
end
|
55
|
+
|
56
|
+
# Raises Surrealist::InvalidCollectionError
|
57
|
+
#
|
58
|
+
# @raise Surrealist::InvalidCollectionError
|
59
|
+
def raise_invalid_collection!
|
60
|
+
raise Surrealist::InvalidCollectionError, "Can't serialize collection - must respond to :each"
|
61
|
+
end
|
62
|
+
|
63
|
+
# Raises ArgumentError if namespaces_nesting_level is not an integer.
|
64
|
+
#
|
65
|
+
# @raise ArgumentError
|
66
|
+
def raise_invalid_nesting!(value)
|
67
|
+
raise ArgumentError,
|
68
|
+
"Expected `namespaces_nesting_level` to be a positive integer, got: #{value}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -1,29 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Surrealist
|
4
|
-
# A helper class
|
4
|
+
# A helper class for hashes transformations.
|
5
5
|
class HashUtils
|
6
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
7
|
# Converts hash's keys to camelBack keys.
|
28
8
|
#
|
29
9
|
# @param [Hash] hash a hash to be camelized.
|
@@ -39,18 +19,6 @@ module Surrealist
|
|
39
19
|
|
40
20
|
private
|
41
21
|
|
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
22
|
# Converts symbol to string and camelizes it.
|
55
23
|
#
|
56
24
|
# @param [String | Symbol] key a key to be camelized.
|
@@ -59,73 +27,13 @@ module Surrealist
|
|
59
27
|
# @return [String | Symbol] camelized key of a hash.
|
60
28
|
def camelize_key(key, first_upper = true)
|
61
29
|
if key.is_a? Symbol
|
62
|
-
camelize(key.to_s, first_upper).to_sym
|
30
|
+
Surrealist::StringUtils.camelize(key.to_s, first_upper).to_sym
|
63
31
|
elsif key.is_a? String
|
64
|
-
camelize(key, first_upper)
|
32
|
+
Surrealist::StringUtils.camelize(key, first_upper)
|
65
33
|
else
|
66
34
|
key
|
67
35
|
end
|
68
36
|
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
37
|
end
|
130
38
|
end
|
131
39
|
end
|
@@ -3,14 +3,71 @@
|
|
3
3
|
module Surrealist
|
4
4
|
# Instance methods that are included to the object's class
|
5
5
|
module InstanceMethods
|
6
|
-
#
|
7
|
-
|
8
|
-
|
6
|
+
# Dumps the object's methods corresponding to the schema
|
7
|
+
# provided in the object's class and type-checks the values.
|
8
|
+
#
|
9
|
+
# @param [Boolean] camelize optional argument for converting hash to camelBack.
|
10
|
+
# @param [Boolean] include_root optional argument for having the root key of the resulting hash
|
11
|
+
# as instance's class name.
|
12
|
+
# @param [Boolean] include_namespaces optional argument for having root key as a nested hash of
|
13
|
+
# instance's namespaces. Animal::Cat.new.surrealize -> (animal: { cat: { weight: '3 kilos' } })
|
14
|
+
# @param [Integer] namespaces_nesting_level level of namespaces nesting.
|
15
|
+
#
|
16
|
+
# @return [String] a json-formatted string corresponding to the schema
|
17
|
+
# provided in the object's class. Values will be taken from the return values
|
18
|
+
# of appropriate methods from the object.
|
19
|
+
#
|
20
|
+
# @raise +Surrealist::UnknownSchemaError+ if no schema was provided in the object's class.
|
21
|
+
#
|
22
|
+
# @raise +Surrealist::InvalidTypeError+ if type-check failed at some point.
|
23
|
+
#
|
24
|
+
# @raise +Surrealist::UndefinedMethodError+ if a key defined in the schema
|
25
|
+
# does not have a corresponding method on the object.
|
26
|
+
#
|
27
|
+
# @example Define a schema and surrealize the object
|
28
|
+
# class User
|
29
|
+
# include Surrealist
|
30
|
+
#
|
31
|
+
# json_schema do
|
32
|
+
# {
|
33
|
+
# name: String,
|
34
|
+
# age: Integer,
|
35
|
+
# }
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# def name
|
39
|
+
# 'Nikita'
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# def age
|
43
|
+
# 23
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# User.new.surrealize
|
48
|
+
# # => "{\"name\":\"Nikita\",\"age\":23}"
|
49
|
+
# # For more examples see README
|
50
|
+
def surrealize(camelize: false, include_root: false, include_namespaces: false, namespaces_nesting_level: DEFAULT_NESTING_LEVEL) # rubocop:disable Metrics/LineLength
|
51
|
+
JSON.dump(
|
52
|
+
build_schema(
|
53
|
+
camelize: camelize,
|
54
|
+
include_root: include_root,
|
55
|
+
include_namespaces: include_namespaces,
|
56
|
+
namespaces_nesting_level: namespaces_nesting_level,
|
57
|
+
),
|
58
|
+
)
|
9
59
|
end
|
10
60
|
|
11
61
|
# Invokes +Surrealist+'s class method +build_schema+
|
12
|
-
def build_schema(camelize: false, include_root: false)
|
13
|
-
Surrealist.
|
62
|
+
def build_schema(camelize: false, include_root: false, include_namespaces: false, namespaces_nesting_level: DEFAULT_NESTING_LEVEL) # rubocop:disable Metrics/LineLength
|
63
|
+
carrier = Surrealist::Carrier.call(
|
64
|
+
camelize: camelize,
|
65
|
+
include_namespaces: include_namespaces,
|
66
|
+
include_root: include_root,
|
67
|
+
namespaces_nesting_level: namespaces_nesting_level,
|
68
|
+
)
|
69
|
+
|
70
|
+
Surrealist.build_schema(instance: self, carrier: carrier)
|
14
71
|
end
|
15
72
|
end
|
16
73
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Surrealist
|
4
|
+
# A helper class for strings transformations.
|
5
|
+
class StringUtils
|
6
|
+
class << self
|
7
|
+
# Converts a string to snake_case.
|
8
|
+
#
|
9
|
+
# @param [String] string a string to be underscored.
|
10
|
+
#
|
11
|
+
# @return [String] underscored string.
|
12
|
+
def underscore(string)
|
13
|
+
string.gsub('::', '_')
|
14
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
15
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
16
|
+
.tr('-', '_')
|
17
|
+
.downcase
|
18
|
+
end
|
19
|
+
|
20
|
+
# Camelizes a string.
|
21
|
+
#
|
22
|
+
# @param [String] snake_string a string to be camelized.
|
23
|
+
# @param [Boolean] first_upper should the first letter be capitalized.
|
24
|
+
#
|
25
|
+
# @return [String] camelized string.
|
26
|
+
def camelize(snake_string, first_upper = true)
|
27
|
+
if first_upper
|
28
|
+
snake_string.to_s
|
29
|
+
.gsub(/(?:^|_)([^_\s]+)/) { Regexp.last_match[1].capitalize }
|
30
|
+
else
|
31
|
+
parts = snake_string.split('_', 2)
|
32
|
+
parts[0] << camelize(parts[1]) if parts.size > 1
|
33
|
+
parts[0] || ''
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Extracts bottom-level class from a namespace.
|
38
|
+
#
|
39
|
+
# @param [String] string full namespace
|
40
|
+
#
|
41
|
+
# @example Extract class
|
42
|
+
# extract_class('Animal::Dog::Collie') # => 'Collie'
|
43
|
+
#
|
44
|
+
# @return [String] extracted class
|
45
|
+
def extract_class(string)
|
46
|
+
uncapitalize(string.split('::').last)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Extracts n amount of classes from a namespaces and returns a nested hash.
|
50
|
+
#
|
51
|
+
# @param [String] klass full namespace as a string.
|
52
|
+
# @param [Boolean] camelize optional camelize argument.
|
53
|
+
# @param [Integer] nesting_level level of required nesting.
|
54
|
+
#
|
55
|
+
# @example 3 levels
|
56
|
+
# klass = 'Business::System::Cashier::Reports::Withdraws'
|
57
|
+
# break_namespaces(klass, camelize: false, nesting_level: 3)
|
58
|
+
# # => { cashier: { reports: { withdraws: {} } } }
|
59
|
+
#
|
60
|
+
# @raise Surrealist::InvalidNestingLevel if nesting level is specified as 0.
|
61
|
+
#
|
62
|
+
# @return [Hash] a nested hash.
|
63
|
+
def break_namespaces(klass, camelize:, nesting_level:)
|
64
|
+
Surrealist::ExceptionRaiser.raise_invalid_nesting!(nesting_level) unless nesting_level > 0
|
65
|
+
arr = klass.split('::')
|
66
|
+
arr.last(nesting_level).reverse.inject({}) do |a, n|
|
67
|
+
camelize ? Hash[camelize(uncapitalize(n), false).to_sym => a] : Hash[underscore(n).to_sym => a]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
# Clones a string and converts first character to lower case.
|
74
|
+
#
|
75
|
+
# @param [String] string a string to be cloned.
|
76
|
+
#
|
77
|
+
# @return [String] new string with lower cased first character.
|
78
|
+
def uncapitalize(string)
|
79
|
+
str = string.dup
|
80
|
+
str[0] = str[0].downcase
|
81
|
+
str
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -4,7 +4,7 @@ module Surrealist
|
|
4
4
|
# Service class for type checking
|
5
5
|
class TypeHelper
|
6
6
|
# Dry-types class matcher
|
7
|
-
DRY_TYPE_CLASS = 'Dry::Types'
|
7
|
+
DRY_TYPE_CLASS = 'Dry::Types'.freeze
|
8
8
|
|
9
9
|
class << self
|
10
10
|
# Checks if value returned from a method is an instance of type class specified
|
data/lib/surrealist/version.rb
CHANGED
data/lib/surrealist.rb
CHANGED
@@ -1,29 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'surrealist/class_methods'
|
4
|
-
require 'surrealist/instance_methods'
|
5
|
-
require 'surrealist/bool'
|
6
3
|
require 'surrealist/any'
|
4
|
+
require 'surrealist/bool'
|
5
|
+
require 'surrealist/builder'
|
6
|
+
require 'surrealist/carrier'
|
7
|
+
require 'surrealist/class_methods'
|
8
|
+
require 'surrealist/copier'
|
9
|
+
require 'surrealist/exception_raiser'
|
7
10
|
require 'surrealist/hash_utils'
|
11
|
+
require 'surrealist/instance_methods'
|
12
|
+
require 'surrealist/schema_definer'
|
13
|
+
require 'surrealist/string_utils'
|
8
14
|
require 'surrealist/type_helper'
|
9
15
|
require 'json'
|
10
16
|
|
11
17
|
# Main module that provides the +json_schema+ class method and +surrealize+ instance method.
|
12
18
|
module Surrealist
|
13
|
-
#
|
14
|
-
|
15
|
-
|
16
|
-
# Error class for classes with +json_schema+ defined not as a hash.
|
17
|
-
class InvalidSchemaError < RuntimeError; end
|
18
|
-
|
19
|
-
# Error class for +NoMethodError+.
|
20
|
-
class UndefinedMethodError < RuntimeError; end
|
21
|
-
|
22
|
-
# Error class for failed type-checks.
|
23
|
-
class InvalidTypeError < TypeError; end
|
24
|
-
|
25
|
-
# Error class for undefined root keys for schema wrapping.
|
26
|
-
class UnknownRootError < RuntimeError; end
|
19
|
+
# Default namespaces nesting level
|
20
|
+
DEFAULT_NESTING_LEVEL = 666
|
27
21
|
|
28
22
|
class << self
|
29
23
|
# @param [Class] base class to include/extend +Surrealist+.
|
@@ -32,58 +26,43 @@ module Surrealist
|
|
32
26
|
base.include(Surrealist::InstanceMethods)
|
33
27
|
end
|
34
28
|
|
35
|
-
#
|
36
|
-
#
|
29
|
+
# Iterates over a collection of Surrealist Objects and
|
30
|
+
# maps surrealize to each record.
|
37
31
|
#
|
38
|
-
# @param [Object]
|
32
|
+
# @param [Object] Collection of instances of a class that has +Surrealist+ included.
|
39
33
|
# @param [Boolean] camelize optional argument for converting hash to camelBack.
|
40
34
|
# @param [Boolean] include_root optional argument for having the root key of the resulting hash
|
41
35
|
# as instance's class name.
|
42
36
|
#
|
43
|
-
# @return [
|
44
|
-
# provided in the object's class. Values will be taken from the return values
|
37
|
+
# @return [Object] the Collection#map with elements being json-formatted string corresponding
|
38
|
+
# to the schema provided in the object's class. Values will be taken from the return values
|
45
39
|
# of appropriate methods from the object.
|
46
40
|
#
|
47
|
-
# @raise +Surrealist::
|
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
|
41
|
+
# @raise +Surrealist::InvalidCollectionError+ if invalid collection passed.
|
64
42
|
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
# def age
|
70
|
-
# 23
|
71
|
-
# end
|
72
|
-
# end
|
73
|
-
#
|
74
|
-
# User.new.surrealize
|
75
|
-
# # => "{\"name\":\"Nikita\",\"age\":23}"
|
43
|
+
# @example surrealize a given collection of Surrealist objects
|
44
|
+
# Surrealist.surrealize_collection(User.all)
|
45
|
+
# # => "[{\"name\":\"Nikita\",\"age\":23}, {\"name\":\"Alessandro\",\"age\":24}]"
|
76
46
|
# # For more examples see README
|
77
|
-
def
|
78
|
-
|
47
|
+
def surrealize_collection(collection, camelize: false, include_root: false, include_namespaces: false, namespaces_nesting_level: DEFAULT_NESTING_LEVEL) # rubocop:disable Metrics/LineLength
|
48
|
+
unless collection.respond_to?(:each)
|
49
|
+
raise Surrealist::ExceptionRaiser.raise_invalid_collection!
|
50
|
+
end
|
51
|
+
|
52
|
+
JSON.dump(collection.map do |record|
|
53
|
+
record.build_schema(
|
54
|
+
camelize: camelize,
|
55
|
+
include_root: include_root,
|
56
|
+
include_namespaces: include_namespaces,
|
57
|
+
namespaces_nesting_level: namespaces_nesting_level,
|
58
|
+
)
|
59
|
+
end)
|
79
60
|
end
|
80
61
|
|
81
62
|
# Builds hash from schema provided in the object's class and type-checks the values.
|
82
63
|
#
|
83
64
|
# @param [Object] instance of a class that has +Surrealist+ included.
|
84
|
-
# @param [
|
85
|
-
# @param [Boolean] include_root optional argument for having the root key of the resulting hash
|
86
|
-
# as instance's class name.
|
65
|
+
# @param [Object] carrier instance of Carrier class that carries arguments passed to +surrealize+
|
87
66
|
#
|
88
67
|
# @return [Hash] a hash corresponding to the schema
|
89
68
|
# provided in the object's class. Values will be taken from the return values
|
@@ -119,29 +98,20 @@ module Surrealist
|
|
119
98
|
# User.new.build_schema
|
120
99
|
# # => { name: 'Nikita', age: 23 }
|
121
100
|
# # For more examples see README
|
122
|
-
def build_schema(instance:,
|
123
|
-
|
101
|
+
def build_schema(instance:, carrier:)
|
102
|
+
delegatee = instance.class.instance_variable_get('@__surrealist_schema_parent')
|
103
|
+
schema = (delegatee || instance.class).instance_variable_get('@__surrealist_schema')
|
124
104
|
|
125
|
-
raise_unknown_schema!(instance) if schema.nil?
|
105
|
+
Surrealist::ExceptionRaiser.raise_unknown_schema!(instance) if schema.nil?
|
126
106
|
|
127
|
-
normalized_schema = Surrealist::
|
128
|
-
hash:
|
129
|
-
klass:
|
130
|
-
|
131
|
-
include_root: include_root,
|
107
|
+
normalized_schema = Surrealist::Copier.deep_copy(
|
108
|
+
hash: schema,
|
109
|
+
klass: instance.class.name,
|
110
|
+
carrier: carrier,
|
132
111
|
)
|
133
112
|
|
134
113
|
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."
|
114
|
+
carrier.camelize ? Surrealist::HashUtils.camelize_hash(hash) : hash
|
145
115
|
end
|
146
116
|
end
|
147
117
|
end
|
data/surrealist.gemspec
CHANGED
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.
|
4
|
+
version: 0.2.0
|
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-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0.12'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.50.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.50.0
|
83
97
|
description: A gem that provides DSL for serialization of plain old Ruby objects to
|
84
98
|
JSON in a declarative style by defining a `schema`. It also provides a trivial type
|
85
99
|
checking in the runtime before serialization.
|
@@ -91,20 +105,27 @@ extra_rdoc_files: []
|
|
91
105
|
files:
|
92
106
|
- ".gitignore"
|
93
107
|
- ".rspec"
|
108
|
+
- ".rubocop.yml"
|
94
109
|
- ".travis.yml"
|
95
110
|
- CHANGELOG.md
|
111
|
+
- CONTRIBUTING.md
|
96
112
|
- Gemfile
|
97
113
|
- LICENSE.txt
|
98
114
|
- README.md
|
115
|
+
- Rakefile
|
99
116
|
- bin/console
|
100
117
|
- lib/surrealist.rb
|
101
118
|
- lib/surrealist/any.rb
|
102
119
|
- lib/surrealist/bool.rb
|
103
120
|
- lib/surrealist/builder.rb
|
121
|
+
- lib/surrealist/carrier.rb
|
104
122
|
- lib/surrealist/class_methods.rb
|
123
|
+
- lib/surrealist/copier.rb
|
124
|
+
- lib/surrealist/exception_raiser.rb
|
105
125
|
- lib/surrealist/hash_utils.rb
|
106
126
|
- lib/surrealist/instance_methods.rb
|
107
127
|
- lib/surrealist/schema_definer.rb
|
128
|
+
- lib/surrealist/string_utils.rb
|
108
129
|
- lib/surrealist/type_helper.rb
|
109
130
|
- lib/surrealist/version.rb
|
110
131
|
- surrealist-icon.png
|
@@ -129,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
150
|
version: '0'
|
130
151
|
requirements: []
|
131
152
|
rubyforge_project:
|
132
|
-
rubygems_version: 2.6.
|
153
|
+
rubygems_version: 2.6.14
|
133
154
|
signing_key:
|
134
155
|
specification_version: 4
|
135
156
|
summary: A gem that provides DSL for serialization of plain old Ruby objects to JSON
|