diff --git a/README.md b/README.md index 3cdfd7c..83ef23a 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ to serialize nested objects and structures. [Introductory blogpost.](https://med * [Include root](#include-root) * [Include namespaces](#include-namespaces) * [Collection Surrealization](#collection-surrealization) + * [Root](#root) * [Bool and Any](#bool-and-any) * [Type errors](#type-errors) * [Undefined methods in schema](#undefined-methods-in-schema) @@ -354,6 +355,35 @@ features (like associations, inheritance etc) are supported and covered. Other O issues as well, tests are in progress. All optional arguments (`camelize`, `include_root` etc) are also supported. Guides on where to use `#surrealize_collection` vs `#surrealize` for all ORMs are coming. +### Root +If you want to wrap the resulting JSON into a specified root key, you can pass optional `root` argument +to `#surrealize` or `#build_schema`. The `root` argument will be stripped of whitespaces. +``` ruby +class Cat + include Surrealist + + json_schema do + { weight: String } + end + + def weight + '3 kilos' + end +end + +Cat.new.surrealize(root: :kitten) +# => '{ "kitten": { "weight": "3 kilos" } }' +Cat.new.surrealize(root: ' kitten ') +# => '{ "kitten": { "weight": "3 kilos" } }' +``` +This overrides the `include_root` and `include_namespaces` arguments. +``` ruby +Animal::Cat.new.surrealize(include_root: true, root: :kitten) +# => '{ "kitten": { "weight": "3 kilos" } }' +Animal::Cat.new.surrealize(include_namespaces: true, root: 'kitten') +# => '{ "kitten": { "weight": "3 kilos" } }' +``` + ### Bool and Any If you have a parameter that is of boolean type, or if you don't care about the type, you can use `Bool` and `Any` respectively. diff --git a/lib/surrealist.rb b/lib/surrealist.rb index 2e9360b..5128d7f 100644 --- a/lib/surrealist.rb +++ b/lib/surrealist.rb @@ -33,6 +33,7 @@ def included(base) # @param [Boolean] camelize optional argument for converting hash to camelBack. # @param [Boolean] include_root optional argument for having the root key of the resulting hash # as instance's class name. + # @param [String] root optional argument for using a specified root key for the resulting hash # # @return [Object] the Collection#map with elements being json-formatted string corresponding # to the schema provided in the object's class. Values will be taken from the return values @@ -44,7 +45,7 @@ def included(base) # Surrealist.surrealize_collection(User.all) # # => "[{\"name\":\"Nikita\",\"age\":23}, {\"name\":\"Alessandro\",\"age\":24}]" # # For more examples see README - def surrealize_collection(collection, camelize: false, include_root: false, include_namespaces: false, namespaces_nesting_level: DEFAULT_NESTING_LEVEL) # rubocop:disable Metrics/LineLength + def surrealize_collection(collection, camelize: false, include_root: false, include_namespaces: false, root: nil, namespaces_nesting_level: DEFAULT_NESTING_LEVEL) # rubocop:disable Metrics/LineLength unless collection.respond_to?(:each) raise Surrealist::ExceptionRaiser.raise_invalid_collection! end @@ -54,6 +55,7 @@ def surrealize_collection(collection, camelize: false, include_root: false, incl camelize: camelize, include_root: include_root, include_namespaces: include_namespaces, + root: root, namespaces_nesting_level: namespaces_nesting_level, ) end) diff --git a/lib/surrealist/carrier.rb b/lib/surrealist/carrier.rb index 1329086..6d0adfa 100644 --- a/lib/surrealist/carrier.rb +++ b/lib/surrealist/carrier.rb @@ -10,21 +10,23 @@ class Carrier # as instance's class name. # @param [Boolean] include_namespaces optional argument for having root key as a nested hash of # instance's namespaces. Animal::Cat.new.surrealize -> (animal: { cat: { weight: '3 kilos' } }) + # @param [String] root optional argument for using a specified root key for the resulting hash # @param [Integer] namespaces_nesting_level level of namespaces nesting. # # @raise ArgumentError if types of arguments are wrong. # # @return [Carrier] self if type checks were passed. - def self.call(camelize:, include_root:, include_namespaces:, namespaces_nesting_level:) - new(camelize, include_root, include_namespaces, namespaces_nesting_level).sanitize! + def self.call(camelize:, include_root:, include_namespaces:, root:, namespaces_nesting_level:) + new(camelize, include_root, include_namespaces, root, namespaces_nesting_level).sanitize! end - attr_reader :camelize, :include_root, :include_namespaces, :namespaces_nesting_level + attr_reader :camelize, :include_root, :include_namespaces, :root, :namespaces_nesting_level - def initialize(camelize, include_root, include_namespaces, namespaces_nesting_level) + def initialize(camelize, include_root, include_namespaces, root, namespaces_nesting_level) @camelize = camelize @include_root = include_root @include_namespaces = include_namespaces + @root = root @namespaces_nesting_level = namespaces_nesting_level end @@ -34,6 +36,8 @@ def initialize(camelize, include_root, include_namespaces, namespaces_nesting_le def sanitize! check_booleans! check_namespaces_nesting! + check_root! + strip_root! self end @@ -65,5 +69,18 @@ def check_namespaces_nesting! Surrealist::ExceptionRaiser.raise_invalid_nesting!(namespaces_nesting_level) end end + + # Checks if root is not nil, a non-empty string, or symbol + # @raise ArgumentError + def check_root! + unless root.nil? || (root.is_a?(String) && root.present?) || root.is_a?(Symbol) + Surrealist::ExceptionRaiser.raise_invalid_root!(root) + end + end + + # Strips root of empty whitespaces + def strip_root! + root.is_a?(String) && @root = root.strip + end end end diff --git a/lib/surrealist/copier.rb b/lib/surrealist/copier.rb index 32bd689..267fe01 100644 --- a/lib/surrealist/copier.rb +++ b/lib/surrealist/copier.rb @@ -14,19 +14,36 @@ class << self def deep_copy(hash:, klass: false, carrier:) namespaces_condition = carrier.include_namespaces || carrier.namespaces_nesting_level != DEFAULT_NESTING_LEVEL # rubocop:disable Metrics/LineLength - return copy_hash(hash) unless carrier.include_root || namespaces_condition + if !klass && (carrier.include_root || namespaces_condition) + Surrealist::ExceptionRaiser.raise_unknown_root! + end + + copied_and_possibly_wrapped_hash(hash, klass, carrier, namespaces_condition) + end - Surrealist::ExceptionRaiser.raise_unknown_root! unless klass + private - if namespaces_condition + # Deeply copies the schema hash and wraps it if there is a need to. + # + # @param [Object] hash object to be copied. + # @param [String] klass instance's class name. + # @param [Object] carrier instance of Carrier class that carries arguments passed to +surrealize+ + # @param [Bool] namespaces_condition whether to wrap into namespace. + # + # @return [Hash] deeply copied hash, possibly wrapped. + def copied_and_possibly_wrapped_hash(hash, klass, carrier, namespaces_condition) + if carrier.root + wrap_schema_into_root(schema: hash, carrier: carrier, root: carrier.root.to_s) + elsif namespaces_condition wrap_schema_into_namespace(schema: hash, klass: klass, carrier: carrier) elsif carrier.include_root - wrap_schema_into_root(schema: hash, klass: klass, carrier: carrier) + actual_class = Surrealist::StringUtils.extract_class(klass) + wrap_schema_into_root(schema: hash, carrier: carrier, root: actual_class) + else + copy_hash(hash) end end - private - # Goes through the hash recursively and deeply copies it. # # @param [Hash] hash the hash to be copied. @@ -42,16 +59,15 @@ def copy_hash(hash, wrapper: {}) # Wraps schema into a root key if `include_root` is passed to Surrealist. # # @param [Hash] schema schema hash. - # @param [String] klass name of the class where schema is defined. # @param [Object] carrier instance of Carrier class that carries arguments passed to +surrealize+ + # @param [String] root what the schema will be wrapped into # # @return [Hash] a hash with schema wrapped inside a root key. - def wrap_schema_into_root(schema:, klass:, carrier:) - actual_class = Surrealist::StringUtils.extract_class(klass) + def wrap_schema_into_root(schema:, carrier:, root:) root_key = if carrier.camelize - Surrealist::StringUtils.camelize(actual_class, false).to_sym + Surrealist::StringUtils.camelize(root, false).to_sym else - Surrealist::StringUtils.underscore(actual_class).to_sym + Surrealist::StringUtils.underscore(root).to_sym end result = Hash[root_key => {}] copy_hash(schema, wrapper: result[root_key]) diff --git a/lib/surrealist/exception_raiser.rb b/lib/surrealist/exception_raiser.rb index ec6c936..73e9efe 100644 --- a/lib/surrealist/exception_raiser.rb +++ b/lib/surrealist/exception_raiser.rb @@ -67,6 +67,14 @@ def raise_invalid_nesting!(value) raise ArgumentError, "Expected `namespaces_nesting_level` to be a positive integer, got: #{value}" end + + # Raises ArgumentError if root is not nil, a non-empty stringk or symbol. + # + # @raise ArgumentError + def raise_invalid_root!(value) + raise ArgumentError, + "Expected `root` to be nil, a non-empty string, or symbol, got: #{value}" + end end end end diff --git a/lib/surrealist/instance_methods.rb b/lib/surrealist/instance_methods.rb index fa2493d..577b4f6 100644 --- a/lib/surrealist/instance_methods.rb +++ b/lib/surrealist/instance_methods.rb @@ -11,6 +11,7 @@ module InstanceMethods # as instance's class name. # @param [Boolean] include_namespaces optional argument for having root key as a nested hash of # instance's namespaces. Animal::Cat.new.surrealize -> (animal: { cat: { weight: '3 kilos' } }) + # @param [String] root optional argument for using a specified root key for the hash # @param [Integer] namespaces_nesting_level level of namespaces nesting. # # @return [String] a json-formatted string corresponding to the schema @@ -47,23 +48,25 @@ module InstanceMethods # User.new.surrealize # # => "{\"name\":\"Nikita\",\"age\":23}" # # For more examples see README - def surrealize(camelize: false, include_root: false, include_namespaces: false, namespaces_nesting_level: DEFAULT_NESTING_LEVEL) # rubocop:disable Metrics/LineLength + def surrealize(camelize: false, include_root: false, include_namespaces: false, root: nil, namespaces_nesting_level: DEFAULT_NESTING_LEVEL) # rubocop:disable Metrics/LineLength JSON.dump( build_schema( camelize: camelize, include_root: include_root, include_namespaces: include_namespaces, + root: root, namespaces_nesting_level: namespaces_nesting_level, ), ) end # Invokes +Surrealist+'s class method +build_schema+ - def build_schema(camelize: false, include_root: false, include_namespaces: false, namespaces_nesting_level: DEFAULT_NESTING_LEVEL) # rubocop:disable Metrics/LineLength + def build_schema(camelize: false, include_root: false, include_namespaces: false, root: nil, namespaces_nesting_level: DEFAULT_NESTING_LEVEL) # rubocop:disable Metrics/LineLength carrier = Surrealist::Carrier.call( camelize: camelize, include_namespaces: include_namespaces, include_root: include_root, + root: root, namespaces_nesting_level: namespaces_nesting_level, ) diff --git a/spec/carrier_spec.rb b/spec/carrier_spec.rb index f928e23..c8c1149 100644 --- a/spec/carrier_spec.rb +++ b/spec/carrier_spec.rb @@ -15,7 +15,7 @@ context 'valid arguments' do VALID_PARAMS.each do |hash| result = described_class.call(hash) - %i[camelize include_namespaces include_root namespaces_nesting_level].each do |method| + %i[camelize include_namespaces include_root namespaces_nesting_level root].each do |method| it "stores #{method} in Carrier and returns self for #{hash}" do expect(result).to be_a(Surrealist::Carrier) expect(result.send(method)).to eq(hash[method]) diff --git a/spec/carriers/params.rb b/spec/carriers/params.rb index bc733b3..540f6d0 100644 --- a/spec/carriers/params.rb +++ b/spec/carriers/params.rb @@ -1,24 +1,51 @@ VALID_PARAMS = [ - { camelize: true, include_namespaces: true, include_root: true, namespaces_nesting_level: 3 }, - { camelize: false, include_namespaces: true, include_root: true, namespaces_nesting_level: 3 }, - { camelize: false, include_namespaces: false, include_root: true, namespaces_nesting_level: 3 }, - { camelize: false, include_namespaces: false, include_root: false, namespaces_nesting_level: 3 }, - { camelize: true, include_namespaces: false, include_root: false, namespaces_nesting_level: 3 }, - { camelize: true, include_namespaces: true, include_root: false, namespaces_nesting_level: 3 }, - { camelize: true, include_namespaces: false, include_root: true, namespaces_nesting_level: 3 }, - { camelize: true, include_namespaces: false, include_root: true, namespaces_nesting_level: 435 }, - { camelize: true, include_namespaces: false, include_root: true, namespaces_nesting_level: 666 }, + { camelize: true, include_namespaces: true, include_root: true, + root: 'root', namespaces_nesting_level: 3 }, + { camelize: false, include_namespaces: true, include_root: true, + root: :root, namespaces_nesting_level: 3 }, + { camelize: false, include_namespaces: false, include_root: true, + root: nil, namespaces_nesting_level: 3 }, + { camelize: false, include_namespaces: false, include_root: false, + root: :root, namespaces_nesting_level: 3 }, + { camelize: true, include_namespaces: false, include_root: false, + root: 'root', namespaces_nesting_level: 3 }, + { camelize: true, include_namespaces: true, include_root: false, + root: nil, namespaces_nesting_level: 3 }, + { camelize: true, include_namespaces: false, include_root: true, + root: 'root', namespaces_nesting_level: 3 }, + { camelize: true, include_namespaces: false, include_root: true, + root: :root, namespaces_nesting_level: 435 }, + { camelize: true, include_namespaces: false, include_root: true, + root: nil, namespaces_nesting_level: 666 }, ].freeze INVALID_PARAMS = [ - { camelize: 'NO', include_namespaces: false, include_root: true, namespaces_nesting_level: 3 }, - { camelize: true, include_namespaces: 'false', include_root: true, namespaces_nesting_level: 3 }, - { camelize: true, include_namespaces: false, include_root: true, namespaces_nesting_level: 0 }, - { camelize: true, include_namespaces: false, include_root: false, namespaces_nesting_level: -3 }, - { camelize: true, include_namespaces: false, include_root: 'yep', namespaces_nesting_level: 3 }, - { camelize: 'NO', include_namespaces: false, include_root: true, namespaces_nesting_level: '3' }, - { camelize: 'NO', include_namespaces: false, include_root: true, namespaces_nesting_level: 3.14 }, - { camelize: Integer, include_namespaces: false, include_root: true, namespaces_nesting_level: 3 }, - { camelize: 'NO', include_namespaces: 'no', include_root: true, namespaces_nesting_level: '3.4' }, - { camelize: 'f', include_namespaces: false, include_root: 't', namespaces_nesting_level: true }, + { camelize: 'NO', include_namespaces: false, include_root: true, + root: 'root', namespaces_nesting_level: 3 }, + { camelize: true, include_namespaces: 'false', include_root: true, + root: :root, namespaces_nesting_level: 3 }, + { camelize: true, include_namespaces: false, include_root: true, + root: 'root', namespaces_nesting_level: 0 }, + { camelize: true, include_namespaces: false, include_root: false, + root: :root, namespaces_nesting_level: -3 }, + { camelize: true, include_namespaces: false, include_root: 'yep', + root: 'root', namespaces_nesting_level: 3 }, + { camelize: 'NO', include_namespaces: false, include_root: true, + root: :root, namespaces_nesting_level: '3' }, + { camelize: 'NO', include_namespaces: false, include_root: true, + root: 'root', namespaces_nesting_level: 3.14 }, + { camelize: Integer, include_namespaces: false, include_root: true, + root: :root, namespaces_nesting_level: 3 }, + { camelize: 'NO', include_namespaces: 'no', include_root: true, + root: 'root', namespaces_nesting_level: '3.4' }, + { camelize: 'f', include_namespaces: false, include_root: 't', + root: :root, namespaces_nesting_level: true }, + { camelize: true, include_namespaces: false, include_root: true, + root: '', namespaces_nesting_level: 666 }, + { camelize: true, include_namespaces: false, include_root: true, + root: 3, namespaces_nesting_level: 666 }, + { camelize: true, include_namespaces: false, include_root: true, + root: -3, namespaces_nesting_level: 666 }, + { camelize: true, include_namespaces: false, include_root: true, + root: 3.14, namespaces_nesting_level: 666 }, ].freeze diff --git a/spec/copier_spec.rb b/spec/copier_spec.rb index 7c0404e..be193d9 100644 --- a/spec/copier_spec.rb +++ b/spec/copier_spec.rb @@ -3,13 +3,14 @@ require_relative '../lib/surrealist' class NullCarrier - attr_reader :camelize, :include_root, :include_namespaces, :namespaces_nesting_level + attr_reader :camelize, :include_root, :include_namespaces, :namespaces_nesting_level, :root def initialize(camelize = false) @camelize = camelize @include_root = false @include_namespaces = false @namespaces_nesting_level = Surrealist::DEFAULT_NESTING_LEVEL + @root = nil end end @@ -46,28 +47,44 @@ def initialize(camelize = false) let(:error) { "Can't wrap schema in root key - class name was not passed" } args_with_root_and_camelize = [ - { camelize: true, include_namespaces: true, include_root: true, namespaces_nesting_level: 3 }, - { camelize: true, include_namespaces: true, include_root: true, namespaces_nesting_level: 666 }, - { camelize: true, include_namespaces: false, include_root: true, namespaces_nesting_level: 3 }, - { camelize: true, include_namespaces: false, include_root: true, namespaces_nesting_level: 666 }, - { camelize: true, include_namespaces: true, include_root: false, namespaces_nesting_level: 3 }, - { camelize: true, include_namespaces: true, include_root: false, namespaces_nesting_level: 666 }, - { camelize: true, include_namespaces: false, include_root: false, namespaces_nesting_level: 3 }, + { camelize: true, include_namespaces: true, include_root: true, + root: nil, namespaces_nesting_level: 3 }, + { camelize: true, include_namespaces: true, include_root: true, + root: nil, namespaces_nesting_level: 666 }, + { camelize: true, include_namespaces: false, include_root: true, + root: nil, namespaces_nesting_level: 3 }, + { camelize: true, include_namespaces: false, include_root: true, + root: nil, namespaces_nesting_level: 666 }, + { camelize: true, include_namespaces: true, include_root: false, + root: nil, namespaces_nesting_level: 3 }, + { camelize: true, include_namespaces: true, include_root: false, + root: nil, namespaces_nesting_level: 666 }, + { camelize: true, include_namespaces: false, include_root: false, + root: nil, namespaces_nesting_level: 3 }, ] args_with_root_and_without_camelize = [ - { camelize: false, include_namespaces: true, include_root: true, namespaces_nesting_level: 3 }, - { camelize: false, include_namespaces: true, include_root: true, namespaces_nesting_level: 666 }, - { camelize: false, include_namespaces: false, include_root: true, namespaces_nesting_level: 3 }, - { camelize: false, include_namespaces: false, include_root: true, namespaces_nesting_level: 666 }, - { camelize: false, include_namespaces: true, include_root: false, namespaces_nesting_level: 3 }, - { camelize: false, include_namespaces: true, include_root: false, namespaces_nesting_level: 666 }, - { camelize: false, include_namespaces: false, include_root: false, namespaces_nesting_level: 3 }, + { camelize: false, include_namespaces: true, include_root: true, + root: nil, namespaces_nesting_level: 3 }, + { camelize: false, include_namespaces: true, include_root: true, + root: nil, namespaces_nesting_level: 666 }, + { camelize: false, include_namespaces: false, include_root: true, + root: nil, namespaces_nesting_level: 3 }, + { camelize: false, include_namespaces: false, include_root: true, + root: nil, namespaces_nesting_level: 666 }, + { camelize: false, include_namespaces: true, include_root: false, + root: nil, namespaces_nesting_level: 3 }, + { camelize: false, include_namespaces: true, include_root: false, + root: nil, namespaces_nesting_level: 666 }, + { camelize: false, include_namespaces: false, include_root: false, + root: nil, namespaces_nesting_level: 3 }, ] args_without_root = [ - { camelize: false, include_namespaces: false, include_root: false, namespaces_nesting_level: 666 }, - { camelize: true, include_namespaces: false, include_root: false, namespaces_nesting_level: 666 }, + { camelize: false, include_namespaces: false, include_root: false, + root: nil, namespaces_nesting_level: 666 }, + { camelize: true, include_namespaces: false, include_root: false, + root: nil, namespaces_nesting_level: 666 }, ] context 'with `camelize: true`' do diff --git a/spec/include_root_spec.rb b/spec/include_root_spec.rb index 76e6f2d..e1dccbc 100644 --- a/spec/include_root_spec.rb +++ b/spec/include_root_spec.rb @@ -37,16 +37,7 @@ def weight end def cat_food - CatFood.new(amount: 3, brand: 'Whiskas') - end -end - -class CatFood - attr_reader :amount, :brand - - def initialize(amount:, brand:) - @amount = amount - @brand = brand + Struct.new(:amount, :brand).new(3, 'Whiskas') end end diff --git a/spec/root_spec.rb b/spec/root_spec.rb new file mode 100644 index 0000000..9320d32 --- /dev/null +++ b/spec/root_spec.rb @@ -0,0 +1,337 @@ +# frozen_string_literal: true + +require_relative '../lib/surrealist' +require 'dry-types' + +module Types + include Dry::Types.module +end + +class Cat + include Surrealist + + json_schema do + { cat_weight: String } + end + + def cat_weight + '3 kilos' + end +end + +class SeriousCat + include Surrealist + + json_schema do + { + weight: Types::String, + cat_food: { + amount: Types::Strict::Int, + brand: Types::Strict::String, + }, + } + end + + def weight + '3 kilos' + end + + def cat_food + Struct.new(:amount, :brand).new(3, 'Whiskas') + end +end + +class Animal + class Dog + include Surrealist + + json_schema do + { breed: String } + end + + def breed + 'Collie' + end + end +end + +module Instrument + class Guitar + include Surrealist + + json_schema do + { brand_name: Types::Strict::String } + end + + def brand_name + 'Fender' + end + end +end + +class Code + class Language + class Ruby + include Surrealist + + json_schema do + { age: Types::String } + end + + def age + '22 years' + end + end + end +end + +RSpec.describe Surrealist do + describe 'root option' do + context 'nil root' do + let(:instance) { Cat.new } + + it 'builds schema' do + expect(instance.build_schema(root: nil)).to eq(cat_weight: '3 kilos') + end + + it 'surrealizes' do + expect(JSON.parse(instance.surrealize(root: nil))) + .to eq('cat_weight' => '3 kilos') + end + + it 'camelizes' do + expect(instance.build_schema(root: nil, camelize: true)) + .to eq(catWeight: '3 kilos') + + expect(JSON.parse(instance.surrealize(root: nil, camelize: true))) + .to eq('catWeight' => '3 kilos') + end + end + + context 'simple example with extra whitespaces' do + let(:instance) { Cat.new } + + it 'builds schema' do + expect(instance.build_schema(root: ' kitten ')).to eq(kitten: { cat_weight: '3 kilos' }) + end + + it 'surrealizes' do + expect(JSON.parse(instance.surrealize(root: ' kitten '))) + .to eq('kitten' => { 'cat_weight' => '3 kilos' }) + end + + it 'camelizes' do + expect(instance.build_schema(root: ' kitten ', camelize: true)) + .to eq(kitten: { catWeight: '3 kilos' }) + + expect(JSON.parse(instance.surrealize(root: ' kitten ', camelize: true))) + .to eq('kitten' => { 'catWeight' => '3 kilos' }) + end + + it 'overrides include_root' do + expect(JSON.parse(instance.surrealize(root: ' kitten ', include_root: true))) + .to eq('kitten' => { 'cat_weight' => '3 kilos' }) + end + + it 'overrides include_namespaces' do + expect(JSON.parse(instance.surrealize(root: ' kitten ', include_namespaces: true))) + .to eq('kitten' => { 'cat_weight' => '3 kilos' }) + end + end + + context 'simple example' do + let(:instance) { Cat.new } + + it 'builds schema' do + expect(instance.build_schema(root: 'kitten')).to eq(kitten: { cat_weight: '3 kilos' }) + end + + it 'surrealizes' do + expect(JSON.parse(instance.surrealize(root: 'kitten'))) + .to eq('kitten' => { 'cat_weight' => '3 kilos' }) + end + + it 'camelizes' do + expect(instance.build_schema(root: 'kitten', camelize: true)) + .to eq(kitten: { catWeight: '3 kilos' }) + + expect(JSON.parse(instance.surrealize(root: 'kitten', camelize: true))) + .to eq('kitten' => { 'catWeight' => '3 kilos' }) + end + + it 'overrides include_root' do + expect(JSON.parse(instance.surrealize(root: 'kitten', include_root: true))) + .to eq('kitten' => { 'cat_weight' => '3 kilos' }) + end + + it 'overrides include_namespaces' do + expect(JSON.parse(instance.surrealize(root: 'kitten', include_namespaces: true))) + .to eq('kitten' => { 'cat_weight' => '3 kilos' }) + end + end + + context 'simple example using a symbol' do + let(:instance) { Cat.new } + + it 'builds schema' do + expect(instance.build_schema(root: :kitten)).to eq(kitten: { cat_weight: '3 kilos' }) + end + + it 'surrealizes' do + expect(JSON.parse(instance.surrealize(root: :kitten))) + .to eq('kitten' => { 'cat_weight' => '3 kilos' }) + end + + it 'camelizes' do + expect(instance.build_schema(root: :kitten, camelize: true)) + .to eq(kitten: { catWeight: '3 kilos' }) + + expect(JSON.parse(instance.surrealize(root: :kitten, camelize: true))) + .to eq('kitten' => { 'catWeight' => '3 kilos' }) + end + + it 'overrides include_root' do + expect(JSON.parse(instance.surrealize(root: :kitten, include_root: true))) + .to eq('kitten' => { 'cat_weight' => '3 kilos' }) + end + + it 'overrides include_namespaces' do + expect(JSON.parse(instance.surrealize(root: :kitten, include_namespaces: true))) + .to eq('kitten' => { 'cat_weight' => '3 kilos' }) + end + end + + context 'with nested objects' do + let(:instance) { SeriousCat.new } + + it 'builds schema' do + expect(instance.build_schema(root: 'serious_kitten')) + .to eq(serious_kitten: { weight: '3 kilos', cat_food: { amount: 3, brand: 'Whiskas' } }) + end + + it 'surrealizes' do + expect(JSON.parse(instance.surrealize(root: 'serious_kitten'))) + .to eq('serious_kitten' => { 'weight' => '3 kilos', + 'cat_food' => { 'amount' => 3, 'brand' => 'Whiskas' } }) + end + + it 'camelizes' do + expect(instance.build_schema(root: 'serious_kitten', camelize: true)) + .to eq(seriousKitten: { weight: '3 kilos', catFood: { amount: 3, brand: 'Whiskas' } }) + + expect(JSON.parse(instance.surrealize(root: 'serious_kitten', camelize: true))) + .to eq('seriousKitten' => { 'weight' => '3 kilos', + 'catFood' => { 'amount' => 3, 'brand' => 'Whiskas' } }) + end + + it 'overrides include_root' do + expect(JSON.parse(instance.surrealize(root: 'serious_kitten', include_root: true))) + .to eq('serious_kitten' => { 'weight' => '3 kilos', + 'cat_food' => { 'amount' => 3, + 'brand' => 'Whiskas' } }) + end + + it 'overrides include_namespaces' do + expect(JSON.parse(instance.surrealize(root: 'serious_kitten', include_namespaces: true))) + .to eq('serious_kitten' => { 'weight' => '3 kilos', + 'cat_food' => { 'amount' => 3, + 'brand' => 'Whiskas' } }) + end + end + + context 'with nested classes' do + let(:instance) { Animal::Dog.new } + + it 'builds schema' do + expect(instance.build_schema(root: 'new_dog')).to eq(new_dog: { breed: 'Collie' }) + end + + it 'surrealizes' do + expect(JSON.parse(instance.surrealize(root: 'new_dog'))) + .to eq('new_dog' => { 'breed' => 'Collie' }) + end + + it 'camelizes' do + expect(instance.build_schema(root: 'newDog', camelize: true)) + .to eq(newDog: { breed: 'Collie' }) + + expect(JSON.parse(instance.surrealize(root: 'new_dog', camelize: true))) + .to eq('newDog' => { 'breed' => 'Collie' }) + end + + it 'overrides include_root' do + expect(JSON.parse(instance.surrealize(root: 'new_dog', include_root: true))) + .to eq('new_dog' => { 'breed' => 'Collie' }) + end + + it 'overrides include_namespaces' do + expect(JSON.parse(instance.surrealize(root: 'new_dog', include_namespaces: true))) + .to eq('new_dog' => { 'breed' => 'Collie' }) + end + end + + context 'Module::Class' do + let(:instance) { Instrument::Guitar.new } + + it 'builds schema' do + expect(instance.build_schema(root: 'new_guitar')) + .to eq(new_guitar: { brand_name: 'Fender' }) + end + + it 'surrealizes' do + expect(JSON.parse(instance.surrealize(root: 'new_guitar'))) + .to eq('new_guitar' => { 'brand_name' => 'Fender' }) + end + + it 'camelizes' do + expect(instance.build_schema(root: 'new_guitar', camelize: true)) + .to eq(newGuitar: { brandName: 'Fender' }) + + expect(JSON.parse(instance.surrealize(root: 'new_guitar', camelize: true))) + .to eq('newGuitar' => { 'brandName' => 'Fender' }) + end + + it 'overrides include_root' do + expect(JSON.parse(instance.surrealize(root: 'new_guitar', include_root: true))) + .to eq('new_guitar' => { 'brand_name' => 'Fender' }) + end + + it 'overrides include_namespaces' do + expect(JSON.parse(instance.surrealize(root: 'new_guitar', include_namespaces: true))) + .to eq('new_guitar' => { 'brand_name' => 'Fender' }) + end + end + + context 'triple nesting' do + let(:instance) { Code::Language::Ruby.new } + + it 'builds schema' do + expect(instance.build_schema(root: 'new_ruby')) + .to eq(new_ruby: { age: '22 years' }) + end + + it 'surrealizes' do + expect(JSON.parse(instance.surrealize(root: 'new_ruby'))) + .to eq('new_ruby' => { 'age' => '22 years' }) + end + + it 'camelizes' do + expect(instance.build_schema(root: 'new_ruby', camelize: true)) + .to eq(newRuby: { age: '22 years' }) + + expect(JSON.parse(instance.surrealize(root: 'new_ruby', camelize: true))) + .to eq('newRuby' => { 'age' => '22 years' }) + end + + it 'overrides include_root' do + expect(JSON.parse(instance.surrealize(root: 'new_ruby', include_root: true))) + .to eq('new_ruby' => { 'age' => '22 years' }) + end + + it 'overrides include_namespaces' do + expect(JSON.parse(instance.surrealize(root: 'new_ruby', include_namespaces: true))) + .to eq('new_ruby' => { 'age' => '22 years' }) + end + end + end +end