Skip to content

Commit acccdcb

Browse files
authored
Update Introspection (#17)
* dont redo the work when passing a Generator object for arrayed subclass * to_h on objects that have generator as key was failing -- recursively go through object instead * pluck introspection specs to own file * bump version * allow introspection of class schema; pull out instrospection to own file * version * better validation for introspection
1 parent 1222a60 commit acccdcb

File tree

10 files changed

+299
-131
lines changed

10 files changed

+299
-131
lines changed

.ruby-version

Lines changed: 0 additions & 1 deletion
This file was deleted.

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
json_schematize (0.10.0)
4+
json_schematize (0.11.0)
55
class_composer (>= 1.0)
66

77
GEM

lib/json_schematize/field.rb

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
class JsonSchematize::Field
77

8-
attr_reader :name, :types, :dig, :dig_type, :symbol, :validator, :empty_value
8+
attr_reader :name, :types, :type, :dig, :dig_type, :symbol, :validator, :empty_value
99
attr_reader :acceptable_types, :required, :converter, :array_of_types
1010

1111
EXPECTED_DIG_TYPE = [DIG_SYMBOL = :symbol, DEFAULT_DIG = DIG_NONE =:none, DIG_STRING = :string]
@@ -89,7 +89,11 @@ def value_from_field(params)
8989
private
9090

9191
def validate_acceptable_types(val:)
92-
(all_allowed_types + @acceptable_types).include?(val.class)
92+
types = (all_allowed_types + @acceptable_types)
93+
return true if types.include?(val.class)
94+
95+
# Allow inheritance here as well -- this only works when a custom converter is defined
96+
Class === val ? types.any? { _1 > val } : false
9397
end
9498

9599
def all_allowed_types
@@ -108,8 +112,14 @@ def iterate_array_of_types(value:)
108112
raise JsonSchematize::InvalidFieldByArrayOfTypes, ":#{name} expected to be an array based on :array_of_types flag. Given #{value.class}"
109113
end
110114

111-
value.map do |val|
112-
raw_converter_call(value: val)
115+
if value.all? { JsonSchematize::Generator === _1 }
116+
# We have already done the work to convert it into a Schematizable object
117+
# Return the array and allow the remaining validations to take place
118+
value
119+
else
120+
value.map do |val|
121+
raw_converter_call(value: val)
122+
end
113123
end
114124
end
115125

lib/json_schematize/generator.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
require "json_schematize/cache"
44
require "json_schematize/field"
5-
require "json_schematize/introspect"
5+
require "json_schematize/introspect_instance_methods"
6+
require "json_schematize/introspect_class_methods"
67

78
class JsonSchematize::Generator
89
EMPTY_VALIDATOR = ->(_transformed_value, _raw_value) { true }
910
PROTECTED_METHODS = [:assign_values!, :convenience_methods, :validate_required!, :validate_optional!, :validate_value]
1011

11-
include JsonSchematize::Introspect
12+
include JsonSchematize::Introspect::InstanceMethods
13+
extend JsonSchematize::Introspect::ClassMethods
1214

1315
def self.add_field(name:, type: nil, types: nil, dig_type: nil, dig: nil, validator: nil, required: nil, converter: nil, array_of_types: nil, empty_value: nil, hash_of_types: nil, hash_of_types_key: "name")
1416
require "json_schematize/empty_value"

lib/json_schematize/introspect.rb

Lines changed: 0 additions & 28 deletions
This file was deleted.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# frozen_string_literal: true
2+
3+
module JsonSchematize::Introspect
4+
module ClassMethods
5+
def introspect(deep: false, prepend_dig: [], introspection: {})
6+
fields.each do |field|
7+
naming = (prepend_dig + field.dig).compact
8+
type_metadata = __field_type__(field)
9+
introspection[naming.join(".")] = {
10+
required: field.required,
11+
allowed: type_metadata[:humanize],
12+
}
13+
14+
if deep && type_metadata[:deep_introspection]
15+
prepended_naming = if field.array_of_types
16+
naming.dup.tap { _1[-1] = "#{_1[-1]}[x]"}
17+
else
18+
naming.dup
19+
end
20+
type_metadata[:types][0].introspect(deep:, prepend_dig: prepended_naming, introspection: introspection)
21+
end
22+
end
23+
24+
introspection
25+
end
26+
27+
def __field_type__(field)
28+
types = Array(field.type || field.types)
29+
deep_introspection = false
30+
type_string = if types.length == 0
31+
"Anything"
32+
elsif types.length == 1
33+
type = types.first
34+
if JsonSchematize::Generator > type
35+
deep_introspection = true
36+
if field.array_of_types
37+
"Array of #{type}"
38+
else
39+
type
40+
end
41+
else
42+
type
43+
end
44+
else
45+
"One of [#{types}]"
46+
end
47+
48+
{
49+
humanize: type_string,
50+
types:,
51+
deep_introspection:,
52+
}
53+
end
54+
end
55+
end
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# frozen_string_literal: true
2+
3+
module JsonSchematize::Introspect
4+
module InstanceMethods
5+
def to_h
6+
self.class.fields.map do |field|
7+
value = method(:"#{field.name}").()
8+
if field.array_of_types
9+
[field.name, value.map(&:to_h)]
10+
elsif value.class == Class
11+
[field.name, value.to_s]
12+
elsif JsonSchematize::Generator > value.class
13+
[field.name, value.to_h]
14+
else
15+
[field.name, value]
16+
end
17+
end.to_h
18+
end
19+
alias :to_hash :to_h
20+
21+
def deep_inspect(with_raw_params: false, with_field: false)
22+
self.class.fields.map do |field|
23+
value = {
24+
required: field.required,
25+
acceptable_types: field.acceptable_types,
26+
value: instance_variable_get(:"@#{field.name}"),
27+
}
28+
value[:field] = field if with_field
29+
value[:raw_params] = @__raw_params if with_raw_params
30+
[field.name, value]
31+
end.to_h
32+
end
33+
34+
def inspect
35+
stringify = to_h.map { |k, v| "#{k}:#{v}" }.join(", ")
36+
"#<#{self.class} - required fields: #{self.class.required_fields.map(&:name)}; #{stringify}>"
37+
end
38+
end
39+
end

lib/json_schematize/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# frozen_string_literal: true
22

33
module JsonSchematize
4-
VERSION = "0.10.0"
4+
VERSION = "0.11.0"
55
end

spec/lib/json_schematize/generator_spec.rb

Lines changed: 0 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# frozen_string_literal: true
22

33
RSpec.describe JsonSchematize::Generator do
4-
54
describe ".add_field" do
65
subject(:add_field) { klass.add_field(**field_params) }
76

@@ -132,99 +131,6 @@ class KlassInit < described_class
132131
end
133132
end
134133

135-
describe "introspection" do
136-
let(:instance) { klass.new(**params) }
137-
138-
let(:params) do
139-
{
140-
id: 6457,
141-
count: 9145,
142-
style: :symbol,
143-
something: "danger",
144-
danger: :count,
145-
zone: :zone,
146-
}
147-
end
148-
149-
describe "#to_h" do
150-
subject(:to_h) { instance.to_h }
151-
152-
let(:klass) do
153-
class IntrospectKlassToH < described_class
154-
add_field name: :id, type: Integer
155-
add_field name: :count, type: Integer
156-
add_field name: :style, type: Symbol
157-
add_field name: :something, type: String
158-
add_field name: :danger, type: Symbol
159-
add_field name: :zone, type: Symbol
160-
end
161-
IntrospectKlassToH
162-
end
163-
it { is_expected.to eq(params) }
164-
end
165-
166-
describe "#deep_inspect" do
167-
subject(:deep_inspect) { instance.deep_inspect(with_raw_params: with_raw_params, with_field: with_field) }
168-
169-
let(:klass) do
170-
class IntrospectKlassDeepInspect < described_class
171-
add_field name: :id, type: Integer
172-
add_field name: :count, type: Integer
173-
add_field name: :style, type: Symbol
174-
add_field name: :something, type: String
175-
add_field name: :danger, type: Symbol
176-
add_field name: :zone, type: Symbol
177-
end
178-
IntrospectKlassDeepInspect
179-
end
180-
let(:with_raw_params) { false }
181-
let(:with_field) { false }
182-
let(:enumerate_expected) do
183-
klass.fields.map do |field|
184-
value = {
185-
required: field.required,
186-
acceptable_types: field.acceptable_types,
187-
value: params[field.name],
188-
}
189-
value[:field] = field if with_field
190-
value[:raw_params] = params if with_raw_params
191-
[field.name, value]
192-
end.to_h
193-
end
194-
195-
it { is_expected.to eq(enumerate_expected) }
196-
197-
context 'when with_raw_params' do
198-
let(:with_raw_params) { true }
199-
it { is_expected.to eq(enumerate_expected) }
200-
end
201-
202-
context 'when with_field' do
203-
let(:with_field) { true }
204-
205-
it { is_expected.to eq(enumerate_expected) }
206-
end
207-
end
208-
209-
describe "#inspect" do
210-
subject(:inspect) { instance.inspect }
211-
212-
let(:klass) do
213-
class IntrospectKlassInspect < described_class
214-
add_field name: :id, type: Integer
215-
add_field name: :count, type: Integer
216-
add_field name: :style, type: Symbol
217-
add_field name: :something, type: String
218-
add_field name: :danger, type: Symbol
219-
add_field name: :zone, type: Symbol
220-
end
221-
IntrospectKlassInspect
222-
end
223-
let(:expected) { "#<#{klass} - required fields: #{params.keys}; #{instance.to_h.map { |k, v| "#{k}:#{v}" }.join(", ")}>" }
224-
it { is_expected.to eq(expected) }
225-
end
226-
end
227-
228134
context "when modifying values" do
229135
let(:instance) { klass.new(raise_on_error: raise_on_error, **params) }
230136
let(:klass) do

0 commit comments

Comments
 (0)