respect 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +289 -0
  3. data/RELATED_WORK.md +40 -0
  4. data/RELEASE_NOTES.md +23 -0
  5. data/Rakefile +31 -0
  6. data/STATUS_MATRIX.html +137 -0
  7. data/lib/respect.rb +231 -0
  8. data/lib/respect/any_schema.rb +22 -0
  9. data/lib/respect/array_def.rb +28 -0
  10. data/lib/respect/array_schema.rb +203 -0
  11. data/lib/respect/boolean_schema.rb +32 -0
  12. data/lib/respect/composite_schema.rb +86 -0
  13. data/lib/respect/core_statements.rb +206 -0
  14. data/lib/respect/datetime_schema.rb +27 -0
  15. data/lib/respect/def_without_name.rb +6 -0
  16. data/lib/respect/divisible_by_validator.rb +20 -0
  17. data/lib/respect/doc_helper.rb +24 -0
  18. data/lib/respect/doc_parser.rb +37 -0
  19. data/lib/respect/dsl_dumper.rb +181 -0
  20. data/lib/respect/equal_to_validator.rb +20 -0
  21. data/lib/respect/fake_name_proxy.rb +116 -0
  22. data/lib/respect/float_schema.rb +27 -0
  23. data/lib/respect/format_validator.rb +136 -0
  24. data/lib/respect/global_def.rb +79 -0
  25. data/lib/respect/greater_than_or_equal_to_validator.rb +19 -0
  26. data/lib/respect/greater_than_validator.rb +19 -0
  27. data/lib/respect/has_constraints.rb +34 -0
  28. data/lib/respect/hash_def.rb +40 -0
  29. data/lib/respect/hash_schema.rb +218 -0
  30. data/lib/respect/in_validator.rb +19 -0
  31. data/lib/respect/integer_schema.rb +27 -0
  32. data/lib/respect/ip_addr_schema.rb +23 -0
  33. data/lib/respect/ipv4_addr_schema.rb +27 -0
  34. data/lib/respect/ipv6_addr_schema.rb +27 -0
  35. data/lib/respect/items_def.rb +21 -0
  36. data/lib/respect/json_schema_html_formatter.rb +143 -0
  37. data/lib/respect/less_than_or_equal_to_validator.rb +19 -0
  38. data/lib/respect/less_than_validator.rb +19 -0
  39. data/lib/respect/match_validator.rb +19 -0
  40. data/lib/respect/max_length_validator.rb +20 -0
  41. data/lib/respect/min_length_validator.rb +20 -0
  42. data/lib/respect/multiple_of_validator.rb +10 -0
  43. data/lib/respect/null_schema.rb +26 -0
  44. data/lib/respect/numeric_schema.rb +33 -0
  45. data/lib/respect/org3_dumper.rb +213 -0
  46. data/lib/respect/regexp_schema.rb +19 -0
  47. data/lib/respect/schema.rb +285 -0
  48. data/lib/respect/schema_def.rb +16 -0
  49. data/lib/respect/string_schema.rb +21 -0
  50. data/lib/respect/unit_test_helper.rb +37 -0
  51. data/lib/respect/uri_schema.rb +23 -0
  52. data/lib/respect/utc_time_schema.rb +17 -0
  53. data/lib/respect/validator.rb +51 -0
  54. data/lib/respect/version.rb +3 -0
  55. data/test/any_schema_test.rb +79 -0
  56. data/test/array_def_test.rb +113 -0
  57. data/test/array_schema_test.rb +487 -0
  58. data/test/boolean_schema_test.rb +89 -0
  59. data/test/composite_schema_test.rb +30 -0
  60. data/test/datetime_schema_test.rb +83 -0
  61. data/test/doc_helper_test.rb +34 -0
  62. data/test/doc_parser_test.rb +109 -0
  63. data/test/dsl_dumper_test.rb +395 -0
  64. data/test/fake_name_proxy_test.rb +138 -0
  65. data/test/float_schema_test.rb +146 -0
  66. data/test/format_validator_test.rb +224 -0
  67. data/test/hash_def_test.rb +126 -0
  68. data/test/hash_schema_test.rb +613 -0
  69. data/test/integer_schema_test.rb +142 -0
  70. data/test/ip_addr_schema_test.rb +78 -0
  71. data/test/ipv4_addr_schema_test.rb +71 -0
  72. data/test/ipv6_addr_schema_test.rb +71 -0
  73. data/test/json_schema_html_formatter_test.rb +214 -0
  74. data/test/null_schema_test.rb +46 -0
  75. data/test/numeric_schema_test.rb +294 -0
  76. data/test/org3_dumper_test.rb +784 -0
  77. data/test/regexp_schema_test.rb +54 -0
  78. data/test/respect_test.rb +108 -0
  79. data/test/schema_def_test.rb +405 -0
  80. data/test/schema_test.rb +290 -0
  81. data/test/string_schema_test.rb +209 -0
  82. data/test/support/circle.rb +11 -0
  83. data/test/support/color.rb +24 -0
  84. data/test/support/point.rb +11 -0
  85. data/test/support/respect/circle_schema.rb +16 -0
  86. data/test/support/respect/color_def.rb +19 -0
  87. data/test/support/respect/color_schema.rb +33 -0
  88. data/test/support/respect/point_schema.rb +19 -0
  89. data/test/support/respect/rgba_schema.rb +20 -0
  90. data/test/support/respect/universal_validator.rb +25 -0
  91. data/test/support/respect/user_macros.rb +12 -0
  92. data/test/support/rgba.rb +11 -0
  93. data/test/test_helper.rb +90 -0
  94. data/test/uri_schema_test.rb +54 -0
  95. data/test/utc_time_schema_test.rb +63 -0
  96. data/test/validator_test.rb +22 -0
  97. metadata +288 -0
