surrealist 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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