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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ddb587c43e017b7be30cbcddf5ae8ee32094cff322233dbd89ed32c6bd8d5f7f
4
- data.tar.gz: 9ac93b85f00189ff0937e056ac1475243e9364a78c70d5c570583028f4073f93
3
+ metadata.gz: 936bf35d7c1f0511db6d2f9458ade02330ec2246d444f9c0c7f9069ed7f24d73
4
+ data.tar.gz: 1b91381b04081fb2e4cd6af4167cd41907f982c9a1f713077e76d78ec51a82c4
5
5
  SHA512:
6
- metadata.gz: 6ad65631c97a6ab8b77d678fac704d8da83ab0d1f7ca1b263169a643ca0e4fe33aaec846bbecd1504ba28147c83fc878b60232acec75cbb11195d6310ebb0a5a
7
- data.tar.gz: 1c2a5350fd1d780dad01df6e5f60f01155c356f319f85e8f623bcb5e87b6bf75cdcc31b6604e309f1a5367d9cceb3279448c85eeea452a4818e27f55adbc5966
6
+ metadata.gz: d058ddf6a4314ee51c09fc2bd7866302971d9a8f9171430189d95df37871331883237519579c9044a4e720435bdf99702e134e9af992cddb4b5a241db528a92e
7
+ data.tar.gz: ccee664c6cadf85ec84e5584a8ac1c9490f43a5dfb7308ecf7d8d883daa5f4bbc6c6f1a2836c58f30677bb1ca6a5cfb1c5111f03db9ca6e0b9fade5420a11998
data/.rubocop.yml CHANGED
@@ -141,3 +141,6 @@ Style/TrailingCommaInArguments:
141
141
 
142
142
  Style/TrailingCommaInLiteral:
143
143
  EnforcedStyleForMultiline: comma
144
+
145
+ Style/MethodMissing:
146
+ Enabled: false
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: 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', '~> 3.0'
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
- * [Collection serialization](https://github.com/nesaulov/surrealist/issues/12)
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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require_relative '../lib/surrealist'
3
4
  require 'bundler/setup'
4
- require 'surrealist'
5
5
  require 'pry'
6
6
 
7
7
  Pry.start
@@ -5,8 +5,6 @@ source 'https://rubygems.org'
5
5
  group :development, :test do
6
6
  gem 'activerecord', '~> 4.2'
7
7
  gem 'coveralls', require: false
8
- gem 'data_mapper'
9
- gem 'dm-sqlite-adapter'
10
8
  gem 'dry-struct'
11
9
  gem 'dry-types'
12
10
  gem 'rom', '~> 3.0'
@@ -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: Schema.new(schema_key, schema_value),
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.
@@ -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(camelize:, include_root:, include_namespaces:, root:, namespaces_nesting_level:)
20
- new(camelize, include_root, include_namespaces, root, namespaces_nesting_level).sanitize!
23
+ def self.call(**args)
24
+ new(args).sanitize!
21
25
  end
22
26
 
23
- attr_reader :camelize, :include_root, :include_namespaces, :root, :namespaces_nesting_level
24
-
25
- def initialize(camelize, include_root, include_namespaces, root, namespaces_nesting_level)
26
- @camelize = camelize
27
- @include_root = include_root
28
- @include_namespaces = include_namespaces
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 [true, false].include?(value)
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.present?) || root.is_a?(Symbol)
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
- instance_variable_set('@__surrealist_schema_parent', klass)
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
@@ -2,7 +2,9 @@
2
2
 
3
3
  module Surrealist
4
4
  # A helper class for deep copying and wrapping hashes.
5
- class Copier
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 == {} ? hash[k] = sub_hash : inject_schema(v, sub_hash)
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
- class ExceptionRaiser
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, 'Class does not include Surrealist'
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, "Can't wrap schema in root key - class name was not passed"
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, "Can't serialize collection - must respond to :each"
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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Surrealist
4
4
  # A helper class for hashes transformations.
5
- class HashUtils
5
+ module HashUtils
6
6
  class << self
7
7
  # Converts hash's keys to camelBack keys.
8
8
  #
@@ -2,16 +2,14 @@
2
2
 
3
3
  module Surrealist
4
4
  # A generic helper.
5
- class Helper
6
- class << self
7
- # Determines if the class uses the Surrealist mixin.
8
- #
9
- # @param [Class] klass a class to be checked.
10
- #
11
- # @return [Boolean] if Surrealist is included in class.
12
- def surrealist?(klass)
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(camelize: false, include_root: false, include_namespaces: false, root: nil, namespaces_nesting_level: DEFAULT_NESTING_LEVEL) # rubocop:disable Metrics/LineLength
52
- JSON.dump(
53
- build_schema(
54
- camelize: camelize,
55
- include_root: include_root,
56
- include_namespaces: include_namespaces,
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(camelize: false, include_root: false, include_namespaces: false, root: nil, namespaces_nesting_level: DEFAULT_NESTING_LEVEL) # rubocop:disable Metrics/LineLength
65
- carrier = Surrealist::Carrier.call(
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
- class SchemaDefiner
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, 'Schema should be defined as a hash' unless hash.is_a?(Hash)
17
+ raise Surrealist::InvalidSchemaError, SCHEMA_TYPE_ERROR unless hash.is_a?(Hash)
17
18
 
18
- if klass.name =~ /ROM::Struct/
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
- class StringUtils
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(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
15
- .gsub(/([a-z\d])([A-Z])/, '\1_\2')
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('_', 2)
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('::').last)
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
- arr = klass.split('::')
66
- arr.last(nesting_level).reverse.inject({}) do |a, n|
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
- class TypeHelper
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
- [true, false].include?(value)
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
- class ValueAssigner
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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Surrealist
4
4
  # Defines the version of Surrealist
5
- VERSION = '0.3.0'.freeze
5
+ VERSION = '0.4.0'.freeze
6
6
  end
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 record.
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 [String] root optional argument for using a specified root key for the resulting hash
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 [Object] the Collection#map with elements being json-formatted string corresponding
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, camelize: false, include_root: false, include_namespaces: false, root: nil, namespaces_nesting_level: DEFAULT_NESTING_LEVEL, raw: false) # rubocop:disable Metrics/LineLength
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 |record|
60
- if Helper.surrealist?(record.class)
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 [Object] carrier instance of Carrier class that carries arguments passed to +surrealize+
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:, carrier:)
116
- schema = find_schema(instance)
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
- def find_schema(instance)
133
- delegatee = instance.class.instance_variable_get(PARENT_VARIABLE)
134
- maybe_schema = (delegatee || instance.class).instance_variable_get(INSTANCE_VARIABLE)
135
- maybe_schema || (instance.class.class_variable_get(CLASS_VARIABLE) if klass_var_defined?(instance))
136
- end
137
-
138
- def klass_var_defined?(instance)
139
- instance.class.class_variable_defined?(CLASS_VARIABLE)
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.3.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: 2017-11-22 00:00:00.000000000 Z
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.2
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