surrealist 0.1.4 → 0.2.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
  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