surrealist 0.1.4 → 0.2.0

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