json_model_rb 0.1.1

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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +0 -0
  4. data/lib/json_model/config/options.rb +29 -0
  5. data/lib/json_model/config.rb +43 -0
  6. data/lib/json_model/errors/error.rb +7 -0
  7. data/lib/json_model/errors/invalid_ref_mode_error.rb +12 -0
  8. data/lib/json_model/errors/unknown_attribute_error.rb +13 -0
  9. data/lib/json_model/errors.rb +5 -0
  10. data/lib/json_model/properties.rb +66 -0
  11. data/lib/json_model/property.rb +54 -0
  12. data/lib/json_model/ref_mode.rb +9 -0
  13. data/lib/json_model/schema.rb +111 -0
  14. data/lib/json_model/schema_meta.rb +60 -0
  15. data/lib/json_model/type_spec/array.rb +62 -0
  16. data/lib/json_model/type_spec/composition/all_of.rb +15 -0
  17. data/lib/json_model/type_spec/composition/any_of.rb +15 -0
  18. data/lib/json_model/type_spec/composition/one_of.rb +15 -0
  19. data/lib/json_model/type_spec/composition.rb +32 -0
  20. data/lib/json_model/type_spec/enum.rb +33 -0
  21. data/lib/json_model/type_spec/object.rb +24 -0
  22. data/lib/json_model/type_spec/primitive/boolean.rb +13 -0
  23. data/lib/json_model/type_spec/primitive/integer.rb +21 -0
  24. data/lib/json_model/type_spec/primitive/null.rb +13 -0
  25. data/lib/json_model/type_spec/primitive/number.rb +14 -0
  26. data/lib/json_model/type_spec/primitive/numeric.rb +83 -0
  27. data/lib/json_model/type_spec/primitive/string.rb +150 -0
  28. data/lib/json_model/type_spec/primitive.rb +26 -0
  29. data/lib/json_model/type_spec.rb +67 -0
  30. data/lib/json_model/types/all_of.rb +26 -0
  31. data/lib/json_model/types/any_of.rb +26 -0
  32. data/lib/json_model/types/array.rb +26 -0
  33. data/lib/json_model/types/boolean.rb +7 -0
  34. data/lib/json_model/types/enum.rb +23 -0
  35. data/lib/json_model/types/one_of.rb +26 -0
  36. data/lib/json_model/types.rb +8 -0
  37. data/lib/json_model/version.rb +5 -0
  38. data/lib/json_model.rb +31 -0
  39. data/spec/config_spec.rb +106 -0
  40. data/spec/json_model_spec.rb +7 -0
  41. data/spec/properties_spec.rb +76 -0
  42. data/spec/property_spec.rb +86 -0
  43. data/spec/schema_meta_spec.rb +78 -0
  44. data/spec/schema_spec.rb +218 -0
  45. data/spec/spec_helper.rb +13 -0
  46. data/spec/type_spec/array_spec.rb +206 -0
  47. data/spec/type_spec/composition/all_of_spec.rb +20 -0
  48. data/spec/type_spec/composition/any_of_spec.rb +20 -0
  49. data/spec/type_spec/composition/one_of_spec.rb +20 -0
  50. data/spec/type_spec/composition_spec.rb +90 -0
  51. data/spec/type_spec/enum_spec.rb +93 -0
  52. data/spec/type_spec/primitive/boolean_spec.rb +12 -0
  53. data/spec/type_spec/primitive/integer_spec.rb +57 -0
  54. data/spec/type_spec/primitive/null_spec.rb +12 -0
  55. data/spec/type_spec/primitive/number_spec.rb +12 -0
  56. data/spec/type_spec/primitive/numeric_spec.rb +176 -0
  57. data/spec/type_spec/primitive/string_spec.rb +119 -0
  58. data/spec/type_spec_spec.rb +32 -0
  59. metadata +183 -0
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JsonModel
4
+ class TypeSpec
5
+ class Primitive
6
+ class Integer < Numeric
7
+ # @param [Hash] options
8
+ def initialize(**options)
9
+ super('integer', **options)
10
+ end
11
+
12
+ # @param [Symbol] name
13
+ # @param [ActiveModel::Validations] klass
14
+ def register_validations(name, klass)
15
+ super
16
+ klass.validates(name, numericality: { only_integer: true }, allow_nil: true)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JsonModel
4
+ class TypeSpec
5
+ class Primitive
6
+ class Null < Primitive
7
+ def initialize
8
+ super('null')
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JsonModel
4
+ class TypeSpec
5
+ class Primitive
6
+ class Number < Numeric
7
+ # @param [Hash] options
8
+ def initialize(**options)
9
+ super('number', **options)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JsonModel
4
+ class TypeSpec
5
+ class Primitive
6
+ class Numeric < Primitive
7
+ # @param [String] type
8
+ # @param [Integer, nil] multiple_of
9
+ # @param [Integer, nil] minimum
10
+ # @param [Integer, nil] maximum
11
+ # @param [Integer, nil] exclusive_minimum
12
+ # @param [Integer, nil] exclusive_maximum
13
+ def initialize(
14
+ type,
15
+ multiple_of: nil,
16
+ minimum: nil,
17
+ maximum: nil,
18
+ exclusive_minimum: nil,
19
+ exclusive_maximum: nil
20
+ )
21
+ super(type)
22
+ @multiple_of = multiple_of
23
+ @minimum = minimum
24
+ @maximum = maximum
25
+ @exclusive_minimum = exclusive_minimum
26
+ @exclusive_maximum = exclusive_maximum
27
+ end
28
+
29
+ # @param [Hash] options
30
+ # @return [Hash]
31
+ def as_schema(**options)
32
+ super
33
+ .merge(
34
+ {
35
+ multipleOf: @multiple_of,
36
+ minimum: @minimum,
37
+ maximum: @maximum,
38
+ exclusiveMinimum: @exclusive_minimum,
39
+ exclusiveMaximum: @exclusive_maximum,
40
+ }.compact,
41
+ )
42
+ end
43
+
44
+ # @param [Symbol] name
45
+ # @param [ActiveModel::Validations] klass
46
+ def register_validations(name, klass)
47
+ super
48
+ register_multiple_of_validation(name, klass, @multiple_of)
49
+ if @minimum || @maximum || @exclusive_minimum || @exclusive_maximum
50
+ klass.validates_numericality_of(
51
+ name,
52
+ **{
53
+ greater_than: @exclusive_minimum,
54
+ less_than: @exclusive_maximum,
55
+ greater_than_or_equal_to: @minimum,
56
+ less_than_or_equal_to: @maximum,
57
+ }.compact,
58
+ allow_nil: true,
59
+ )
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ # @param [Symbol] name
66
+ # @param [ActiveModel::Validations] klass
67
+ # @param [Integer, nil] multiple_of
68
+ def register_multiple_of_validation(name, klass, multiple_of)
69
+ if multiple_of.nil?
70
+ return
71
+ end
72
+
73
+ klass.validate do |record|
74
+ value = record.send(name)
75
+ if value.present? && value % multiple_of != 0
76
+ record.errors.add(name, :multiple_of, message: "must be a multiple of #{multiple_of}")
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JsonModel
4
+ class TypeSpec
5
+ class Primitive
6
+ class String < Primitive
7
+ JSON_SCHEMA_FORMATS = {
8
+ date_time: lambda { |v|
9
+ begin
10
+ DateTime.iso8601(v)
11
+ true
12
+ rescue StandardError
13
+ false
14
+ end
15
+ },
16
+ date: lambda { |v|
17
+ begin
18
+ Date.iso8601(v)
19
+ true
20
+ rescue StandardError
21
+ false
22
+ end
23
+ },
24
+ time: lambda { |v|
25
+ begin
26
+ Time.iso8601(v)
27
+ true
28
+ rescue StandardError
29
+ false
30
+ end
31
+ },
32
+ email: ->(v) { v.match?(URI::MailTo::EMAIL_REGEXP) },
33
+ hostname: ->(v) { v.match?(/\A[a-zA-Z0-9\-.]{1,253}\z/) },
34
+ ipv4: lambda { |v|
35
+ begin
36
+ IPAddr.new(v).ipv4?
37
+ rescue StandardError
38
+ false
39
+ end
40
+ },
41
+ ipv6: lambda { |v|
42
+ begin
43
+ IPAddr.new(v).ipv6?
44
+ rescue StandardError
45
+ false
46
+ end
47
+ },
48
+ uri: lambda { |v|
49
+ begin
50
+ URI.parse(v)
51
+ true
52
+ rescue StandardError
53
+ false
54
+ end
55
+ },
56
+ uri_reference: lambda { |v|
57
+ begin
58
+ URI.parse(v)
59
+ true
60
+ rescue StandardError
61
+ false
62
+ end
63
+ },
64
+ uuid: lambda { |v|
65
+ SecureRandom(v.match?(/\A[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/i))
66
+ },
67
+ regex: lambda { |v|
68
+ begin
69
+ Regexp.new(v)
70
+ true
71
+ rescue StandardError
72
+ false
73
+ end
74
+ },
75
+ json_pointer: ->(v) { v.match?(%r{\A(?:/(?:[^~]|~[01])*)*\z}) },
76
+ relative_json_pointer: ->(v) { v.match?(%r{\A(?:0|[1-9][0-9]*)(?:#|(?:/(?:[^~]|~[01])*))*\z}) },
77
+ }.freeze
78
+
79
+
80
+ # @param [Integer, nil] min_length
81
+ # @param [Integer, nil] max_length
82
+ # @param [Regexp, nil] pattern
83
+ # @param [String, nil] format
84
+ def initialize(min_length: nil, max_length: nil, pattern: nil, format: nil)
85
+ super('string')
86
+
87
+ @min_length = min_length
88
+ @max_length = max_length
89
+ @pattern = pattern
90
+ @format = format
91
+ end
92
+
93
+ # @param [Hash] options
94
+ # @return [Hash]
95
+ def as_schema(**options)
96
+ super.merge(
97
+ {
98
+ minLength: @min_length,
99
+ maxLength: @max_length,
100
+ pattern: @pattern&.source,
101
+ format: @format&.to_s&.tr('_', '-'),
102
+ }.compact,
103
+ )
104
+ end
105
+
106
+ # @param [Symbol] name
107
+ # @param [ActiveModel::Validations]
108
+ def register_validations(name, klass)
109
+ super
110
+
111
+ if !@min_length.nil? || !@max_length.nil?
112
+ klass.validates(name, length: { minimum: @min_length, maximum: @max_length }.compact, allow_nil: true)
113
+ end
114
+ if @pattern
115
+ klass.validates(name, format: { with: @pattern }, allow_nil: true)
116
+ end
117
+ if @format
118
+ if JSON_SCHEMA_FORMATS.key?(@format)
119
+
120
+ register_format_validation(klass, name)
121
+ else
122
+ raise(ArgumentError, "Invalid format: #{@format}")
123
+ end
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ def register_format_validation(klass, name)
130
+ if @format.nil?
131
+ return
132
+ end
133
+
134
+ format_validator = JSON_SCHEMA_FORMATS[@format]
135
+ if format_validator.nil?
136
+ return
137
+ end
138
+
139
+ klass.validate do |record|
140
+ value = record.send(name)
141
+
142
+ if !value.nil? && !format_validator.call(value)
143
+ record.errors.add(name, :invalid_format, message: "must be a valid #{@format}")
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JsonModel
4
+ class TypeSpec
5
+ class Primitive < TypeSpec
6
+ # @param [String] type
7
+ def initialize(type)
8
+ super()
9
+ @type = type
10
+ end
11
+
12
+ # @param [Hash] _options
13
+ # @return [Hash]
14
+ def as_schema(**_options)
15
+ { type: @type }
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ require_relative('primitive/boolean')
22
+ require_relative('primitive/numeric')
23
+ require_relative('primitive/integer')
24
+ require_relative('primitive/null')
25
+ require_relative('primitive/number')
26
+ require_relative('primitive/string')
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative('type_spec/array')
4
+ require_relative('type_spec/composition')
5
+ require_relative('type_spec/enum')
6
+ require_relative('type_spec/object')
7
+ require_relative('type_spec/primitive')
8
+
9
+ module JsonModel
10
+ class TypeSpec
11
+ # @param [Hash] options
12
+ # @return [Hash]
13
+ def as_schema(**options)
14
+ raise(NotImplementedError)
15
+ end
16
+
17
+ # @param [Symbol] name
18
+ # @param [ActiveModel::Validations] klass
19
+ def register_validations(name, klass) end
20
+
21
+ # @return [Array<TypeSpec>]
22
+ def referenced_schemas
23
+ []
24
+ end
25
+
26
+ class << self
27
+ # @param [Object] type
28
+ # @param [Hash] options
29
+ # @return [TypeSpec]
30
+ def resolve(type, **options)
31
+ case type
32
+ when TypeSpec
33
+ type
34
+ when Class
35
+ resolve_type_from_class(type, **options)
36
+ when T::AllOf, T::AnyOf, T::Boolean, T::OneOf, T::Array, T::Enum
37
+ type.to_type_spec(**options)
38
+ else
39
+ raise(ArgumentError, "Unsupported type: #{type}")
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ # @param [Object] type
46
+ # @param [Hash] options
47
+ # @return [TypeSpec]
48
+ def resolve_type_from_class(type, **options)
49
+ if type == String
50
+ Primitive::String.new(**options)
51
+ elsif type == Integer
52
+ Primitive::Integer.new(**options)
53
+ elsif type == Float
54
+ Primitive::Number.new(**options)
55
+ elsif [TrueClass, FalseClass].include?(type)
56
+ Primitive::Boolean.new(**options)
57
+ elsif type == NilClass
58
+ Primitive::Null.new(**options)
59
+ elsif type < Schema
60
+ TypeSpec::Object.new(type, **options)
61
+ else
62
+ raise(ArgumentError, "Unsupported type: #{type}")
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module T
4
+ class AllOf
5
+ # @param [Array<Class>] types
6
+ def initialize(*types)
7
+ @types = types
8
+ end
9
+
10
+ # @return [JsonModel::TypeSpec::Composition::AllOf]
11
+ def to_type_spec(**options)
12
+ JsonModel::TypeSpec::Composition::AllOf.new(
13
+ *@types.map { |type| JsonModel::TypeSpec.resolve(type) },
14
+ **options,
15
+ )
16
+ end
17
+
18
+ class << self
19
+ # @param [Array] types
20
+ # @return [AllOf]
21
+ def [](*types)
22
+ AllOf.new(*types)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module T
4
+ class AnyOf
5
+ # @param [Array<Class>] types
6
+ def initialize(*types)
7
+ @types = types
8
+ end
9
+
10
+ # @return [JsonModel::TypeSpec::Composition::AnyOf]
11
+ def to_type_spec(**options)
12
+ JsonModel::TypeSpec::Composition::AnyOf.new(
13
+ *@types.map { |type| JsonModel::TypeSpec.resolve(type) },
14
+ **options,
15
+ )
16
+ end
17
+
18
+ class << self
19
+ # @param [Array] types
20
+ # @return [AnyOf]
21
+ def [](*types)
22
+ AnyOf.new(*types)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module T
4
+ class Array
5
+ # @param [Class] type
6
+ def initialize(type)
7
+ @type = type
8
+ end
9
+
10
+ # @return [JsonModel::TypeSpec::Array]
11
+ def to_type_spec(**options)
12
+ JsonModel::TypeSpec::Array.new(
13
+ JsonModel::TypeSpec.resolve(@type),
14
+ **options,
15
+ )
16
+ end
17
+
18
+ class << self
19
+ # @param [Class] type
20
+ # @return [Array]
21
+ def [](type)
22
+ Array.new(type)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module T
4
+ Boolean = Class.new do
5
+ def to_type_spec(**_options) = JsonModel::TypeSpec::Primitive::Boolean.new
6
+ end.new
7
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module T
4
+ class Enum
5
+ # @param [Array<Object>] values
6
+ def initialize(*values)
7
+ @values = values
8
+ end
9
+
10
+ # @return [JsonModel::TypeSpec::Enum]
11
+ def to_type_spec(**options)
12
+ JsonModel::TypeSpec::Enum.new(*@values, **options)
13
+ end
14
+
15
+ class << self
16
+ # @param [Array] args
17
+ # @return [Enum]
18
+ def [](*args)
19
+ Enum.new(*args)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module T
4
+ class OneOf
5
+ # @param [Array<Class>] types
6
+ def initialize(*types)
7
+ @types = types
8
+ end
9
+
10
+ # @return [JsonModel::TypeSpec::Composition::OneOf]
11
+ def to_type_spec(**options)
12
+ JsonModel::TypeSpec::Composition::OneOf.new(
13
+ *@types.map { |type| JsonModel::TypeSpec.resolve(type) },
14
+ **options,
15
+ )
16
+ end
17
+
18
+ class << self
19
+ # @param [Array] types
20
+ # @return [OneOf]
21
+ def [](*types)
22
+ OneOf.new(*types)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative('types/all_of')
4
+ require_relative('types/any_of')
5
+ require_relative('types/boolean')
6
+ require_relative('types/one_of')
7
+ require_relative('types/array')
8
+ require_relative('types/enum')
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JsonModel
4
+ VERSION = '0.1.1'
5
+ end
data/lib/json_model.rb ADDED
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require('active_model')
4
+ require('active_support/concern')
5
+ require('active_support/core_ext/class/attribute')
6
+ require('active_support/descendants_tracker')
7
+ require('uri')
8
+ require('json_model/config')
9
+ require('json_model/errors')
10
+ require('json_model/properties')
11
+ require('json_model/property')
12
+ require('json_model/ref_mode')
13
+ require('json_model/schema_meta')
14
+ require('json_model/schema')
15
+ require('json_model/type_spec')
16
+ require('json_model/types')
17
+ require('json_model/version')
18
+
19
+ module JsonModel
20
+ class << self
21
+ # @return [Config]
22
+ def configure(&)
23
+ yield(Config)
24
+ end
25
+
26
+ # @return [Config]
27
+ def config
28
+ Config
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require('spec_helper')
4
+
5
+ RSpec.describe(JsonModel::Config) do
6
+ describe('#property_naming_strategy') do
7
+ it('defaults to identity') do
8
+ value = SecureRandom.uuid.to_sym
9
+ expect(described_class.property_naming_strategy.call(value))
10
+ .to(eq(value))
11
+ end
12
+
13
+ it('can be changed to a proc') do
14
+ value = SecureRandom.uuid.to_sym
15
+ JsonModel.configure { |config| config.property_naming_strategy = ->(value) { value.to_s.upcase.to_sym } }
16
+
17
+ expect(described_class.property_naming_strategy.call(value))
18
+ .to(eq(value.to_s.upcase.to_sym))
19
+ end
20
+
21
+ it('can be changed to pascal case') do
22
+ JsonModel.configure { |config| config.property_naming_strategy = :pascal_case }
23
+
24
+ expect(described_class.property_naming_strategy.call(:foo_bar))
25
+ .to(eq('FooBar'))
26
+ end
27
+
28
+ it('can be changed to camel case') do
29
+ JsonModel.configure { |config| config.property_naming_strategy = :camel_case }
30
+
31
+ expect(described_class.property_naming_strategy.call(:foo_bar))
32
+ .to(eq('fooBar'))
33
+ end
34
+ end
35
+
36
+ describe('#validate_after_instantiation') do
37
+ it('defaults to true') do
38
+ expect(JsonModel.config.validate_after_instantiation)
39
+ .to(eq(true))
40
+ end
41
+
42
+ it('can be changed to false') do
43
+ JsonModel.configure { |config| config.validate_after_instantiation = false }
44
+
45
+ expect(JsonModel.config.validate_after_instantiation)
46
+ .to(eq(false))
47
+ end
48
+ end
49
+
50
+ describe('#schema_id_base_uri') do
51
+ it('defaults to nil') do
52
+ expect(JsonModel.config.schema_id_base_uri)
53
+ .to(be_nil)
54
+ end
55
+
56
+ it('can be changed to false') do
57
+ JsonModel.configure { |config| config.schema_id_base_uri = 'https://example.com/schemas/' }
58
+
59
+ expect(JsonModel.config.schema_id_base_uri)
60
+ .to(eq('https://example.com/schemas/'))
61
+ end
62
+ end
63
+
64
+ describe('#schema_id_naming_strategy') do
65
+ let(:klass) do
66
+ Class.new do
67
+ def self.name
68
+ 'Baz::FooBar'
69
+ end
70
+ end
71
+ end
72
+
73
+ it('defaults to none') do
74
+ expect(JsonModel.config.schema_id_naming_strategy.call(klass))
75
+ .to(be_nil)
76
+ end
77
+
78
+ it('can be changed to class_name') do
79
+ JsonModel.configure { |config| config.schema_id_naming_strategy = :class_name }
80
+
81
+ expect(JsonModel.config.schema_id_naming_strategy.call(klass))
82
+ .to(eq('FooBar'))
83
+ end
84
+
85
+ it('can be changed to kebab_case_class_name') do
86
+ JsonModel.configure { |config| config.schema_id_naming_strategy = :kebab_case_class_name }
87
+
88
+ expect(JsonModel.config.schema_id_naming_strategy.call(klass))
89
+ .to(eq('foo-bar'))
90
+ end
91
+
92
+ it('can be changed to snake_case_class_name') do
93
+ JsonModel.configure { |config| config.schema_id_naming_strategy = :snake_case_class_name }
94
+
95
+ expect(JsonModel.config.schema_id_naming_strategy.call(klass))
96
+ .to(eq('foo_bar'))
97
+ end
98
+
99
+ it('can use a custom proc') do
100
+ JsonModel.configure { |config| config.schema_id_naming_strategy = ->(klass) { klass.name.downcase } }
101
+
102
+ expect(JsonModel.config.schema_id_naming_strategy.call(klass))
103
+ .to(eq('baz::foobar'))
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe(JsonModel) do
4
+ it('has a version number') do
5
+ expect(JsonModel::VERSION).not_to(be(nil))
6
+ end
7
+ end