tomyum 0.1.0.a
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +1106 -0
- data/lib/tomyum/assertions.rb +80 -0
- data/lib/tomyum/attributes/array.rb +11 -0
- data/lib/tomyum/attributes/attribute.rb +130 -0
- data/lib/tomyum/attributes/boolean.rb +22 -0
- data/lib/tomyum/attributes/currency.rb +19 -0
- data/lib/tomyum/attributes/date.rb +11 -0
- data/lib/tomyum/attributes/float.rb +13 -0
- data/lib/tomyum/attributes/integer.rb +14 -0
- data/lib/tomyum/attributes/ip_address.rb +15 -0
- data/lib/tomyum/attributes/number.rb +24 -0
- data/lib/tomyum/attributes/object.rb +71 -0
- data/lib/tomyum/attributes/schema.rb +23 -0
- data/lib/tomyum/attributes/string.rb +36 -0
- data/lib/tomyum/attributes/time.rb +19 -0
- data/lib/tomyum/attributes/uri.rb +19 -0
- data/lib/tomyum/attributes/visitor.rb +136 -0
- data/lib/tomyum/attributes.rb +92 -0
- data/lib/tomyum/endpoint.rb +102 -0
- data/lib/tomyum/endpoints/method.rb +90 -0
- data/lib/tomyum/endpoints/params.rb +115 -0
- data/lib/tomyum/error.rb +17 -0
- data/lib/tomyum/functions.rb +49 -0
- data/lib/tomyum/generators/generator.rb +16 -0
- data/lib/tomyum/generators/grpc/generator.rb +10 -0
- data/lib/tomyum/generators/open_api/generator.rb +205 -0
- data/lib/tomyum/generators/open_api/property_generator.rb +111 -0
- data/lib/tomyum/generators.rb +3 -0
- data/lib/tomyum/registry.rb +75 -0
- data/lib/tomyum/resolvable.rb +11 -0
- data/lib/tomyum/resolver.rb +99 -0
- data/lib/tomyum/serializer.rb +125 -0
- data/lib/tomyum/serializers/serializable.rb +23 -0
- data/lib/tomyum/server/app.rb +33 -0
- data/lib/tomyum/server/document.rb +20 -0
- data/lib/tomyum/server/documents/redoc.rb +36 -0
- data/lib/tomyum/server/documents/swagger.rb +47 -0
- data/lib/tomyum/server/routes.rb +0 -0
- data/lib/tomyum/support.rb +13 -0
- data/lib/tomyum/validator.rb +205 -0
- data/lib/tomyum/validators/normalizable.rb +24 -0
- data/lib/tomyum/validators/proxy.rb +77 -0
- data/lib/tomyum/validators/validatable.rb +48 -0
- data/lib/tomyum/version.rb +3 -0
- data/lib/tomyum.rb +28 -0
- metadata +202 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
module Tomyum
|
2
|
+
# Declarative way to deal with errors
|
3
|
+
module Assertions
|
4
|
+
Error = Class.new(StandardError)
|
5
|
+
KeyError = Class.new(Error)
|
6
|
+
SubsetError = Class.new(Error)
|
7
|
+
|
8
|
+
extend self
|
9
|
+
def self.included(object)
|
10
|
+
object.extend(self)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Usage
|
14
|
+
#
|
15
|
+
# Use default error message
|
16
|
+
#
|
17
|
+
# assert valid?
|
18
|
+
#
|
19
|
+
# Use custom error message
|
20
|
+
#
|
21
|
+
# assert valid?, "Invalid value"
|
22
|
+
#
|
23
|
+
# Use custom Error class
|
24
|
+
#
|
25
|
+
# assert valid?, ValidationError, "Invalid value"
|
26
|
+
def assert(object, *msgs)
|
27
|
+
raises Error, msgs << "#{object.class} is not a truthy" unless object
|
28
|
+
end
|
29
|
+
|
30
|
+
def assert_not(object, *msgs)
|
31
|
+
raises Error, msgs << "#{object.class} is not a falsey" if object
|
32
|
+
end
|
33
|
+
|
34
|
+
def assert_kind_of(classes, object, *msgs)
|
35
|
+
classes = normalize_array(classes)
|
36
|
+
found = classes.find { |klass| object.kind_of?(klass) }
|
37
|
+
|
38
|
+
raises Error, msgs << "#{object.class} must be an instance of #{classes.map(&:name).join(', ')}" unless found
|
39
|
+
end
|
40
|
+
|
41
|
+
alias_method :assert_kind_of_either, :assert_kind_of
|
42
|
+
|
43
|
+
# Usage
|
44
|
+
#
|
45
|
+
# assert_in ["john", "joe"], "joe"
|
46
|
+
# assert_in { amount: 1 }, :amount
|
47
|
+
def assert_in(list, value, *msgs)
|
48
|
+
raises KeyError, msgs << "#{value.class} - #{value} is not in the #{list.keys}" if list.respond_to?(:key?) && !list.key?(value)
|
49
|
+
raises KeyError, msgs << "#{value.class} - #{value} is not in the #{list}" if list.kind_of?(Array) && !list.include?(value)
|
50
|
+
end
|
51
|
+
|
52
|
+
def assert_subset_of(parent, subset, *msgs)
|
53
|
+
rest = subset - parent
|
54
|
+
raises SubsetError, msgs << "#{rest} are not subset of #{parent}" if rest.present?
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def normalize_array(values)
|
60
|
+
values.respond_to?(:each) ? values : [values]
|
61
|
+
end
|
62
|
+
|
63
|
+
# allow caller to pass custom Error
|
64
|
+
def raises(error, msgs = [])
|
65
|
+
error, msg = msgs unless msgs.first.kind_of?(String)
|
66
|
+
|
67
|
+
# Error class has its own error message and caller
|
68
|
+
# doesn't specify custom message
|
69
|
+
if msgs.length == 2
|
70
|
+
if error.respond_to?(:message)
|
71
|
+
msg = error.message
|
72
|
+
elsif error.respond_to?(:new)
|
73
|
+
msg = error.new&.message
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
raise error, msg || msgs.last
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module Tomyum
|
2
|
+
module Attributes
|
3
|
+
# An abstract Attribute based class.
|
4
|
+
#
|
5
|
+
# @abstract
|
6
|
+
class Attribute
|
7
|
+
include Tomyum::Assertions
|
8
|
+
include Tomyum::Serializers::Serializable
|
9
|
+
include Tomyum::Validators::Normalizable
|
10
|
+
include Tomyum::Validators::Validatable
|
11
|
+
|
12
|
+
class_attribute :options
|
13
|
+
attr_reader :name
|
14
|
+
|
15
|
+
# Assigns default class attribute values
|
16
|
+
self.options = {}.freeze
|
17
|
+
|
18
|
+
def initialize(name, options = {})
|
19
|
+
@name = name
|
20
|
+
@options = self.class.options.merge(options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def options
|
24
|
+
@options.merge({
|
25
|
+
name: @name,
|
26
|
+
match: match,
|
27
|
+
method: method,
|
28
|
+
type: type,
|
29
|
+
native_type: native_type,
|
30
|
+
of: of,
|
31
|
+
in: send(:in),
|
32
|
+
if: @options[:if],
|
33
|
+
unless: @options[:unless],
|
34
|
+
null: null,
|
35
|
+
spec: spec,
|
36
|
+
spec_uri: spec_uri,
|
37
|
+
format: format,
|
38
|
+
private: private,
|
39
|
+
deprecated: deprecated,
|
40
|
+
required: required,
|
41
|
+
default: default,
|
42
|
+
description: description,
|
43
|
+
attributes: attributes.values&.map(&:options),
|
44
|
+
})
|
45
|
+
end
|
46
|
+
|
47
|
+
def type
|
48
|
+
self.class.name.demodulize.chomp("Attribute").underscore
|
49
|
+
end
|
50
|
+
|
51
|
+
def match
|
52
|
+
@options[:match]
|
53
|
+
end
|
54
|
+
|
55
|
+
def method
|
56
|
+
@options[:method] || @name
|
57
|
+
end
|
58
|
+
|
59
|
+
def of
|
60
|
+
@options[:of]
|
61
|
+
end
|
62
|
+
|
63
|
+
def in
|
64
|
+
@options[:in]
|
65
|
+
end
|
66
|
+
|
67
|
+
def if
|
68
|
+
@options[:if]
|
69
|
+
end
|
70
|
+
|
71
|
+
def unless
|
72
|
+
@options[:unless]
|
73
|
+
end
|
74
|
+
|
75
|
+
def null
|
76
|
+
@options.fetch(:null, false)
|
77
|
+
end
|
78
|
+
|
79
|
+
def native_type
|
80
|
+
type
|
81
|
+
end
|
82
|
+
|
83
|
+
def format
|
84
|
+
@options[:format]
|
85
|
+
end
|
86
|
+
|
87
|
+
def spec
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def spec_uri
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
|
95
|
+
def attributes
|
96
|
+
{}
|
97
|
+
end
|
98
|
+
|
99
|
+
# deprecated attribute should be listed in schema
|
100
|
+
def deprecated
|
101
|
+
@options.fetch(:deprecated, false)
|
102
|
+
end
|
103
|
+
|
104
|
+
def private
|
105
|
+
@options.fetch(:private, false)
|
106
|
+
end
|
107
|
+
|
108
|
+
def required
|
109
|
+
@options.fetch(:required, false)
|
110
|
+
end
|
111
|
+
|
112
|
+
def default
|
113
|
+
@options[:default]
|
114
|
+
end
|
115
|
+
|
116
|
+
def description
|
117
|
+
@options[:description]
|
118
|
+
end
|
119
|
+
|
120
|
+
# Should not be listed in schema or serialize this attribute
|
121
|
+
def serializable?
|
122
|
+
!private
|
123
|
+
end
|
124
|
+
|
125
|
+
def value(value)
|
126
|
+
value.nil? ? default : value
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative "attribute"
|
2
|
+
|
3
|
+
module Tomyum
|
4
|
+
module Attributes
|
5
|
+
# @example Registers custom normalizer that accepts "on" as truth value
|
6
|
+
# Boolean.normalizer = ->(x) { ["on", true].include?(x) ? true : false }
|
7
|
+
#
|
8
|
+
# @example Registers custom serializer
|
9
|
+
# Boolean.serialize = ->(x) { x == "on" ? true : false }
|
10
|
+
class Boolean < Attribute
|
11
|
+
self.serializer = ->(v) { ActiveModel::Type::Boolean.new.cast(v) }
|
12
|
+
|
13
|
+
def native_type
|
14
|
+
"boolean"
|
15
|
+
end
|
16
|
+
|
17
|
+
def in
|
18
|
+
@options.fetch(:in, [true, false])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative "attribute"
|
2
|
+
|
3
|
+
module Tomyum
|
4
|
+
module Attributes
|
5
|
+
class Currency < String
|
6
|
+
def format
|
7
|
+
super || type
|
8
|
+
end
|
9
|
+
|
10
|
+
def spec
|
11
|
+
"ISO 4217"
|
12
|
+
end
|
13
|
+
|
14
|
+
def spec_uri
|
15
|
+
"https://en.wikipedia.org/wiki/ISO_4217"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative "attribute"
|
2
|
+
|
3
|
+
module Tomyum
|
4
|
+
module Attributes
|
5
|
+
class Number < Attribute
|
6
|
+
self.serializer = ->(v) { v.to_s.include?(".") ? v.to_f : v.to_i }
|
7
|
+
|
8
|
+
RULES = {
|
9
|
+
max: {
|
10
|
+
group: :numericality,
|
11
|
+
name: :less_than_or_equal,
|
12
|
+
},
|
13
|
+
min: {
|
14
|
+
group: :numericality,
|
15
|
+
name: :greater_than_or_equal,
|
16
|
+
},
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
def native_type
|
20
|
+
"number"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require_relative "attribute"
|
2
|
+
|
3
|
+
module Tomyum
|
4
|
+
module Attributes
|
5
|
+
class Object < Attribute
|
6
|
+
class_attribute :stacks
|
7
|
+
attr_reader :attributes
|
8
|
+
|
9
|
+
self.stacks = []
|
10
|
+
|
11
|
+
def initialize(name, options = {}, &block)
|
12
|
+
super
|
13
|
+
|
14
|
+
@attributes = {}
|
15
|
+
instance_exec(&block) if block_given?
|
16
|
+
end
|
17
|
+
|
18
|
+
def native_type
|
19
|
+
"object"
|
20
|
+
end
|
21
|
+
|
22
|
+
# Apply default options to all attributes
|
23
|
+
#
|
24
|
+
# group if: :deleted? { ... }
|
25
|
+
# group unless: :deleted? { ... }
|
26
|
+
# group if: ->{ @user.deleted? } { ... }
|
27
|
+
def group(options, &block)
|
28
|
+
return unless block_given?
|
29
|
+
|
30
|
+
# Use stacks to allow nested conditions
|
31
|
+
self.class.stacks << Attribute.options
|
32
|
+
Attribute.options = options
|
33
|
+
|
34
|
+
instance_exec(&block)
|
35
|
+
|
36
|
+
# restore options
|
37
|
+
Attribute.options = self.class.stacks.pop
|
38
|
+
end
|
39
|
+
|
40
|
+
# Shortcut for creating attribute (delegate call to Registry.create)
|
41
|
+
# This allows us to access newly registered attributes
|
42
|
+
#
|
43
|
+
# string :username (or array, number etc)
|
44
|
+
def method_missing(type, name = nil, options = {}, &block)
|
45
|
+
if Attributes.registry.key?(type)
|
46
|
+
@attributes[name] = Attributes.create(type, name, options, &block)
|
47
|
+
else
|
48
|
+
super
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def respond_to_missing?(method, *)
|
53
|
+
super
|
54
|
+
end
|
55
|
+
|
56
|
+
def [](name)
|
57
|
+
@attributes[name.to_sym]
|
58
|
+
end
|
59
|
+
|
60
|
+
def []=(name, attribute)
|
61
|
+
assert_kind_of Attribute, attribute
|
62
|
+
|
63
|
+
@attributes[name.to_sym] = attribute
|
64
|
+
end
|
65
|
+
|
66
|
+
def keys
|
67
|
+
@attributes.keys
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative "attribute"
|
2
|
+
|
3
|
+
module Tomyum
|
4
|
+
module Attributes
|
5
|
+
# Reference to other schema and doesn't accept &block.
|
6
|
+
#
|
7
|
+
# Schema supports extra option named `expandable`
|
8
|
+
# which will either return `id` or `serialized object`
|
9
|
+
# as the result.
|
10
|
+
class Schema < Object
|
11
|
+
DEFAULT_EXPANDABLE = false
|
12
|
+
def initialize(name, options = {})
|
13
|
+
# automatically add name to :of if it's not given
|
14
|
+
options[:of] = name unless options.key?(:of)
|
15
|
+
@expandable = options.fetch(:expandable, DEFAULT_EXPANDABLE)
|
16
|
+
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :expandable
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative "attribute"
|
2
|
+
|
3
|
+
module Tomyum
|
4
|
+
module Attributes
|
5
|
+
class String < Attribute
|
6
|
+
self.serializer = ->(v) { v.to_s }
|
7
|
+
|
8
|
+
RULES = {
|
9
|
+
match: {
|
10
|
+
group: :format,
|
11
|
+
name: :with,
|
12
|
+
},
|
13
|
+
min: {
|
14
|
+
group: :length,
|
15
|
+
name: :minimum,
|
16
|
+
},
|
17
|
+
max: {
|
18
|
+
group: :length,
|
19
|
+
name: :maximum,
|
20
|
+
},
|
21
|
+
range: {
|
22
|
+
group: :length,
|
23
|
+
name: :in,
|
24
|
+
},
|
25
|
+
}.freeze
|
26
|
+
|
27
|
+
def native_type
|
28
|
+
"string"
|
29
|
+
end
|
30
|
+
|
31
|
+
def in
|
32
|
+
super&.map(&:to_s)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative "attribute"
|
2
|
+
|
3
|
+
module Tomyum
|
4
|
+
module Attributes
|
5
|
+
class Time < String
|
6
|
+
def format
|
7
|
+
super || "date-time"
|
8
|
+
end
|
9
|
+
|
10
|
+
def spec
|
11
|
+
"ISO 8601"
|
12
|
+
end
|
13
|
+
|
14
|
+
def spec_uri
|
15
|
+
"https://en.wikipedia.org/wiki/ISO_8601"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative "attribute"
|
2
|
+
|
3
|
+
module Tomyum
|
4
|
+
module Attributes
|
5
|
+
class URI < String
|
6
|
+
def format
|
7
|
+
super || type
|
8
|
+
end
|
9
|
+
|
10
|
+
def spec
|
11
|
+
"RFC 3986"
|
12
|
+
end
|
13
|
+
|
14
|
+
def spec_uri
|
15
|
+
"https://tools.ietf.org/html/rfc3986"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
module Tomyum
|
2
|
+
module Attributes
|
3
|
+
# An abstract Visitor for traversing {Attribute}.
|
4
|
+
#
|
5
|
+
# Sub-classes must implement the methods correspond to {Attribute} classes.
|
6
|
+
#
|
7
|
+
# @example Implements +string+ visitor for {String}
|
8
|
+
# class SimpleVisitor < Tomyum::Attributes::Visitor
|
9
|
+
# def visit_string(attribute, *args)
|
10
|
+
# "Hello #{attribute.name}"
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# # Usage
|
15
|
+
# username = Attributes.create(:string, :username)
|
16
|
+
# visitor = StringVisitor.new
|
17
|
+
# visitor.visit(username) # => "Hello username"
|
18
|
+
#
|
19
|
+
# @abstract
|
20
|
+
class Visitor
|
21
|
+
include Functions
|
22
|
+
|
23
|
+
MethodNotFound = Tomyum::Error.create("Cannot find method Visitor#%s")
|
24
|
+
|
25
|
+
# Traveres a visitable object and calls corresponding method based-on
|
26
|
+
# sub-classes' impementations.
|
27
|
+
#
|
28
|
+
# @example When +attribute+ is an instance of {Attribute}.
|
29
|
+
# object = Attributes.create(:object, :user) { }
|
30
|
+
# visit(object) # => visit_object
|
31
|
+
#
|
32
|
+
# @example When +attribute+ is a {Symbol}.
|
33
|
+
# visit(:user) # => visit_user or visit_schema(attr, of: :user)
|
34
|
+
#
|
35
|
+
# @param [Attribute|Symbol] The +Attribute+ object or +Symbol+
|
36
|
+
def visit(attribute, *args)
|
37
|
+
method, attribute = find_method!(attribute, *args)
|
38
|
+
|
39
|
+
send(method, attribute, *args)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Performs visitor logic when no corresponding method can be found (Catch-all).
|
43
|
+
def visit_attribute(attribute, options = {})
|
44
|
+
raise NotImplementedError
|
45
|
+
end
|
46
|
+
|
47
|
+
def visit_schema(attribute, options = {})
|
48
|
+
raise NotImplementedError
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Finds a vistor method.
|
54
|
+
#
|
55
|
+
# @return Array<String, Attribute> Pair of method name and +Attribute+ object
|
56
|
+
#
|
57
|
+
# It'll return a first callable method in the chains or return +nil+ when no methods can be found.
|
58
|
+
# If +attribute+ is a Symbol. It'll stop looking any further.
|
59
|
+
#
|
60
|
+
# find_method(:integer) # => `visit_integer`
|
61
|
+
#
|
62
|
+
# If it's an instance of <tt>Attribute</tt>. It'll look up in ancestors
|
63
|
+
# chain and return the first callable method.
|
64
|
+
#
|
65
|
+
# object = Attributes.create(:object, :name) { ... }
|
66
|
+
# find_method(object) => `visit_object`
|
67
|
+
def find_method(attribute, *args)
|
68
|
+
compose(
|
69
|
+
curry(:resolve_methods),
|
70
|
+
curry(:respond_to_visitor?),
|
71
|
+
curry(:normalize_symbol, attribute, args)
|
72
|
+
).(attribute)
|
73
|
+
end
|
74
|
+
|
75
|
+
def find_method!(attribute, *args)
|
76
|
+
method, attribute = find_method(attribute, *args)
|
77
|
+
|
78
|
+
raise MethodNotFound, normalize_method(attribute) unless method
|
79
|
+
|
80
|
+
[method, attribute]
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns list of method Symbols from given +Attribute+
|
84
|
+
#
|
85
|
+
# @return [Symbol] Method symbols e.g. [:string, String, Attribute]
|
86
|
+
def resolve_methods(attribute)
|
87
|
+
return [attribute] if attribute.kind_of?(Symbol)
|
88
|
+
|
89
|
+
# Finds all ancestors in hierarchy up to `Attribute`
|
90
|
+
parents = attribute.class.ancestors.map(&:to_s)
|
91
|
+
parents.slice(0, parents.index(Attribute.name).to_i + 1)
|
92
|
+
end
|
93
|
+
|
94
|
+
def normalize_method(method)
|
95
|
+
return unless method
|
96
|
+
|
97
|
+
# Cannot use `chomp("Attribute")` here, because the top most class is `Attribute`
|
98
|
+
"visit_" + method.to_s.demodulize.gsub(/(\w+)Attribute/, "\\1").underscore
|
99
|
+
end
|
100
|
+
|
101
|
+
# Creates Attribute from symbol
|
102
|
+
# e.g. :integer to Integer
|
103
|
+
def normalize_attribute(attribute, options = {})
|
104
|
+
return attribute unless Attributes.key?(attribute)
|
105
|
+
|
106
|
+
Attributes.create(attribute, attribute.to_s, options)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Finds method from method list that respond_to `visit_{attribute}` call
|
110
|
+
def respond_to_visitor?(methods)
|
111
|
+
methods.find { |m| respond_to?(normalize_method(m)) }
|
112
|
+
end
|
113
|
+
|
114
|
+
def normalize_symbol(attribute, args, method)
|
115
|
+
# When we can't find method e.g. `visit_{method}`
|
116
|
+
# and `attribute` is a symbol (e.g. :user),
|
117
|
+
# we'll enforce the same logic for all visitor sub-classes
|
118
|
+
# by calling `visit_schema` with of: attribute
|
119
|
+
if !method && attribute.kind_of?(Symbol)
|
120
|
+
method, attribute = normalize_schema_symbol(attribute, *args)
|
121
|
+
end
|
122
|
+
|
123
|
+
[
|
124
|
+
normalize_method(method),
|
125
|
+
normalize_attribute(attribute, *args),
|
126
|
+
]
|
127
|
+
end
|
128
|
+
|
129
|
+
def normalize_schema_symbol(schema, *args)
|
130
|
+
args.last[:of] = schema if args.last.is_a?(Hash)
|
131
|
+
|
132
|
+
[:schema, :schema]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|