surrealist 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/.travis.yml +3 -1
- data/CHANGELOG.md +13 -1
- data/Gemfile +1 -3
- data/README.md +68 -1
- data/bin/console +1 -1
- data/gemfiles/activerecord42.gemfile +0 -2
- data/lib/surrealist/builder.rb +3 -3
- data/lib/surrealist/carrier.rb +14 -12
- data/lib/surrealist/class_methods.rb +15 -1
- data/lib/surrealist/copier.rb +4 -2
- data/lib/surrealist/exception_raiser.rb +8 -4
- data/lib/surrealist/hash_utils.rb +1 -1
- data/lib/surrealist/helper.rb +8 -10
- data/lib/surrealist/instance_methods.rb +13 -25
- data/lib/surrealist/schema_definer.rb +4 -7
- data/lib/surrealist/serializer.rb +70 -0
- data/lib/surrealist/string_utils.rb +21 -13
- data/lib/surrealist/type_helper.rb +2 -2
- data/lib/surrealist/value_assigner.rb +1 -1
- data/lib/surrealist/vars_helper.rb +65 -0
- data/lib/surrealist/version.rb +1 -1
- data/lib/surrealist.rb +66 -50
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 936bf35d7c1f0511db6d2f9458ade02330ec2246d444f9c0c7f9069ed7f24d73
|
4
|
+
data.tar.gz: 1b91381b04081fb2e4cd6af4167cd41907f982c9a1f713077e76d78ec51a82c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d058ddf6a4314ee51c09fc2bd7866302971d9a8f9171430189d95df37871331883237519579c9044a4e720435bdf99702e134e9af992cddb4b5a241db528a92e
|
7
|
+
data.tar.gz: ccee664c6cadf85ec84e5584a8ac1c9490f43a5dfb7308ecf7d8d883daa5f4bbc6c6f1a2836c58f30677bb1ca6a5cfb1c5111f03db9ca6e0b9fade5420a11998
|
data/.rubocop.yml
CHANGED
data/.travis.yml
CHANGED
@@ -8,6 +8,8 @@ matrix:
|
|
8
8
|
include:
|
9
9
|
- rvm: ruby-head
|
10
10
|
gemfile: Gemfile
|
11
|
+
- rvm: 2.5.0
|
12
|
+
gemfile: Gemfile
|
11
13
|
- rvm: 2.4.2
|
12
14
|
gemfile: Gemfile
|
13
15
|
- rvm: 2.4.0
|
@@ -17,6 +19,6 @@ matrix:
|
|
17
19
|
- rvm: 2.3.1
|
18
20
|
gemfile: Gemfile
|
19
21
|
- rvm: 2.2.5
|
20
|
-
gemfile:
|
22
|
+
gemfile: gemfiles/activerecord42.gemfile
|
21
23
|
- rvm: 2.2.0
|
22
24
|
gemfile: gemfiles/activerecord42.gemfile
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,20 @@
|
|
1
|
+
# 0.4.0
|
2
|
+
|
3
|
+
## Added
|
4
|
+
* Introduce an abstract serializer class (@nesaulov) #61
|
5
|
+
* Full integration for Sequel (@nesaulov) #47
|
6
|
+
* Integration for ROM 4.x (@nesaulov) #56
|
7
|
+
* Ruby 2.5 support (@nesaulov) #57
|
8
|
+
|
9
|
+
## Miscellaneous
|
10
|
+
* Memory & performance optimizations (@nesaulov) #51
|
11
|
+
* Refactorings (@nulldef) #55
|
12
|
+
|
1
13
|
# 0.3.0
|
2
14
|
|
3
15
|
## Added
|
4
16
|
* Full integration for ActiveRecord (@nesaulov, @AlessandroMinali) #37
|
5
|
-
* Full integration for ROM (@nesaulov, @AlessandroMinali) #37
|
17
|
+
* Full integration for ROM <= 3 (@nesaulov, @AlessandroMinali) #37
|
6
18
|
* `root` optional argument (@chrisatanasian) #32
|
7
19
|
* Nested records surrealization (@AlessandroMinali) #34
|
8
20
|
|
data/Gemfile
CHANGED
@@ -6,11 +6,9 @@ gemspec
|
|
6
6
|
group :development, :test do
|
7
7
|
gem 'activerecord'
|
8
8
|
gem 'coveralls', require: false
|
9
|
-
gem 'data_mapper'
|
10
|
-
gem 'dm-sqlite-adapter'
|
11
9
|
gem 'dry-struct'
|
12
10
|
gem 'dry-types'
|
13
|
-
gem 'rom', '~>
|
11
|
+
gem 'rom', '~> 4.0'
|
14
12
|
gem 'rom-repository'
|
15
13
|
gem 'rom-sql'
|
16
14
|
gem 'sequel'
|
data/README.md
CHANGED
@@ -24,6 +24,7 @@ to serialize nested objects and structures. [Introductory blogpost.](https://med
|
|
24
24
|
* [Nested objects](#nested-objects)
|
25
25
|
* [Delegating Surrealization](#delegating-surrealization)
|
26
26
|
* [Usage with Dry::Types](#usage-with-drytypes)
|
27
|
+
* [Defining custom serializers](#defining-custom-serializers)
|
27
28
|
* [Build schema](#build-schema)
|
28
29
|
* [Camelization](#camelization)
|
29
30
|
* [Include root](#include-root)
|
@@ -247,6 +248,72 @@ Car.new.surrealize
|
|
247
248
|
# => '{ "age": 7, "brand": "Toyota", "doors": null, "horsepower": 140, "fuel_system": "Direct injection", "previous_owner": "John Doe" }'
|
248
249
|
```
|
249
250
|
|
251
|
+
### Defining custom serializers
|
252
|
+
If you need to keep serialization logic separately from the model, you can define a class that
|
253
|
+
will inherit from `Surrealist::Serializer`. To point to that class from the model use a class method
|
254
|
+
`.surrealize_with`. Example usage:
|
255
|
+
``` ruby
|
256
|
+
class CatSerializer < Surrealist::Serializer
|
257
|
+
json_schema { { age: Integer, age_group: String } }
|
258
|
+
|
259
|
+
def age_group
|
260
|
+
age <= 5 ? 'kitten' : 'cat'
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
class Cat
|
265
|
+
include Surrealist
|
266
|
+
attr_reader :age
|
267
|
+
|
268
|
+
surrealize_with CatSerializer
|
269
|
+
|
270
|
+
def initialize(age)
|
271
|
+
@age = age
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
Cat.new(12).surrealize # Implicit usage through .surrealize_with
|
276
|
+
# => '{ "age": 12, "age_group": "cat" }'
|
277
|
+
|
278
|
+
CatSerializer.new(Cat.new(3)).surrealize # explicit usage of CatSerializer
|
279
|
+
# => '{ "age": 3, "age_group": "kitten" }'
|
280
|
+
```
|
281
|
+
The constructor of `Surrealist::Serializer` takes two arguments: serializable model (or collection) and
|
282
|
+
a context hash. So if there is an object that is not coupled to serializable model
|
283
|
+
but it is still necessary for constructing JSON, you can pass it to constructor as a hash. It will
|
284
|
+
be available in the serializer in the `context` hash.
|
285
|
+
``` ruby
|
286
|
+
class IncomeSerializer < Surrealist::Serializer
|
287
|
+
json_schema { { amount: Integer } }
|
288
|
+
|
289
|
+
def amount
|
290
|
+
current_user.guest? ? 100000000 : object.amount
|
291
|
+
end
|
292
|
+
|
293
|
+
def current_user
|
294
|
+
context[:current_user]
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
class Income
|
299
|
+
include Surrealist
|
300
|
+
surrealize_with IncomeSerializer
|
301
|
+
|
302
|
+
attr_reader :amount
|
303
|
+
|
304
|
+
def initialize(amount)
|
305
|
+
@amount = amount
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
income = Income.new(200)
|
310
|
+
IncomeSerializer.new(income, current_user: GuestUser.new).surrealize
|
311
|
+
# => '{ "amount": 100000000 }'
|
312
|
+
|
313
|
+
IncomeSerializer.new(income, current_user: User.find(3)).surrealize
|
314
|
+
# => '{ "amount": 200 }'
|
315
|
+
```
|
316
|
+
|
250
317
|
### Build schema
|
251
318
|
If you don't need to dump the hash to json, you can use `#build_schema`
|
252
319
|
method on the instance. It calculates values and checks types, but returns
|
@@ -451,7 +518,7 @@ type check will be passed. If you want to be strict about `nil`s consider using
|
|
451
518
|
|
452
519
|
## Roadmap
|
453
520
|
Here is a list of features that are not implemented yet (contributions are welcome):
|
454
|
-
* [
|
521
|
+
* [Benchmarks](https://github.com/nesaulov/surrealist/issues/40)
|
455
522
|
|
456
523
|
## Contributing
|
457
524
|
Bug reports and pull requests are welcome on GitHub at https://github.com/nesaulov/surrealist.
|
data/bin/console
CHANGED
data/lib/surrealist/builder.rb
CHANGED
@@ -6,8 +6,6 @@ module Surrealist
|
|
6
6
|
# Struct to carry schema along
|
7
7
|
Schema = Struct.new(:key, :value).freeze
|
8
8
|
|
9
|
-
attr_reader :carrier, :instance, :schema
|
10
|
-
|
11
9
|
# @param [Carrier] carrier instance of Surrealist::Carrier
|
12
10
|
# @param [Hash] schema the schema defined in the object's class.
|
13
11
|
# @param [Object] instance the instance of the object which methods from the schema are called on.
|
@@ -31,7 +29,7 @@ module Surrealist
|
|
31
29
|
if schema_value.is_a?(Hash)
|
32
30
|
check_for_ar(schema, instance, schema_key, schema_value)
|
33
31
|
else
|
34
|
-
ValueAssigner.assign(schema:
|
32
|
+
ValueAssigner.assign(schema: Schema.new(schema_key, schema_value),
|
35
33
|
instance: instance) { |coerced_value| schema[schema_key] = coerced_value }
|
36
34
|
end
|
37
35
|
end
|
@@ -41,6 +39,8 @@ module Surrealist
|
|
41
39
|
|
42
40
|
private
|
43
41
|
|
42
|
+
attr_reader :carrier, :instance, :schema
|
43
|
+
|
44
44
|
# Checks if result is an instance of ActiveRecord::Relation
|
45
45
|
#
|
46
46
|
# @param [Hash] schema the schema defined in the object's class.
|
data/lib/surrealist/carrier.rb
CHANGED
@@ -3,6 +3,10 @@
|
|
3
3
|
module Surrealist
|
4
4
|
# A data structure to carry arguments across methods.
|
5
5
|
class Carrier
|
6
|
+
BOOLEANS = [true, false].freeze
|
7
|
+
|
8
|
+
attr_reader :camelize, :include_root, :include_namespaces, :root, :namespaces_nesting_level
|
9
|
+
|
6
10
|
# Public wrapper for Carrier.
|
7
11
|
#
|
8
12
|
# @param [Boolean] camelize optional argument for converting hash to camelBack.
|
@@ -16,18 +20,16 @@ module Surrealist
|
|
16
20
|
# @raise ArgumentError if types of arguments are wrong.
|
17
21
|
#
|
18
22
|
# @return [Carrier] self if type checks were passed.
|
19
|
-
def self.call(
|
20
|
-
new(
|
23
|
+
def self.call(**args)
|
24
|
+
new(args).sanitize!
|
21
25
|
end
|
22
26
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
@
|
27
|
-
@
|
28
|
-
@
|
29
|
-
@root = root
|
30
|
-
@namespaces_nesting_level = namespaces_nesting_level
|
27
|
+
def initialize(**args)
|
28
|
+
@camelize = args.delete(:camelize) || false
|
29
|
+
@include_root = args.delete(:include_root) || false
|
30
|
+
@include_namespaces = args.delete(:include_namespaces) || false
|
31
|
+
@root = args.delete(:root) || nil
|
32
|
+
@namespaces_nesting_level = args.delete(:namespaces_nesting_level) || DEFAULT_NESTING_LEVEL
|
31
33
|
end
|
32
34
|
|
33
35
|
# Performs type checks
|
@@ -47,7 +49,7 @@ module Surrealist
|
|
47
49
|
# @raise ArgumentError
|
48
50
|
def check_booleans!
|
49
51
|
booleans_hash.each do |key, value|
|
50
|
-
unless
|
52
|
+
unless BOOLEANS.include?(value)
|
51
53
|
raise ArgumentError, "Expected `#{key}` to be either true or false, got #{value}"
|
52
54
|
end
|
53
55
|
end
|
@@ -73,7 +75,7 @@ module Surrealist
|
|
73
75
|
# Checks if root is not nil, a non-empty string, or symbol
|
74
76
|
# @raise ArgumentError
|
75
77
|
def check_root!
|
76
|
-
unless root.nil? || (root.is_a?(String) && root.
|
78
|
+
unless root.nil? || (root.is_a?(String) && !root.strip.empty?) || root.is_a?(Symbol)
|
77
79
|
Surrealist::ExceptionRaiser.raise_invalid_root!(root)
|
78
80
|
end
|
79
81
|
end
|
@@ -89,7 +89,21 @@ module Surrealist
|
|
89
89
|
|
90
90
|
Surrealist::ExceptionRaiser.raise_invalid_schema_delegation! unless Helper.surrealist?(klass)
|
91
91
|
|
92
|
-
|
92
|
+
hash = Surrealist::VarsHelper.find_schema(klass)
|
93
|
+
Surrealist::VarsHelper.set_schema(self, hash)
|
94
|
+
end
|
95
|
+
|
96
|
+
# A DSL method for defining a class that holds serialization logic.
|
97
|
+
#
|
98
|
+
# @param [Class] klass a class that should inherit form Surrealist::Serializer
|
99
|
+
#
|
100
|
+
# @raise ArgumentError if Surrealist::Serializer is not found in the ancestors chain
|
101
|
+
def surrealize_with(klass)
|
102
|
+
if klass < Surrealist::Serializer
|
103
|
+
Surrealist::VarsHelper.set_serializer(self, klass)
|
104
|
+
else
|
105
|
+
raise ArgumentError, "#{klass} should be inherited from Surrealist::Serializer"
|
106
|
+
end
|
93
107
|
end
|
94
108
|
end
|
95
109
|
end
|
data/lib/surrealist/copier.rb
CHANGED
@@ -2,7 +2,9 @@
|
|
2
2
|
|
3
3
|
module Surrealist
|
4
4
|
# A helper class for deep copying and wrapping hashes.
|
5
|
-
|
5
|
+
module Copier
|
6
|
+
EMPTY_HASH = {}.freeze
|
7
|
+
|
6
8
|
class << self
|
7
9
|
# Deeply copies the schema hash and wraps it if there is a need to.
|
8
10
|
#
|
@@ -107,7 +109,7 @@ module Surrealist
|
|
107
109
|
# @return [Hash] resulting hash.
|
108
110
|
def inject_schema(hash, sub_hash)
|
109
111
|
hash.each do |k, v|
|
110
|
-
v ==
|
112
|
+
v == EMPTY_HASH ? hash[k] = sub_hash : inject_schema(v, sub_hash)
|
111
113
|
end
|
112
114
|
end
|
113
115
|
end
|
@@ -26,14 +26,18 @@ module Surrealist
|
|
26
26
|
class InvalidNestingLevel < ArgumentError; end
|
27
27
|
|
28
28
|
# A class that raises all Surrealist exceptions
|
29
|
-
|
29
|
+
module ExceptionRaiser
|
30
|
+
CLASS_NAME_NOT_PASSED = "Can't wrap schema in root key - class name was not passed".freeze
|
31
|
+
MUST_RESPOND_TO_EACH = "Can't serialize collection - must respond to :each".freeze
|
32
|
+
CLASS_DOESNT_INCLUDE_SURREALIST = 'Class does not include Surrealist'.freeze
|
33
|
+
|
30
34
|
class << self
|
31
35
|
# Raises Surrealist::InvalidSchemaDelegation if destination of delegation does not
|
32
36
|
# include Surrealist.
|
33
37
|
#
|
34
38
|
# @raise Surrealist::InvalidSchemaDelegation
|
35
39
|
def raise_invalid_schema_delegation!
|
36
|
-
raise Surrealist::InvalidSchemaDelegation,
|
40
|
+
raise Surrealist::InvalidSchemaDelegation, CLASS_DOESNT_INCLUDE_SURREALIST
|
37
41
|
end
|
38
42
|
|
39
43
|
# Raises Surrealist::UnknownSchemaError
|
@@ -50,14 +54,14 @@ module Surrealist
|
|
50
54
|
#
|
51
55
|
# @raise Surrealist::UnknownRootError
|
52
56
|
def raise_unknown_root!
|
53
|
-
raise Surrealist::UnknownRootError,
|
57
|
+
raise Surrealist::UnknownRootError, CLASS_NAME_NOT_PASSED
|
54
58
|
end
|
55
59
|
|
56
60
|
# Raises Surrealist::InvalidCollectionError
|
57
61
|
#
|
58
62
|
# @raise Surrealist::InvalidCollectionError
|
59
63
|
def raise_invalid_collection!
|
60
|
-
raise Surrealist::InvalidCollectionError,
|
64
|
+
raise Surrealist::InvalidCollectionError, MUST_RESPOND_TO_EACH
|
61
65
|
end
|
62
66
|
|
63
67
|
# Raises ArgumentError if namespaces_nesting_level is not an integer.
|
data/lib/surrealist/helper.rb
CHANGED
@@ -2,16 +2,14 @@
|
|
2
2
|
|
3
3
|
module Surrealist
|
4
4
|
# A generic helper.
|
5
|
-
|
6
|
-
class
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
klass < Surrealist
|
14
|
-
end
|
5
|
+
module Helper
|
6
|
+
# Determines if the class uses the Surrealist mixin.
|
7
|
+
#
|
8
|
+
# @param [Class] klass a class to be checked.
|
9
|
+
#
|
10
|
+
# @return [Boolean] if Surrealist is included in class.
|
11
|
+
def self.surrealist?(klass)
|
12
|
+
klass < Surrealist || klass < Surrealist::Serializer
|
15
13
|
end
|
16
14
|
end
|
17
15
|
end
|
@@ -6,13 +6,13 @@ module Surrealist
|
|
6
6
|
# Dumps the object's methods corresponding to the schema
|
7
7
|
# provided in the object's class and type-checks the values.
|
8
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
|
9
|
+
# @param [Boolean] [optional] camelize optional argument for converting hash to camelBack.
|
10
|
+
# @param [Boolean] [optional] include_root optional argument for having the root key of the resulting hash
|
11
11
|
# as instance's class name.
|
12
|
-
# @param [Boolean] include_namespaces optional argument for having root key as a nested hash of
|
12
|
+
# @param [Boolean] [optional] include_namespaces optional argument for having root key as a nested hash of
|
13
13
|
# instance's namespaces. Animal::Cat.new.surrealize -> (animal: { cat: { weight: '3 kilos' } })
|
14
|
-
# @param [String] root optional argument for using a specified root key for the hash
|
15
|
-
# @param [Integer] namespaces_nesting_level level of namespaces nesting.
|
14
|
+
# @param [String] [optional] root optional argument for using a specified root key for the hash
|
15
|
+
# @param [Integer] [optional] namespaces_nesting_level level of namespaces nesting.
|
16
16
|
#
|
17
17
|
# @return [String] a json-formatted string corresponding to the schema
|
18
18
|
# provided in the object's class. Values will be taken from the return values
|
@@ -48,29 +48,17 @@ module Surrealist
|
|
48
48
|
# User.new.surrealize
|
49
49
|
# # => "{\"name\":\"Nikita\",\"age\":23}"
|
50
50
|
# # For more examples see README
|
51
|
-
def surrealize(
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
root: root,
|
58
|
-
namespaces_nesting_level: namespaces_nesting_level,
|
59
|
-
),
|
60
|
-
)
|
51
|
+
def surrealize(**args)
|
52
|
+
if (serializer = Surrealist::VarsHelper.find_serializer(self.class))
|
53
|
+
return serializer.new(self).surrealize(args)
|
54
|
+
end
|
55
|
+
|
56
|
+
JSON.dump(build_schema(args))
|
61
57
|
end
|
62
58
|
|
63
59
|
# Invokes +Surrealist+'s class method +build_schema+
|
64
|
-
def build_schema(
|
65
|
-
|
66
|
-
camelize: camelize,
|
67
|
-
include_namespaces: include_namespaces,
|
68
|
-
include_root: include_root,
|
69
|
-
root: root,
|
70
|
-
namespaces_nesting_level: namespaces_nesting_level,
|
71
|
-
)
|
72
|
-
|
73
|
-
Surrealist.build_schema(instance: self, carrier: carrier)
|
60
|
+
def build_schema(**args)
|
61
|
+
Surrealist.build_schema(instance: self, **args)
|
74
62
|
end
|
75
63
|
end
|
76
64
|
end
|
@@ -2,7 +2,8 @@
|
|
2
2
|
|
3
3
|
module Surrealist
|
4
4
|
# A class that defines a method on the object that stores the schema.
|
5
|
-
|
5
|
+
module SchemaDefiner
|
6
|
+
SCHEMA_TYPE_ERROR = 'Schema should be defined as a hash'.freeze
|
6
7
|
# Defines an instance variable on the object that stores the schema.
|
7
8
|
#
|
8
9
|
# @param [Object] klass class of the object that needs to be surrealized.
|
@@ -13,13 +14,9 @@ module Surrealist
|
|
13
14
|
#
|
14
15
|
# @raise +Surrealist::InvalidSchemaError+ if schema was defined not through a hash.
|
15
16
|
def self.call(klass, hash)
|
16
|
-
raise Surrealist::InvalidSchemaError,
|
17
|
+
raise Surrealist::InvalidSchemaError, SCHEMA_TYPE_ERROR unless hash.is_a?(Hash)
|
17
18
|
|
18
|
-
|
19
|
-
klass.class_variable_set('@@__surrealist_schema', hash)
|
20
|
-
else
|
21
|
-
klass.instance_variable_set('@__surrealist_schema', hash)
|
22
|
-
end
|
19
|
+
Surrealist::VarsHelper.set_schema(klass, hash)
|
23
20
|
end
|
24
21
|
end
|
25
22
|
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Surrealist
|
4
|
+
# Abstract class to be inherited from
|
5
|
+
#
|
6
|
+
# @example Usage
|
7
|
+
# class CatSerializer < Surrealist::Serializer
|
8
|
+
# json_schema { { age: Integer, age_group: String } }
|
9
|
+
#
|
10
|
+
# def age_group
|
11
|
+
# age <= 5 ? 'kitten' : 'cat'
|
12
|
+
# end
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# class Cat
|
16
|
+
# include Surrealist
|
17
|
+
# attr_reader :age
|
18
|
+
#
|
19
|
+
# surrealize_with CatSerializer
|
20
|
+
#
|
21
|
+
# def initialize(age)
|
22
|
+
# @age = age
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# Cat.new(12).surrealize # Checks for schema in CatSerializer (if .surrealize_with is stated)
|
27
|
+
# # => '{ "age": 12, "age_group": "cat" }'
|
28
|
+
#
|
29
|
+
# CatSerializer.new(Cat.new(3)).surrealize # explicit usage of CatSerializer
|
30
|
+
# # => '{ "age": 3, "age_group": "kitten" }'
|
31
|
+
class Serializer
|
32
|
+
extend Surrealist::ClassMethods
|
33
|
+
|
34
|
+
# NOTE: #context will work only when using serializer explicitly,
|
35
|
+
# e.g `CatSerializer.new(Cat.new(3), food: CatFood.new)`
|
36
|
+
# And then food will be available inside serializer via `context[:food]`
|
37
|
+
def initialize(object, **context)
|
38
|
+
@object = object
|
39
|
+
@context = context
|
40
|
+
end
|
41
|
+
|
42
|
+
# Checks whether object is a collection or an instance and serializes it
|
43
|
+
def surrealize(**args)
|
44
|
+
if object.respond_to?(:each)
|
45
|
+
Surrealist.surrealize_collection(object, args.merge(context: context))
|
46
|
+
else
|
47
|
+
Surrealist.surrealize(instance: self, **args)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Passes build_schema to Surrealist
|
52
|
+
def build_schema(**args)
|
53
|
+
Surrealist.build_schema(instance: self, **args)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
attr_reader :object, :context
|
59
|
+
|
60
|
+
# Methods not found inside serializer will be invoked on the object
|
61
|
+
def method_missing(method, *args, &block)
|
62
|
+
object.public_send(method, *args, &block)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Methods not found inside serializer will be invoked on the object
|
66
|
+
def respond_to_missing?(method, include_private = false)
|
67
|
+
object.respond_to?(method) || super
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -2,18 +2,27 @@
|
|
2
2
|
|
3
3
|
module Surrealist
|
4
4
|
# A helper class for strings transformations.
|
5
|
-
|
5
|
+
module StringUtils
|
6
|
+
DASH = '-'.freeze
|
7
|
+
UNDERSCORE = '_'.freeze
|
8
|
+
EMPTY_STRING = ''.freeze
|
9
|
+
DASH_REGEXP1 = /([A-Z]+)([A-Z][a-z])/o
|
10
|
+
DASH_REGEXP2 = /([a-z\d])([A-Z])/o
|
11
|
+
UNDERSCORE_REGEXP = /(?:^|_)([^_\s]+)/o
|
12
|
+
NAMESPACES_SEPARATOR = '::'.freeze
|
13
|
+
UNDERSCORE_SUBSTITUTE = '\1_\2'.freeze
|
14
|
+
|
6
15
|
class << self
|
7
16
|
# Converts a string to snake_case.
|
8
17
|
#
|
9
18
|
# @param [String] string a string to be underscored.
|
10
19
|
#
|
11
|
-
# @return [String] underscored string.
|
20
|
+
# @return [String] new underscored string.
|
12
21
|
def underscore(string)
|
13
|
-
string.gsub(
|
14
|
-
.gsub(
|
15
|
-
.gsub(
|
16
|
-
.tr(
|
22
|
+
string.gsub(NAMESPACES_SEPARATOR, UNDERSCORE)
|
23
|
+
.gsub(DASH_REGEXP1, UNDERSCORE_SUBSTITUTE)
|
24
|
+
.gsub(DASH_REGEXP2, UNDERSCORE_SUBSTITUTE)
|
25
|
+
.tr(DASH, UNDERSCORE)
|
17
26
|
.downcase
|
18
27
|
end
|
19
28
|
|
@@ -25,12 +34,11 @@ module Surrealist
|
|
25
34
|
# @return [String] camelized string.
|
26
35
|
def camelize(snake_string, first_upper = true)
|
27
36
|
if first_upper
|
28
|
-
snake_string.to_s
|
29
|
-
.gsub(/(?:^|_)([^_\s]+)/) { Regexp.last_match[1].capitalize }
|
37
|
+
snake_string.to_s.gsub(UNDERSCORE_REGEXP) { Regexp.last_match[1].capitalize }
|
30
38
|
else
|
31
|
-
parts = snake_string.split(
|
39
|
+
parts = snake_string.split(UNDERSCORE, 2)
|
32
40
|
parts[0] << camelize(parts[1]) if parts.size > 1
|
33
|
-
parts[0] ||
|
41
|
+
parts[0] || EMPTY_STRING
|
34
42
|
end
|
35
43
|
end
|
36
44
|
|
@@ -43,7 +51,7 @@ module Surrealist
|
|
43
51
|
#
|
44
52
|
# @return [String] extracted class
|
45
53
|
def extract_class(string)
|
46
|
-
uncapitalize(string.split(
|
54
|
+
uncapitalize(string.split(NAMESPACES_SEPARATOR).last)
|
47
55
|
end
|
48
56
|
|
49
57
|
# Extracts n amount of classes from a namespaces and returns a nested hash.
|
@@ -62,8 +70,8 @@ module Surrealist
|
|
62
70
|
# @return [Hash] a nested hash.
|
63
71
|
def break_namespaces(klass, camelize:, nesting_level:)
|
64
72
|
Surrealist::ExceptionRaiser.raise_invalid_nesting!(nesting_level) unless nesting_level > 0
|
65
|
-
|
66
|
-
|
73
|
+
|
74
|
+
klass.split(NAMESPACES_SEPARATOR).last(nesting_level).reverse.inject({}) do |a, n|
|
67
75
|
camelize ? Hash[camelize(uncapitalize(n), false).to_sym => a] : Hash[underscore(n).to_sym => a]
|
68
76
|
end
|
69
77
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Surrealist
|
4
4
|
# Service class for type checking
|
5
|
-
|
5
|
+
module TypeHelper
|
6
6
|
# Dry-types class matcher
|
7
7
|
DRY_TYPE_CLASS = 'Dry::Types'.freeze
|
8
8
|
|
@@ -18,7 +18,7 @@ module Surrealist
|
|
18
18
|
return true if type == Any
|
19
19
|
|
20
20
|
if type == Bool
|
21
|
-
|
21
|
+
Surrealist::Carrier::BOOLEANS.include?(value)
|
22
22
|
elsif dry_type?(type)
|
23
23
|
type.try(value).success?
|
24
24
|
else
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Surrealist
|
4
4
|
# A class that determines the correct value to return for serialization. May descend recursively.
|
5
|
-
|
5
|
+
module ValueAssigner
|
6
6
|
class << self
|
7
7
|
# Assigns value returned from a method to a corresponding key in the schema hash.
|
8
8
|
#
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Surrealist
|
2
|
+
# Module for finding and setting hash into vars
|
3
|
+
module VarsHelper
|
4
|
+
# Instance variable name that is set by SchemaDefiner
|
5
|
+
INSTANCE_VARIABLE = '@__surrealist_schema'.freeze
|
6
|
+
# Instance's parent instance variable name that is set by SchemaDefiner
|
7
|
+
PARENT_VARIABLE = '@__surrealist_schema_parent'.freeze
|
8
|
+
# Class variable name that is set by SchemaDefiner
|
9
|
+
CLASS_VARIABLE = '@@__surrealist_schema'.freeze
|
10
|
+
# Regexp to resolve ROM structure
|
11
|
+
ROM_REGEXP = /ROM::Struct/o
|
12
|
+
# Instance variable that keeps serializer class
|
13
|
+
SERIALIZER_CLASS = '@__surrealist_serializer'.freeze
|
14
|
+
|
15
|
+
class << self
|
16
|
+
# Find the schema
|
17
|
+
#
|
18
|
+
# @param [Class] klass Class that included Surrealist
|
19
|
+
#
|
20
|
+
# @return [Hash] Found hash
|
21
|
+
def find_schema(klass)
|
22
|
+
if use_class_var?(klass)
|
23
|
+
klass.class_variable_get(CLASS_VARIABLE) if klass.class_variable_defined?(CLASS_VARIABLE)
|
24
|
+
else
|
25
|
+
klass.instance_variable_get(INSTANCE_VARIABLE)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Setting schema into var
|
30
|
+
#
|
31
|
+
# @param [Class] klass Class that included Surrealist
|
32
|
+
# @param [Hash] hash Schema hash
|
33
|
+
def set_schema(klass, hash)
|
34
|
+
if use_class_var?(klass)
|
35
|
+
klass.class_variable_set(CLASS_VARIABLE, hash)
|
36
|
+
else
|
37
|
+
klass.instance_variable_set(INSTANCE_VARIABLE, hash)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Checks if there is a serializer defined for a given class and returns it
|
42
|
+
#
|
43
|
+
# @param [Class] klass a class to check
|
44
|
+
#
|
45
|
+
# @return [Class | nil]
|
46
|
+
def find_serializer(klass)
|
47
|
+
klass.instance_variable_get(SERIALIZER_CLASS)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Sets a serializer for class
|
51
|
+
#
|
52
|
+
# @param [Class] self_class class of object that points to serializer
|
53
|
+
# @param [Class] serializer_class class of serializer
|
54
|
+
def set_serializer(self_class, serializer_class)
|
55
|
+
self_class.instance_variable_set(SERIALIZER_CLASS, serializer_class)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def use_class_var?(klass)
|
61
|
+
klass.name =~ ROM_REGEXP
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/surrealist/version.rb
CHANGED
data/lib/surrealist.rb
CHANGED
@@ -1,31 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
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'
|
10
|
-
require 'surrealist/hash_utils'
|
11
|
-
require 'surrealist/instance_methods'
|
12
|
-
require 'surrealist/schema_definer'
|
13
|
-
require 'surrealist/string_utils'
|
14
|
-
require 'surrealist/helper'
|
15
|
-
require 'surrealist/type_helper'
|
16
|
-
require 'surrealist/value_assigner'
|
17
3
|
require 'json'
|
4
|
+
require_relative 'surrealist/any'
|
5
|
+
require_relative 'surrealist/bool'
|
6
|
+
require_relative 'surrealist/builder'
|
7
|
+
require_relative 'surrealist/carrier'
|
8
|
+
require_relative 'surrealist/class_methods'
|
9
|
+
require_relative 'surrealist/copier'
|
10
|
+
require_relative 'surrealist/exception_raiser'
|
11
|
+
require_relative 'surrealist/hash_utils'
|
12
|
+
require_relative 'surrealist/helper'
|
13
|
+
require_relative 'surrealist/instance_methods'
|
14
|
+
require_relative 'surrealist/schema_definer'
|
15
|
+
require_relative 'surrealist/serializer'
|
16
|
+
require_relative 'surrealist/string_utils'
|
17
|
+
require_relative 'surrealist/type_helper'
|
18
|
+
require_relative 'surrealist/value_assigner'
|
19
|
+
require_relative 'surrealist/vars_helper'
|
18
20
|
|
19
21
|
# Main module that provides the +json_schema+ class method and +surrealize+ instance method.
|
20
22
|
module Surrealist
|
21
23
|
# Default namespaces nesting level
|
22
24
|
DEFAULT_NESTING_LEVEL = 666
|
23
|
-
# Instance variable name that is set by SchemaDefiner
|
24
|
-
INSTANCE_VARIABLE = '@__surrealist_schema'.freeze
|
25
|
-
# Instance's parent instance variable name that is set by SchemaDefiner
|
26
|
-
PARENT_VARIABLE = '@__surrealist_schema_parent'.freeze
|
27
|
-
# Class variable name that is set by SchemaDefiner
|
28
|
-
CLASS_VARIABLE = '@@__surrealist_schema'.freeze
|
29
25
|
|
30
26
|
class << self
|
31
27
|
# @param [Class] base class to include/extend +Surrealist+.
|
@@ -35,15 +31,19 @@ module Surrealist
|
|
35
31
|
end
|
36
32
|
|
37
33
|
# Iterates over a collection of Surrealist Objects and
|
38
|
-
# maps surrealize to each
|
34
|
+
# maps surrealize to each object.
|
39
35
|
#
|
40
36
|
# @param [Object] collection of instances of a class that has +Surrealist+ included.
|
41
|
-
# @param [Boolean] camelize optional argument for converting hash to camelBack.
|
42
|
-
# @param [Boolean] include_root optional argument for having the root key of the resulting hash
|
37
|
+
# @param [Boolean] [optional] camelize optional argument for converting hash to camelBack.
|
38
|
+
# @param [Boolean] [optional] include_root optional argument for having the root key of the resulting hash
|
43
39
|
# as instance's class name.
|
44
|
-
# @param [
|
40
|
+
# @param [Boolean] [optional] include_namespaces optional argument for having root key as a nested hash of
|
41
|
+
# instance's namespaces. Animal::Cat.new.surrealize -> (animal: { cat: { weight: '3 kilos' } })
|
42
|
+
# @param [String] [optional] root optional argument for using a specified root key for the hash.
|
43
|
+
# @param [Integer] [optional] namespaces_nesting_level level of namespaces nesting.
|
44
|
+
# @param [Boolean] [optional] raw optional argument for specifying the expected output format.
|
45
45
|
#
|
46
|
-
# @return [
|
46
|
+
# @return [JSON | Hash] the Collection#map with elements being json-formatted string corresponding
|
47
47
|
# to the schema provided in the object's class. Values will be taken from the return values
|
48
48
|
# of appropriate methods from the object.
|
49
49
|
#
|
@@ -53,30 +53,38 @@ module Surrealist
|
|
53
53
|
# Surrealist.surrealize_collection(User.all)
|
54
54
|
# # => "[{\"name\":\"Nikita\",\"age\":23}, {\"name\":\"Alessandro\",\"age\":24}]"
|
55
55
|
# # For more examples see README
|
56
|
-
def surrealize_collection(collection,
|
56
|
+
def surrealize_collection(collection, **args)
|
57
57
|
raise Surrealist::ExceptionRaiser.raise_invalid_collection! unless collection.respond_to?(:each)
|
58
58
|
|
59
|
-
result = collection.map do |
|
60
|
-
|
61
|
-
record.build_schema(
|
62
|
-
camelize: camelize,
|
63
|
-
include_root: include_root,
|
64
|
-
include_namespaces: include_namespaces,
|
65
|
-
root: root,
|
66
|
-
namespaces_nesting_level: namespaces_nesting_level,
|
67
|
-
)
|
68
|
-
else
|
69
|
-
record
|
70
|
-
end
|
59
|
+
result = collection.map do |object|
|
60
|
+
Helper.surrealist?(object.class) ? __build_schema(object, args) : object
|
71
61
|
end
|
72
62
|
|
73
|
-
raw ? result : JSON.dump(result)
|
63
|
+
args[:raw] ? result : JSON.dump(result)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Dumps the object's methods corresponding to the schema
|
67
|
+
# provided in the object's class and type-checks the values.
|
68
|
+
#
|
69
|
+
# @param [Boolean] [optional] camelize optional argument for converting hash to camelBack.
|
70
|
+
# @param [Boolean] [optional] include_root optional argument for having the root key of the resulting hash
|
71
|
+
# as instance's class name.
|
72
|
+
# @param [Boolean] [optional] include_namespaces optional argument for having root key as a nested hash of
|
73
|
+
# instance's namespaces. Animal::Cat.new.surrealize -> (animal: { cat: { weight: '3 kilos' } })
|
74
|
+
# @param [String] [optional] root optional argument for using a specified root key for the hash
|
75
|
+
# @param [Integer] [optional] namespaces_nesting_level level of namespaces nesting.
|
76
|
+
#
|
77
|
+
# @return [String] a json-formatted string corresponding to the schema
|
78
|
+
# provided in the object's class. Values will be taken from the return values
|
79
|
+
# of appropriate methods from the object.
|
80
|
+
def surrealize(instance:, **args)
|
81
|
+
JSON.dump(build_schema(instance: instance, **args))
|
74
82
|
end
|
75
83
|
|
76
84
|
# Builds hash from schema provided in the object's class and type-checks the values.
|
77
85
|
#
|
78
86
|
# @param [Object] instance of a class that has +Surrealist+ included.
|
79
|
-
# @param [
|
87
|
+
# @param [Hash] args optional arguments
|
80
88
|
#
|
81
89
|
# @return [Hash] a hash corresponding to the schema
|
82
90
|
# provided in the object's class. Values will be taken from the return values
|
@@ -112,8 +120,9 @@ module Surrealist
|
|
112
120
|
# User.new.build_schema
|
113
121
|
# # => { name: 'Nikita', age: 23 }
|
114
122
|
# # For more examples see README
|
115
|
-
def build_schema(instance:,
|
116
|
-
|
123
|
+
def build_schema(instance:, **args)
|
124
|
+
carrier = Surrealist::Carrier.call(args)
|
125
|
+
schema = Surrealist::VarsHelper.find_schema(instance.class)
|
117
126
|
|
118
127
|
Surrealist::ExceptionRaiser.raise_unknown_schema!(instance) if schema.nil?
|
119
128
|
|
@@ -129,14 +138,21 @@ module Surrealist
|
|
129
138
|
|
130
139
|
private
|
131
140
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
141
|
+
# Checks if there is a serializer (< Surrealist::Serializer) defined for the object and delegates
|
142
|
+
# surrealization to it.
|
143
|
+
#
|
144
|
+
# @param [Object] object serializable object
|
145
|
+
# @param [Hash] args optional arguments passed to +surrealize_collection+
|
146
|
+
#
|
147
|
+
# @return [Hash] a hash corresponding to the schema
|
148
|
+
# provided in the object's class. Values will be taken from the return values
|
149
|
+
# of appropriate methods from the object.
|
150
|
+
def __build_schema(object, **args)
|
151
|
+
if (serializer = Surrealist::VarsHelper.find_serializer(object.class))
|
152
|
+
serializer.new(object, args[:context].to_h).build_schema(args)
|
153
|
+
else
|
154
|
+
build_schema(instance: object, **args)
|
155
|
+
end
|
140
156
|
end
|
141
157
|
end
|
142
158
|
end
|
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.4.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:
|
11
|
+
date: 2018-01-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -114,9 +114,11 @@ files:
|
|
114
114
|
- lib/surrealist/helper.rb
|
115
115
|
- lib/surrealist/instance_methods.rb
|
116
116
|
- lib/surrealist/schema_definer.rb
|
117
|
+
- lib/surrealist/serializer.rb
|
117
118
|
- lib/surrealist/string_utils.rb
|
118
119
|
- lib/surrealist/type_helper.rb
|
119
120
|
- lib/surrealist/value_assigner.rb
|
121
|
+
- lib/surrealist/vars_helper.rb
|
120
122
|
- lib/surrealist/version.rb
|
121
123
|
- surrealist-icon.png
|
122
124
|
- surrealist.gemspec
|
@@ -140,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
140
142
|
version: '0'
|
141
143
|
requirements: []
|
142
144
|
rubyforge_project:
|
143
|
-
rubygems_version: 2.7.
|
145
|
+
rubygems_version: 2.7.3
|
144
146
|
signing_key:
|
145
147
|
specification_version: 4
|
146
148
|
summary: A gem that provides DSL for serialization of plain old Ruby objects to JSON
|