@@ -0,0 +1,23 @@
1
+ require 'ipaddr'
2
+
3
+ module Respect
4
+ # Validate a string containing an IPv4 or IPv6 address but also accept IPAddr object.
5
+ class IPAddrSchema < StringSchema
6
+
7
+ def validate_type(object)
8
+ case object
9
+ when NilClass
10
+ if allow_nil?
11
+ nil
12
+ else
13
+ raise ValidationError, "object is nil but this #{self.class} does not allow nil"
14
+ end
15
+ when IPAddr
16
+ object
17
+ else
18
+ FormatValidator.new(:ip_addr).validate(object)
19
+ end
20
+ end
21
+
22
+ end # class IPAddrSchema
23
+ end # module Respect
@@ -0,0 +1,27 @@
1
+ require 'ipaddr'
2
+
3
+ module Respect
4
+ # Validate a string containing an IPv4 address but also accept IPAddr object.
5
+ class Ipv4AddrSchema < StringSchema
6
+
7
+ def validate_type(object)
8
+ case object
9
+ when NilClass
10
+ if allow_nil?
11
+ nil
12
+ else
13
+ raise ValidationError, "object is nil but this #{self.class} does not allow nil"
14
+ end
15
+ when IPAddr
16
+ if object.ipv4?
17
+ object
18
+ else
19
+ raise ValidationError, "IPAddr object '#{object}' is not IPv4"
20
+ end
21
+ else
22
+ FormatValidator.new(:ipv4_addr).validate(object)
23
+ end
24
+ end
25
+
26
+ end # class Ipv4AddrSchema
27
+ end # module Respect
@@ -0,0 +1,27 @@
1
+ require 'ipaddr'
2
+
3
+ module Respect
4
+ # Validate a string containing an IPv6 address but also accept IPAddr object.
5
+ class Ipv6AddrSchema < StringSchema
6
+
7
+ def validate_type(object)
8
+ case object
9
+ when NilClass
10
+ if allow_nil?
11
+ nil
12
+ else
13
+ raise ValidationError, "object is nil but this #{self.class} does not allow nil"
14
+ end
15
+ when IPAddr
16
+ if object.ipv6?
17
+ object
18
+ else
19
+ raise ValidationError, "IPAddr object '#{object}' is not IPv6"
20
+ end
21
+ else
22
+ FormatValidator.new(:ipv6_addr).validate(object)
23
+ end
24
+ end
25
+
26
+ end # class Ipv6AddrSchema
27
+ end # module Respect
@@ -0,0 +1,21 @@
1
+ module Respect
2
+ class ItemsDef < GlobalDef
3
+ include_core_statements
4
+ include DefWithoutName
5
+
6
+ def initialize
7
+ @items = []
8
+ end
9
+
10
+ private
11
+
12
+ def evaluation_result
13
+ @items
14
+ end
15
+
16
+ def update_context(name, schema)
17
+ @items << schema
18
+ schema
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,143 @@
1
+ module Respect
2
+ # Format a JSON schema in HTML with CSS class to allow highlighting.
3
+ class JSONSchemaHTMLFormatter
4
+ def initialize(json_schema)
5
+ @indent_level = 0
6
+ @indent_size = 2
7
+ @json_schema = json_schema
8
+ @css_class ||= {
9
+ json_highlight: "json_highlight",
10
+ plain: "plain",
11
+ key: "key",
12
+ keyword: "keyword",
13
+ string: "string",
14
+ numeric: "numeric",
15
+ comment: "comment",
16
+ }
17
+ end
18
+
19
+ attr_accessor :css_class
20
+
21
+ def dump(output = "")
22
+ @output = output
23
+ @output ||= String.new
24
+ @output << "<div class=\"#{css_class[:json_highlight]}\"><pre>"
25
+ @output << dump_json(@json_schema)
26
+ @output << "</pre></div>\n"
27
+ @output
28
+ end
29
+
30
+ private
31
+
32
+ def indent(&block)
33
+ @indent_level += 1
34
+ block.call
35
+ @indent_level -= 1
36
+ end
37
+
38
+ def newline
39
+ "\n#{indentation}"
40
+ end
41
+
42
+ def indentation
43
+ " " * @indent_level * @indent_size
44
+ end
45
+
46
+ def dump_json(json)
47
+ case json
48
+ when Hash
49
+ dump_hash(json)
50
+ when Array
51
+ dump_array(json)
52
+ else
53
+ dump_terminal(json)
54
+ end
55
+ end
56
+
57
+ def dump_hash(json)
58
+ result = plain_text("{")
59
+ indent do
60
+ result << newline
61
+ keys = json.keys
62
+ keys.each_with_index do |key, i|
63
+ if json[key].is_a? Hash
64
+ doc = ""
65
+ if json[key].key? "title"
66
+ doc << json[key]["title"]
67
+ json[key].delete("title")
68
+ end
69
+ if json[key].key? "description"
70
+ doc << "\n\n"
71
+ doc << json[key]["description"]
72
+ json[key].delete("description")
73
+ end
74
+ unless doc.empty?
75
+ result << comment(doc)
76
+ result << newline
77
+ end
78
+ end
79
+ result << span(css_class[:key], key.to_s.inspect) << plain_text(":") << " "
80
+ result << dump_json(json[key])
81
+ if i < keys.size - 1
82
+ result << plain_text(",")
83
+ result << newline
84
+ end
85
+ end
86
+ end
87
+ result << newline
88
+ result << plain_text("}")
89
+ result
90
+ end
91
+
92
+ def dump_array(json)
93
+ result = plain_text("[")
94
+ indent do
95
+ result << newline
96
+ json.each_with_index do |item, i|
97
+ result << dump_json(item)
98
+ if i < json.size - 1
99
+ result << plain_text(",")
100
+ result << newline
101
+ end
102
+ end
103
+ end
104
+ result << newline
105
+ result << plain_text("]")
106
+ result
107
+ end
108
+
109
+ def dump_terminal(json)
110
+ css = (case json
111
+ when TrueClass, FalseClass
112
+ css_class[:keyword]
113
+ when String
114
+ css_class[:string]
115
+ when Numeric
116
+ css_class[:numeric]
117
+ else
118
+ css_class[:plain]
119
+ end)
120
+ span(css, json.inspect)
121
+ end
122
+
123
+ def plain_text(text)
124
+ span(css_class[:plain], text)
125
+ end
126
+
127
+ def tag(tag, klass, value)
128
+ "<#{tag} class=\"#{klass}\">#{value}</#{tag}>"
129
+ end
130
+
131
+ def span(klass, value)
132
+ tag("span", klass, value)
133
+ end
134
+
135
+ def comment(text)
136
+ s = text.dup
137
+ s.sub!(/\n*\Z/m, '')
138
+ s.gsub!(/\n/m, "\n#{indentation}// ")
139
+ s.gsub!(/\s*\n/m, "\n")
140
+ span(css_class[:comment], "// #{s}")
141
+ end
142
+ end
143
+ end # module Respect
@@ -0,0 +1,19 @@
1
+ module Respect
2
+ class LessThanOrEqualToValidator < Validator
3
+ def initialize(max)
4
+ @max = max
5
+ end
6
+
7
+ def validate(value)
8
+ unless value <= @max
9
+ raise ValidationError, "#{value} is not less than or equal to #@max"
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def to_h_org3
16
+ { "maximum" => @max }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module Respect
2
+ class LessThanValidator < Validator
3
+ def initialize(max)
4
+ @max = max
5
+ end
6
+
7
+ def validate(value)
8
+ unless value < @max
9
+ raise ValidationError, "#{value} is not less than #@max"
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def to_h_org3
16
+ { "maximum" => @max, "exclusiveMaximum" => true }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module Respect
2
+ class MatchValidator < Validator
3
+ def initialize(pattern)
4
+ @pattern = pattern
5
+ end
6
+
7
+ def validate(value)
8
+ unless value =~ @pattern
9
+ raise ValidationError, "#{value.inspect} does not match #{@pattern.inspect}"
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def to_h_org3
16
+ { "pattern" => @pattern.source }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ module Respect
2
+ class MaxLengthValidator < Validator
3
+ def initialize(max_length)
4
+ @max_length = max_length
5
+ end
6
+
7
+ def validate(value)
8
+ unless value.length <= @max_length
9
+ raise ValidationError,
10
+ "#{value.inspect} must be at most #@max_length long but is #{value.length}"
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def to_h_org3
17
+ { 'maxLength' => @max_length }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module Respect
2
+ class MinLengthValidator < Validator
3
+ def initialize(min_length)
4
+ @min_length = min_length
5
+ end
6
+
7
+ def validate(value)
8
+ unless value.length >= @min_length
9
+ raise ValidationError,
10
+ "#{value.inspect} must be at least #@min_length long but is #{value.length}"
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def to_h_org3
17
+ { 'minLength' => @min_length }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ module Respect
2
+ class MultipleOfValidator < DivisibleByValidator
3
+ def validate(value)
4
+ super
5
+ rescue ValidationError => e
6
+ raise ValidationError,
7
+ e.message.sub(/\bdivisible by\b/, "a multiple of")
8
+ end
9
+ end
10
+ end # module Respect
@@ -0,0 +1,26 @@
1
+ module Respect
2
+ class NullSchema < Schema
3
+
4
+ public_class_method :new
5
+
6
+ def validate(object)
7
+ case object
8
+ when String
9
+ if object == "null"
10
+ self.sanitized_object = nil
11
+ true
12
+ else
13
+ raise ValidationError,
14
+ "expected 'null' value but got '#{object}:#{object.class}'"
15
+ end
16
+ when NilClass
17
+ self.sanitized_object = nil
18
+ true
19
+ else
20
+ raise ValidationError,
21
+ "object is not of null type but a #{object.class}"
22
+ end
23
+ end
24
+
25
+ end # class NullSchema
26
+ end # module Respect
@@ -0,0 +1,33 @@
1
+ module Respect
2
+ class NumericSchema < Schema
3
+ include HasConstraints
4
+
5
+ public_class_method :new
6
+
7
+ def validate_type(object)
8
+ case object
9
+ when String
10
+ if match_data = /^[-+]?\d+(\.\d+)?$/.match(object)
11
+ if match_data[1]
12
+ object.to_f
13
+ else
14
+ object.to_i
15
+ end
16
+ else
17
+ raise ValidationError, "malformed numeric value: `#{object}'"
18
+ end
19
+ when Integer, Float
20
+ object
21
+ when NilClass
22
+ if allow_nil?
23
+ nil
24
+ else
25
+ raise ValidationError, "object is nil but this #{self.class} does not allow nil"
26
+ end
27
+ else
28
+ raise ValidationError, "object is not a numeric but a '#{object.class}'"
29
+ end
30
+ end
31
+
32
+ end # class NumericSchema
33
+ end # module Respect
@@ -0,0 +1,213 @@
1
+ module Respect
2
+ # Dump a schema to a hash representation following the format specified
3
+ # on {json-schema.org standard draft v3}[http://tools.ietf.org/id/draft-zyp-json-schema-03.html].
4
+ #
5
+ # The current implementation covers all the _Schema_ and _Validator_ classes
6
+ # defined in this package. User-defined {Schema} and {Validator} are not guarantee
7
+ # to work and may never work in the future. The _JSON-Schema_ standard is
8
+ # a general purpose standard and include only primitive type so it is
9
+ # very unlikely that it will include your custom schema and validator
10
+ # out of the box. However, if you can translate your schema/validator
11
+ # as a composition of primitive type mentioned in the standard it will work.
12
+ # That's why it is recommended to sub-class {CompositeSchema} when creating
13
+ # your own schema. User-defined are not properly supported yet as the
14
+ # API of this dumper is *experimental*. However, an easy way to extend
15
+ # both the schema and validator class hierarchies will be added in
16
+ # future releases.
17
+ class Org3Dumper
18
+
19
+ # Translation table mapping DSL options with json-schema.org v3
20
+ # options. The associated hash is injected in the output. Values
21
+ # are interpreted as follow:
22
+ # - :option_value represent the option value passed to the DSL option parameter.
23
+ # - a proc is called with the option value as argument and the result is used
24
+ # as the value for the output key if it is not nil.
25
+ # - other value are inserted verbatim.
26
+ OPTION_MAP = {
27
+ min_size: { 'minItems' => :option_value },
28
+ max_size: { 'maxItems' => :option_value },
29
+ uniq: { "uniqueItems" => Proc.new{|v| v if v } },
30
+ default: { "default" => Proc.new{|v| v unless v.nil? } },
31
+ required: { "required" => Proc.new{|v| true if required? } },
32
+ }.freeze
33
+
34
+ def initialize(schema)
35
+ @schema = schema
36
+ end
37
+
38
+ def dump(output = nil)
39
+ @output = output
40
+ @output ||= Hash.new
41
+ @output = dump_schema(@schema, ignore: [:required])
42
+ @output
43
+ end
44
+
45
+ attr_reader :output
46
+
47
+ def dump_schema(schema, *args)
48
+ dispatch("dump_schema", schema.class, schema, *args)
49
+ end
50
+
51
+ def dump_schema_for_schema(schema, params = {})
52
+ return nil if !schema.documented?
53
+ h = {}
54
+ h['type'] = dump_statement_name(schema)
55
+ # Dump generic options.
56
+ schema.options.each do |opt, opt_value|
57
+ next if params[:ignore] && params[:ignore].include?(opt)
58
+ if validator_class = Respect.validator_for(opt)
59
+ h.merge!(validator_class.new(opt_value).to_h(:org3))
60
+ elsif Org3Dumper::OPTION_MAP.has_key?(opt)
61
+ Org3Dumper::OPTION_MAP[opt].each do |k, v|
62
+ if v == :option_value
63
+ h[k] = (opt_value.is_a?(Numeric) ? opt_value : opt_value.dup)
64
+ elsif v.is_a?(Proc)
65
+ result = schema.instance_exec(opt_value, &v)
66
+ h[k] = result unless result.nil?
67
+ else
68
+ h[k] = v
69
+ end
70
+ end
71
+ end
72
+ end
73
+ h.merge!(dump_options(schema))
74
+ # Dump documentation
75
+ h["title"] = schema.title if schema.title
76
+ h["description"] = schema.description if schema.description
77
+ h
78
+ end
79
+
80
+ def dump_schema_for_hash_schema(schema, params = {})
81
+ h = dump_schema_for_schema(schema, params)
82
+ return nil if h.nil?
83
+ props = {}
84
+ pattern_props = {}
85
+ additional_props = {}
86
+ schema.properties.each do |prop, schema|
87
+ if prop.is_a?(Regexp)
88
+ if schema.optional?
89
+ # FIXME(Nicolas Despres): Find a better warning reporting system.
90
+ warn "pattern properties cannot be optional in json-schema.org draft v3"
91
+ else
92
+ # FIXME(Nicolas Despres): What do we do with regexp options such as 'i'?
93
+ schema_dump = dump_schema(schema)
94
+ pattern_props[prop.source] = schema_dump if schema_dump
95
+ end
96
+ else
97
+ if schema.optional?
98
+ schema_dump = dump_schema(schema)
99
+ additional_props[prop.to_s] = schema_dump if schema_dump
100
+ else
101
+ schema_dump = dump_schema(schema)
102
+ props[prop.to_s] = schema_dump if schema_dump
103
+ end
104
+ end
105
+ end
106
+ h['properties'] = props unless props.empty?
107
+ h['patternProperties'] = pattern_props unless pattern_props.empty?
108
+ if additional_props.empty?
109
+ if schema.options[:strict]
110
+ h['additionalProperties'] = false
111
+ end
112
+ else
113
+ h['additionalProperties'] = additional_props
114
+ end
115
+ h
116
+ end
117
+
118
+ def dump_schema_for_array_schema(schema, params = {})
119
+ h = dump_schema_for_schema(schema, params)
120
+ return nil if h.nil?
121
+ if schema.item
122
+ h['items'] = dump_schema(schema.item, ignore: [:required])
123
+ else
124
+ if schema.items && !schema.items.empty?
125
+ h['items'] = schema.items.map do |x|
126
+ dump_schema(x, ignore: [:required])
127
+ end
128
+ end
129
+ if schema.extra_items && !schema.extra_items.empty?
130
+ h['additionalItems'] = schema.extra_items.map do |x|
131
+ dump_schema(x, ignore: [:required])
132
+ end
133
+ end
134
+ end
135
+ h
136
+ end
137
+
138
+ def dump_schema_for_composite_schema(schema, params = {})
139
+ dump_schema(schema.schema, params)
140
+ end
141
+
142
+ def dump_statement_name(schema, *args)
143
+ dispatch("dump_statement_name", schema.class, schema, *args)
144
+ end
145
+
146
+ def dump_statement_name_for_schema(schema)
147
+ schema.class.statement_name
148
+ end
149
+
150
+ def dump_statement_name_for_hash_schema(schema)
151
+ "object"
152
+ end
153
+
154
+ def dump_statement_name_for_numeric_schema(schema)
155
+ "number"
156
+ end
157
+
158
+ def dump_statement_name_for_integer_schema(schema)
159
+ "integer"
160
+ end
161
+
162
+ def dump_statement_name_for_string_schema(schema)
163
+ "string"
164
+ end
165
+
166
+ def dump_options(schema, *args)
167
+ dispatch("dump_options", schema.class, schema, *args)
168
+ end
169
+
170
+ def dump_options_for_schema(schema)
171
+ {}
172
+ end
173
+
174
+ def dump_options_for_uri_schema(schema)
175
+ { "format" => "uri" }
176
+ end
177
+
178
+ def dump_options_for_regexp_schema(schema)
179
+ { "format" => "regex" }
180
+ end
181
+
182
+ def dump_options_for_datetime_schema(schema)
183
+ { "format" => "date-time" }
184
+ end
185
+
186
+ def dump_options_for_ipv4_addr_schema(schema)
187
+ { "format" => "ip-address" }
188
+ end
189
+
190
+ def dump_options_for_ipv6_addr_schema(schema)
191
+ { "format" => "ipv6" }
192
+ end
193
+
194
+ private
195
+
196
+ # Perform a virtual dispatch on a single object.
197
+ # FIXME(Nicolas Despres): Get me out of here and test me.
198
+ def dispatch(prefix, klass, object, *args, &block)
199
+ symbol = "#{prefix}_for_#{klass.name.demodulize.underscore}"
200
+ if respond_to? symbol
201
+ send(symbol, object, *args, &block)
202
+ else
203
+ if klass == BasicObject
204
+ raise NoMethodError, "undefined method '#{symbol}' for schema class #{object.class}"
205
+ else
206
+ dispatch(prefix, klass.superclass, object, *args, &block)
207
+ end
208
+ end
209
+ end
210
+
211
+ end
212
+
213
+ end