graphql-client 0.9.0 → 0.10.0
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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/graphql/client.rb +20 -10
- data/lib/graphql/client/definition.rb +116 -23
- data/lib/graphql/client/deprecation.rb +2 -2
- data/lib/graphql/client/document_types.rb +3 -0
- data/lib/graphql/client/error.rb +3 -0
- data/lib/graphql/client/response.rb +2 -2
- data/lib/graphql/client/schema.rb +106 -0
- data/lib/graphql/client/schema/base_type.rb +39 -0
- data/lib/graphql/client/schema/enum_type.rb +56 -0
- data/lib/graphql/client/schema/include_directive.rb +44 -0
- data/lib/graphql/client/schema/interface_type.rb +31 -0
- data/lib/graphql/client/schema/list_type.rb +57 -0
- data/lib/graphql/client/schema/non_null_type.rb +52 -0
- data/lib/graphql/client/schema/object_type.rb +162 -0
- data/lib/graphql/client/schema/possible_types.rb +55 -0
- data/lib/graphql/client/schema/scalar_type.rb +47 -0
- data/lib/graphql/client/schema/skip_directive.rb +44 -0
- data/lib/graphql/client/schema/union_type.rb +31 -0
- metadata +14 -3
- data/lib/graphql/client/query_result.rb +0 -402
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "graphql/client/schema/base_type"
|
4
|
+
|
5
|
+
module GraphQL
|
6
|
+
class Client
|
7
|
+
module Schema
|
8
|
+
class ScalarType
|
9
|
+
include BaseType
|
10
|
+
|
11
|
+
# Internal: Construct type wrapper from another GraphQL::BaseType.
|
12
|
+
#
|
13
|
+
# type - GraphQL::BaseType instance
|
14
|
+
def initialize(type)
|
15
|
+
unless type.is_a?(GraphQL::ScalarType)
|
16
|
+
raise "expected type to be a GraphQL::ScalarType, but was #{type.class}"
|
17
|
+
end
|
18
|
+
|
19
|
+
@type = type
|
20
|
+
end
|
21
|
+
|
22
|
+
def define_class(definition, irep_node)
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
# Internal: Cast raw JSON value to Ruby scalar object.
|
27
|
+
#
|
28
|
+
# value - JSON value
|
29
|
+
# errors - Errors instance
|
30
|
+
#
|
31
|
+
# Returns casted Object.
|
32
|
+
def cast(value, _errors = nil)
|
33
|
+
case value
|
34
|
+
when NilClass
|
35
|
+
nil
|
36
|
+
else
|
37
|
+
if type.respond_to?(:coerce_isolated_input)
|
38
|
+
type.coerce_isolated_input(value)
|
39
|
+
else
|
40
|
+
type.coerce_input(value)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "graphql/client/schema/base_type"
|
4
|
+
|
5
|
+
module GraphQL
|
6
|
+
class Client
|
7
|
+
module Schema
|
8
|
+
class SkipDirective
|
9
|
+
include BaseType
|
10
|
+
|
11
|
+
# Internal: Construct list wrapper from other BaseType.
|
12
|
+
#
|
13
|
+
# of_klass - BaseType instance
|
14
|
+
def initialize(of_klass)
|
15
|
+
unless of_klass.is_a?(BaseType)
|
16
|
+
raise TypeError, "expected #{of_klass.inspect} to be a #{BaseType}"
|
17
|
+
end
|
18
|
+
|
19
|
+
@of_klass = of_klass
|
20
|
+
end
|
21
|
+
|
22
|
+
# Internal: Get wrapped klass.
|
23
|
+
#
|
24
|
+
# Returns BaseType instance.
|
25
|
+
attr_reader :of_klass
|
26
|
+
|
27
|
+
# Internal: Cast JSON value to wrapped value.
|
28
|
+
#
|
29
|
+
# values - JSON value
|
30
|
+
# errors - Errors instance
|
31
|
+
#
|
32
|
+
# Returns List instance or nil.
|
33
|
+
def cast(value, errors)
|
34
|
+
case value
|
35
|
+
when NilClass
|
36
|
+
nil
|
37
|
+
else
|
38
|
+
of_klass.cast(value, errors)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "graphql/client/schema/possible_types"
|
4
|
+
|
5
|
+
module GraphQL
|
6
|
+
class Client
|
7
|
+
module Schema
|
8
|
+
class UnionType < Module
|
9
|
+
include BaseType
|
10
|
+
|
11
|
+
def initialize(type)
|
12
|
+
unless type.is_a?(GraphQL::UnionType)
|
13
|
+
raise "expected type to be a GraphQL::UnionType, but was #{type.class}"
|
14
|
+
end
|
15
|
+
|
16
|
+
@type = type
|
17
|
+
end
|
18
|
+
|
19
|
+
def new(types)
|
20
|
+
PossibleTypes.new(type, types)
|
21
|
+
end
|
22
|
+
|
23
|
+
def define_class(definition, irep_node)
|
24
|
+
new(irep_node.typed_children.keys.map { |ctype|
|
25
|
+
schema_module.const_get(ctype.name).define_class(definition, irep_node)
|
26
|
+
})
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-05-
|
11
|
+
date: 2017-05-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -173,10 +173,21 @@ files:
|
|
173
173
|
- lib/graphql/client/list.rb
|
174
174
|
- lib/graphql/client/log_subscriber.rb
|
175
175
|
- lib/graphql/client/operation_definition.rb
|
176
|
-
- lib/graphql/client/query_result.rb
|
177
176
|
- lib/graphql/client/query_typename.rb
|
178
177
|
- lib/graphql/client/railtie.rb
|
179
178
|
- lib/graphql/client/response.rb
|
179
|
+
- lib/graphql/client/schema.rb
|
180
|
+
- lib/graphql/client/schema/base_type.rb
|
181
|
+
- lib/graphql/client/schema/enum_type.rb
|
182
|
+
- lib/graphql/client/schema/include_directive.rb
|
183
|
+
- lib/graphql/client/schema/interface_type.rb
|
184
|
+
- lib/graphql/client/schema/list_type.rb
|
185
|
+
- lib/graphql/client/schema/non_null_type.rb
|
186
|
+
- lib/graphql/client/schema/object_type.rb
|
187
|
+
- lib/graphql/client/schema/possible_types.rb
|
188
|
+
- lib/graphql/client/schema/scalar_type.rb
|
189
|
+
- lib/graphql/client/schema/skip_directive.rb
|
190
|
+
- lib/graphql/client/schema/union_type.rb
|
180
191
|
- lib/graphql/client/view_module.rb
|
181
192
|
- lib/graphql/language/nodes/deep_freeze_ext.rb
|
182
193
|
- lib/rubocop/cop/graphql/heredoc.rb
|
@@ -1,402 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
require "active_support/inflector"
|
3
|
-
require "graphql"
|
4
|
-
require "graphql/client/errors"
|
5
|
-
require "graphql/client/list"
|
6
|
-
require "set"
|
7
|
-
|
8
|
-
module GraphQL
|
9
|
-
class Client
|
10
|
-
# A QueryResult struct wraps data returned from a GraphQL response.
|
11
|
-
#
|
12
|
-
# Wrapping the JSON-like Hash allows access with nice Ruby accessor methods
|
13
|
-
# rather than using `obj["key"]` access.
|
14
|
-
#
|
15
|
-
# Wrappers also limit field visibility to fragment definitions.
|
16
|
-
class QueryResult
|
17
|
-
# Internal: Get QueryResult class for result of query.
|
18
|
-
#
|
19
|
-
# Returns subclass of QueryResult or nil.
|
20
|
-
def self.wrap(source_definition, node, type, name: nil)
|
21
|
-
case type
|
22
|
-
when GraphQL::NonNullType
|
23
|
-
NonNullWrapper.new(wrap(source_definition, node, type.of_type, name: name))
|
24
|
-
when GraphQL::ListType
|
25
|
-
ListWrapper.new(wrap(source_definition, node, type.of_type, name: name))
|
26
|
-
when GraphQL::ScalarType
|
27
|
-
ScalarWrapper.new(type)
|
28
|
-
when GraphQL::EnumType
|
29
|
-
EnumWrapper.new(type)
|
30
|
-
# when GraphQL::UnionType
|
31
|
-
# types = {}
|
32
|
-
#
|
33
|
-
# node.selections.each do |selection|
|
34
|
-
# case selection
|
35
|
-
# when Language::Nodes::InlineFragment
|
36
|
-
# selection_type = source_definition.document_types[selection]
|
37
|
-
# selection_wrapper = wrap(source_definition, selection, selection_type, name: name)
|
38
|
-
# if types[selection_type]
|
39
|
-
# p [:merge, selection_type]
|
40
|
-
# types[selection_type.name] |= selection_wrapper
|
41
|
-
# else
|
42
|
-
# types[selection_type.name] = selection_wrapper
|
43
|
-
# end
|
44
|
-
# end
|
45
|
-
# end
|
46
|
-
#
|
47
|
-
# UnionWrapper.new(types)
|
48
|
-
when GraphQL::ObjectType, GraphQL::InterfaceType, GraphQL::UnionType
|
49
|
-
fields = {}
|
50
|
-
|
51
|
-
node.selections.each do |selection|
|
52
|
-
case selection
|
53
|
-
when Language::Nodes::FragmentSpread
|
54
|
-
nil
|
55
|
-
when Language::Nodes::Field
|
56
|
-
field_name = selection.alias || selection.name
|
57
|
-
selection_type = source_definition.document_types[selection]
|
58
|
-
selection_type = GraphQL::STRING_TYPE if field_name == "__typename"
|
59
|
-
field_klass = wrap(source_definition, selection, selection_type, name: "#{name}[:#{field_name}]")
|
60
|
-
fields[field_name] ? fields[field_name] |= field_klass : fields[field_name] = field_klass
|
61
|
-
when Language::Nodes::InlineFragment
|
62
|
-
selection_type = source_definition.document_types[selection]
|
63
|
-
wrap(source_definition, selection, selection_type, name: name).fields.each do |fragment_name, klass|
|
64
|
-
fields[fragment_name.to_s] ? fields[fragment_name.to_s] |= klass : fields[fragment_name.to_s] = klass
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
define(name: name, type: type, source_definition: source_definition, source_node: node, fields: fields)
|
70
|
-
else
|
71
|
-
raise TypeError, "unexpected #{type.class}"
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
class UnionWrapper
|
76
|
-
def initialize(possible_types)
|
77
|
-
@possible_types = possible_types
|
78
|
-
end
|
79
|
-
|
80
|
-
def cast(value, errors = nil)
|
81
|
-
typename = value && value["__typename"]
|
82
|
-
if wrapper = @possible_types[typename]
|
83
|
-
wrapper.cast(value, errors)
|
84
|
-
else
|
85
|
-
raise TypeError, "expected union value to be #{@possible_types.keys.join(", ")}, but was #{typename}"
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def |(_other)
|
90
|
-
# XXX: How would union merge?
|
91
|
-
self
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
class ListWrapper
|
96
|
-
attr_reader :of_klass
|
97
|
-
|
98
|
-
def initialize(type)
|
99
|
-
@of_klass = type
|
100
|
-
end
|
101
|
-
|
102
|
-
def cast(value, errors)
|
103
|
-
case value
|
104
|
-
when Array
|
105
|
-
List.new(value.each_with_index.map { |e, idx|
|
106
|
-
@of_klass.cast(e, errors.filter_by_path(idx))
|
107
|
-
}, errors)
|
108
|
-
when NilClass
|
109
|
-
nil
|
110
|
-
else
|
111
|
-
raise ArgumentError, "expected list value to be an Array, but was #{value.class}"
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
def |(other)
|
116
|
-
if self.class == other.class
|
117
|
-
self.of_klass | other.of_klass
|
118
|
-
else
|
119
|
-
raise TypeError, "expected other to be a #{self.class}"
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
class NonNullWrapper
|
125
|
-
attr_reader :of_klass
|
126
|
-
|
127
|
-
def initialize(type)
|
128
|
-
@of_klass = type
|
129
|
-
end
|
130
|
-
|
131
|
-
def cast(value, errors)
|
132
|
-
case value
|
133
|
-
when NilClass
|
134
|
-
# TODO
|
135
|
-
# raise ArgumentError, "expected non-nullable value to be present"
|
136
|
-
nil
|
137
|
-
else
|
138
|
-
@of_klass.cast(value, errors)
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
def |(other)
|
143
|
-
if self.class == other.class
|
144
|
-
self.of_klass | other.of_klass
|
145
|
-
else
|
146
|
-
raise TypeError, "expected other to be a #{self.class}"
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
class EnumWrapper
|
152
|
-
def initialize(type)
|
153
|
-
@type = type
|
154
|
-
end
|
155
|
-
|
156
|
-
def cast(value, _errors = nil)
|
157
|
-
value
|
158
|
-
end
|
159
|
-
|
160
|
-
def |(_other)
|
161
|
-
# XXX: How would enums merge?
|
162
|
-
self
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
# :nodoc:
|
167
|
-
class ScalarWrapper
|
168
|
-
def initialize(type)
|
169
|
-
@type = type
|
170
|
-
end
|
171
|
-
|
172
|
-
def cast(value, _errors = nil)
|
173
|
-
if value.is_a? Array
|
174
|
-
value.map { |item|
|
175
|
-
if @type.respond_to?(:coerce_isolated_input)
|
176
|
-
@type.coerce_isolated_input(item)
|
177
|
-
else
|
178
|
-
@type.coerce_input(item)
|
179
|
-
end
|
180
|
-
}
|
181
|
-
else
|
182
|
-
if @type.respond_to?(:coerce_isolated_input)
|
183
|
-
@type.coerce_isolated_input(value)
|
184
|
-
else
|
185
|
-
@type.coerce_input(value)
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
def |(_other)
|
191
|
-
# XXX: How would scalars merge?
|
192
|
-
self
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
# Internal
|
197
|
-
def self.define(name:, type:, source_definition:, source_node:, fields: {})
|
198
|
-
Class.new(self) do
|
199
|
-
@name = name
|
200
|
-
@type = type
|
201
|
-
@source_node = source_node
|
202
|
-
@source_definition = source_definition
|
203
|
-
@fields = {}
|
204
|
-
|
205
|
-
field_readers = Set.new
|
206
|
-
|
207
|
-
fields.each do |field, klass|
|
208
|
-
@fields[field.to_sym] = klass
|
209
|
-
|
210
|
-
send :attr_reader, field
|
211
|
-
field_readers << field.to_sym
|
212
|
-
|
213
|
-
# Convert GraphQL camelcase to snake case: commitComments -> commit_comments
|
214
|
-
field_alias = ActiveSupport::Inflector.underscore(field)
|
215
|
-
send :alias_method, field_alias, field if field != field_alias
|
216
|
-
field_readers << field_alias.to_sym
|
217
|
-
|
218
|
-
class_eval <<-RUBY, __FILE__, __LINE__
|
219
|
-
def #{field_alias}?
|
220
|
-
#{field_alias} ? true : false
|
221
|
-
end
|
222
|
-
RUBY
|
223
|
-
field_readers << "#{field_alias}?".to_sym
|
224
|
-
end
|
225
|
-
|
226
|
-
assigns = @fields.map do |field, klass|
|
227
|
-
<<-RUBY
|
228
|
-
@#{field} = self.class.fields[:#{field}].cast(@data["#{field}"], @errors.filter_by_path("#{field}"))
|
229
|
-
RUBY
|
230
|
-
end
|
231
|
-
|
232
|
-
if @type.is_a?(GraphQL::ObjectType)
|
233
|
-
assigns.unshift "@__typename = self.class.type.name"
|
234
|
-
end
|
235
|
-
|
236
|
-
class_eval <<-RUBY, __FILE__, __LINE__
|
237
|
-
def initialize(data, errors = Errors.new)
|
238
|
-
@data = data
|
239
|
-
@errors = errors
|
240
|
-
|
241
|
-
#{assigns.join("\n")}
|
242
|
-
freeze
|
243
|
-
end
|
244
|
-
RUBY
|
245
|
-
|
246
|
-
if @source_definition.enforce_collocated_callers
|
247
|
-
Client.enforce_collocated_callers(self, field_readers, source_definition.source_location[0])
|
248
|
-
end
|
249
|
-
end
|
250
|
-
end
|
251
|
-
|
252
|
-
class << self
|
253
|
-
attr_reader :type
|
254
|
-
|
255
|
-
attr_reader :source_definition
|
256
|
-
|
257
|
-
attr_reader :source_node
|
258
|
-
|
259
|
-
attr_reader :fields
|
260
|
-
|
261
|
-
def schema
|
262
|
-
source_definition.schema
|
263
|
-
end
|
264
|
-
end
|
265
|
-
|
266
|
-
def self.name
|
267
|
-
@name || super || GraphQL::Client::QueryResult.name
|
268
|
-
end
|
269
|
-
|
270
|
-
def self.inspect
|
271
|
-
"#<#{name} fields=#{@fields.keys.inspect}>"
|
272
|
-
end
|
273
|
-
|
274
|
-
def self.cast(obj, errors = Errors.new)
|
275
|
-
case obj
|
276
|
-
when Hash
|
277
|
-
new(obj, errors)
|
278
|
-
when Array
|
279
|
-
obj.map { |o| new(o, errors) }
|
280
|
-
when self
|
281
|
-
obj
|
282
|
-
when QueryResult
|
283
|
-
spreads = Set.new(self.spreads(obj.class.source_node).map(&:name))
|
284
|
-
|
285
|
-
unless spreads.include?(source_node.name)
|
286
|
-
raise TypeError, "#{self.source_definition.name} is not included in #{obj.class.source_definition.name}"
|
287
|
-
end
|
288
|
-
cast(obj.to_h, obj.errors)
|
289
|
-
when NilClass
|
290
|
-
nil
|
291
|
-
else
|
292
|
-
raise TypeError, "expected #{obj.inspect} to be a Hash"
|
293
|
-
end
|
294
|
-
end
|
295
|
-
|
296
|
-
# Internal
|
297
|
-
def self.spreads(node)
|
298
|
-
node.selections.flat_map do |selection|
|
299
|
-
case selection
|
300
|
-
when Language::Nodes::FragmentSpread
|
301
|
-
selection
|
302
|
-
when Language::Nodes::InlineFragment
|
303
|
-
spreads(selection)
|
304
|
-
else
|
305
|
-
[]
|
306
|
-
end
|
307
|
-
end
|
308
|
-
end
|
309
|
-
|
310
|
-
def self.new(obj, *args)
|
311
|
-
case obj
|
312
|
-
when Hash
|
313
|
-
super
|
314
|
-
else
|
315
|
-
cast(obj, *args)
|
316
|
-
end
|
317
|
-
end
|
318
|
-
|
319
|
-
def self.|(other)
|
320
|
-
new_fields = fields.dup
|
321
|
-
other.fields.each do |name, value|
|
322
|
-
if new_fields[name]
|
323
|
-
new_fields[name] |= value
|
324
|
-
else
|
325
|
-
new_fields[name] = value
|
326
|
-
end
|
327
|
-
end
|
328
|
-
# TODO: Picking first source node seems error prone
|
329
|
-
define(name: self.name, type: self.type, source_definition: source_definition, source_node: source_node, fields: new_fields)
|
330
|
-
end
|
331
|
-
|
332
|
-
# Public: Return errors associated with data.
|
333
|
-
#
|
334
|
-
# Returns Errors collection.
|
335
|
-
attr_reader :errors
|
336
|
-
|
337
|
-
attr_reader :__typename
|
338
|
-
alias typename __typename
|
339
|
-
|
340
|
-
# Public: Returns the raw response data
|
341
|
-
#
|
342
|
-
# Returns Hash
|
343
|
-
def to_h
|
344
|
-
@data
|
345
|
-
end
|
346
|
-
|
347
|
-
def type_of?(*types)
|
348
|
-
types.any? do |type|
|
349
|
-
if type = self.class.schema.types.fetch(type.to_s, nil)
|
350
|
-
self.class.schema.possible_types(type).any? { |t| @__typename == t.name }
|
351
|
-
else
|
352
|
-
false
|
353
|
-
end
|
354
|
-
end
|
355
|
-
end
|
356
|
-
|
357
|
-
def inspect
|
358
|
-
ivars = self.class.fields.keys.map do |sym|
|
359
|
-
value = instance_variable_get("@#{sym}")
|
360
|
-
if value.is_a?(QueryResult)
|
361
|
-
"#{sym}=#<#{value.class.name}>"
|
362
|
-
else
|
363
|
-
"#{sym}=#{value.inspect}"
|
364
|
-
end
|
365
|
-
end
|
366
|
-
buf = "#<#{self.class.name}".dup
|
367
|
-
buf << " " << ivars.join(" ") if ivars.any?
|
368
|
-
buf << ">"
|
369
|
-
buf
|
370
|
-
end
|
371
|
-
|
372
|
-
def method_missing(*args)
|
373
|
-
super
|
374
|
-
rescue NoMethodError => e
|
375
|
-
type = self.class.type
|
376
|
-
raise e unless type
|
377
|
-
|
378
|
-
field = type.all_fields.find do |f|
|
379
|
-
f.name == e.name.to_s || ActiveSupport::Inflector.underscore(f.name) == e.name.to_s
|
380
|
-
end
|
381
|
-
|
382
|
-
unless field
|
383
|
-
raise UnimplementedFieldError, "undefined field `#{e.name}' on #{type} type. https://git.io/v1y3m"
|
384
|
-
end
|
385
|
-
|
386
|
-
if @data[field.name]
|
387
|
-
error_class = ImplicitlyFetchedFieldError
|
388
|
-
message = "implicitly fetched field `#{field.name}' on #{type} type. https://git.io/v1yGL"
|
389
|
-
else
|
390
|
-
error_class = UnfetchedFieldError
|
391
|
-
message = "unfetched field `#{field.name}' on #{type} type. https://git.io/v1y3U"
|
392
|
-
end
|
393
|
-
|
394
|
-
raise error_class, message
|
395
|
-
end
|
396
|
-
|
397
|
-
def respond_to_missing?(*args)
|
398
|
-
super
|
399
|
-
end
|
400
|
-
end
|
401
|
-
end
|
402
|
-
end
|