actionwebservice 0.6.2 → 0.7.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.
Files changed (58) hide show
  1. data/CHANGELOG +21 -0
  2. data/README +50 -6
  3. data/Rakefile +9 -9
  4. data/TODO +6 -6
  5. data/lib/action_web_service.rb +4 -3
  6. data/lib/action_web_service/api.rb +248 -1
  7. data/lib/action_web_service/casting.rb +111 -0
  8. data/lib/action_web_service/client/soap_client.rb +17 -33
  9. data/lib/action_web_service/client/xmlrpc_client.rb +10 -34
  10. data/lib/action_web_service/container/delegated_container.rb +1 -1
  11. data/lib/action_web_service/dispatcher/abstract.rb +52 -72
  12. data/lib/action_web_service/dispatcher/action_controller_dispatcher.rb +71 -55
  13. data/lib/action_web_service/protocol/abstract.rb +82 -3
  14. data/lib/action_web_service/protocol/discovery.rb +2 -2
  15. data/lib/action_web_service/protocol/soap_protocol.rb +95 -22
  16. data/lib/action_web_service/protocol/soap_protocol/marshaler.rb +197 -0
  17. data/lib/action_web_service/protocol/xmlrpc_protocol.rb +56 -24
  18. data/lib/action_web_service/scaffolding.rb +237 -0
  19. data/lib/action_web_service/struct.rb +17 -4
  20. data/lib/action_web_service/support/signature_types.rb +194 -0
  21. data/lib/action_web_service/templates/scaffolds/layout.rhtml +65 -0
  22. data/lib/action_web_service/templates/scaffolds/methods.rhtml +6 -0
  23. data/lib/action_web_service/templates/scaffolds/parameters.rhtml +28 -0
  24. data/lib/action_web_service/templates/scaffolds/result.rhtml +30 -0
  25. data/lib/action_web_service/test_invoke.rb +23 -42
  26. data/test/abstract_dispatcher.rb +102 -48
  27. data/test/abstract_unit.rb +1 -1
  28. data/test/api_test.rb +40 -7
  29. data/test/casting_test.rb +82 -0
  30. data/test/client_soap_test.rb +3 -0
  31. data/test/client_xmlrpc_test.rb +5 -1
  32. data/test/dispatcher_action_controller_soap_test.rb +9 -12
  33. data/test/dispatcher_action_controller_xmlrpc_test.rb +1 -11
  34. data/test/run +1 -0
  35. data/test/scaffolded_controller_test.rb +67 -0
  36. data/test/struct_test.rb +33 -21
  37. data/test/test_invoke_test.rb +1 -1
  38. metadata +18 -31
  39. data/lib/action_web_service/api/base.rb +0 -135
  40. data/lib/action_web_service/vendor/ws.rb +0 -4
  41. data/lib/action_web_service/vendor/ws/common.rb +0 -8
  42. data/lib/action_web_service/vendor/ws/encoding.rb +0 -3
  43. data/lib/action_web_service/vendor/ws/encoding/abstract.rb +0 -26
  44. data/lib/action_web_service/vendor/ws/encoding/soap_rpc_encoding.rb +0 -90
  45. data/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb +0 -53
  46. data/lib/action_web_service/vendor/ws/marshaling.rb +0 -3
  47. data/lib/action_web_service/vendor/ws/marshaling/abstract.rb +0 -17
  48. data/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb +0 -277
  49. data/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb +0 -116
  50. data/lib/action_web_service/vendor/ws/types.rb +0 -165
  51. data/test/ws/abstract_encoding.rb +0 -68
  52. data/test/ws/abstract_unit.rb +0 -13
  53. data/test/ws/gencov +0 -3
  54. data/test/ws/run +0 -5
  55. data/test/ws/soap_marshaling_test.rb +0 -91
  56. data/test/ws/soap_rpc_encoding_test.rb +0 -47
  57. data/test/ws/types_test.rb +0 -43
  58. data/test/ws/xmlrpc_encoding_test.rb +0 -34
@@ -1,3 +1,9 @@
1
+ require 'xmlrpc/marshal'
2
+
3
+ class XMLRPC::FaultException
4
+ alias :message :faultString
5
+ end
6
+
1
7
  module ActionWebService # :nodoc:
2
8
  module Protocol # :nodoc:
3
9
  module XmlRpc # :nodoc:
@@ -5,43 +11,69 @@ module ActionWebService # :nodoc:
5
11
  base.register_protocol(XmlRpcProtocol)
6
12
  end
7
13
 
8
- class XmlRpcProtocol # :nodoc:
9
- attr :marshaler
10
-
11
- def initialize
12
- @encoder = WS::Encoding::XmlRpcEncoding.new
13
- @marshaler = WS::Marshaling::XmlRpcMarshaler.new
14
+ class XmlRpcProtocol < AbstractProtocol # :nodoc:
15
+ def decode_action_pack_request(action_pack_request)
16
+ service_name = action_pack_request.parameters['action']
17
+ decode_request(action_pack_request.raw_post, service_name)
14
18
  end
15
19
 
16
- def unmarshal_request(ap_request)
17
- method_name, params = @encoder.decode_rpc_call(ap_request.raw_post)
18
- params = params.map{|x| @marshaler.unmarshal(x)}
19
- service_name = ap_request.parameters['action']
20
+ def decode_request(raw_request, service_name)
21
+ method_name, params = XMLRPC::Marshal.load_call(raw_request)
20
22
  Request.new(self, method_name, params, service_name)
21
- rescue
22
- nil
23
23
  end
24
24
 
25
- def marshal_response(method_name, return_value, signature_type)
26
- if !return_value.nil? && signature_type
27
- type_binding = @marshaler.register_type(signature_type)
28
- info = WS::ParamInfo.create(signature_type, type_binding, 0)
29
- return_value = @marshaler.marshal(WS::Param.new(return_value, info))
30
- else
31
- return_value = nil
25
+ def encode_request(method_name, params, param_types)
26
+ if param_types
27
+ params = params.dup
28
+ param_types.each_with_index{ |type, i| params[i] = value_to_xmlrpc_wire_format(params[i], type) }
32
29
  end
33
- body = @encoder.encode_rpc_response(method_name, return_value)
34
- Response.new(body, 'text/xml')
30
+ XMLRPC::Marshal.dump_call(method_name, *params)
31
+ end
32
+
33
+ def decode_response(raw_response)
34
+ [nil, XMLRPC::Marshal.load_response(raw_response)]
35
35
  end
36
36
 
37
- def register_signature_type(spec)
38
- nil
37
+ def encode_response(method_name, return_value, return_type)
38
+ return_value = true if return_value.nil?
39
+ if return_type
40
+ return_value = value_to_xmlrpc_wire_format(return_value, return_type)
41
+ end
42
+ raw_response = XMLRPC::Marshal.dump_response(return_value)
43
+ Response.new(raw_response, 'text/xml', return_value)
39
44
  end
40
45
 
41
- def protocol_client(api, protocol_name, endpoint_uri, options)
46
+ def protocol_client(api, protocol_name, endpoint_uri, options={})
42
47
  return nil unless protocol_name == :xmlrpc
43
48
  ActionWebService::Client::XmlRpc.new(api, endpoint_uri, options)
44
49
  end
50
+
51
+ def value_to_xmlrpc_wire_format(value, value_type)
52
+ if value_type.array?
53
+ value.map{ |val| value_to_xmlrpc_wire_format(val, value_type.element_type) }
54
+ else
55
+ if value.is_a?(ActionWebService::Struct)
56
+ struct = {}
57
+ value.class.members.each do |name, type|
58
+ member_value = value[name]
59
+ next if member_value.nil?
60
+ struct[name.to_s] = value_to_xmlrpc_wire_format(member_value, type)
61
+ end
62
+ struct
63
+ elsif value.is_a?(ActiveRecord::Base)
64
+ struct = {}
65
+ value.attributes.each do |key, member_value|
66
+ next if member_value.nil?
67
+ struct[key.to_s] = member_value
68
+ end
69
+ struct
70
+ elsif value.is_a?(Exception) && !value.is_a?(XMLRPC::FaultException)
71
+ XMLRPC::FaultException.new(2, value.message)
72
+ else
73
+ value
74
+ end
75
+ end
76
+ end
45
77
  end
46
78
  end
47
79
  end
@@ -0,0 +1,237 @@
1
+ require 'ostruct'
2
+ require 'uri'
3
+ require 'benchmark'
4
+ require 'pathname'
5
+
6
+ module ActionWebService
7
+ module Scaffolding # :nodoc:
8
+ class ScaffoldingError < ActionWebServiceError # :nodoc:
9
+ end
10
+
11
+ def self.append_features(base)
12
+ super
13
+ base.extend(ClassMethods)
14
+ end
15
+
16
+ # Web service invocation scaffolding provides a way to quickly invoke web service methods in a controller. The
17
+ # generated scaffold actions have default views to let you enter the method parameters and view the
18
+ # results.
19
+ #
20
+ # Example:
21
+ #
22
+ # class ApiController < ActionController
23
+ # web_service_scaffold :invoke
24
+ # end
25
+ #
26
+ # This example generates an +invoke+ action in the +ApiController+ that you can navigate to from
27
+ # your browser, select the API method, enter its parameters, and perform the invocation.
28
+ #
29
+ # If you want to customize the default views, create the following views in "app/views":
30
+ #
31
+ # * <tt>action_name/methods.rhtml</tt>
32
+ # * <tt>action_name/parameters.rhtml</tt>
33
+ # * <tt>action_name/result.rhtml</tt>
34
+ # * <tt>action_name/layout.rhtml</tt>
35
+ #
36
+ # Where <tt>action_name</tt> is the name of the action you gave to ClassMethods#web_service_scaffold.
37
+ #
38
+ # You can use the default views in <tt>RAILS_DIR/lib/action_web_service/templates/scaffolds</tt> as
39
+ # a guide.
40
+ module ClassMethods
41
+ # Generates web service invocation scaffolding for the current controller. The given action name
42
+ # can then be used as the entry point for invoking API methods from a web browser.
43
+ def web_service_scaffold(action_name)
44
+ add_template_helper(Helpers)
45
+ module_eval <<-END, __FILE__, __LINE__
46
+ def #{action_name}
47
+ if @request.method == :get
48
+ setup_invocation_assigns
49
+ render_invocation_scaffold 'methods'
50
+ end
51
+ end
52
+
53
+ def #{action_name}_method_params
54
+ if @request.method == :get
55
+ setup_invocation_assigns
56
+ render_invocation_scaffold 'parameters'
57
+ end
58
+ end
59
+
60
+ def #{action_name}_submit
61
+ if @request.method == :post
62
+ setup_invocation_assigns
63
+ protocol_name = @params['protocol'] ? @params['protocol'].to_sym : :soap
64
+ case protocol_name
65
+ when :soap
66
+ @protocol = Protocol::Soap::SoapProtocol.new
67
+ when :xmlrpc
68
+ @protocol = Protocol::XmlRpc::XmlRpcProtocol.new
69
+ end
70
+ @invocation_cgi = @request.respond_to?(:cgi) ? @request.cgi : nil
71
+ bm = Benchmark.measure do
72
+ @protocol.register_api(@scaffold_service.api)
73
+ params = @params['method_params'] ? @params['method_params'].dup : nil
74
+ params = @scaffold_method.cast_expects(params)
75
+ method_name = public_method_name(@scaffold_service.name, @scaffold_method.public_name)
76
+ @method_request_xml = @protocol.encode_request(method_name, params, @scaffold_method.expects)
77
+ new_request = @protocol.encode_action_pack_request(@scaffold_service.name, @scaffold_method.public_name, @method_request_xml)
78
+ prepare_request(new_request, @scaffold_service.name, @scaffold_method.public_name)
79
+ @request = new_request
80
+ dispatch_web_service_request
81
+ @method_response_xml = @response.body
82
+ method_name, obj = @protocol.decode_response(@method_response_xml)
83
+ return if handle_invocation_exception(obj)
84
+ @method_return_value = @scaffold_method.cast_returns(obj)
85
+ end
86
+ @method_elapsed = bm.real
87
+ add_instance_variables_to_assigns
88
+ reset_invocation_response
89
+ render_invocation_scaffold 'result'
90
+ end
91
+ end
92
+
93
+ private
94
+ def setup_invocation_assigns
95
+ @scaffold_class = self.class
96
+ @scaffold_action_name = "#{action_name}"
97
+ @scaffold_container = WebServiceModel::Container.new(self)
98
+ if @params['service'] && @params['method']
99
+ @scaffold_service = @scaffold_container.services.find{ |x| x.name == @params['service'] }
100
+ @scaffold_method = @scaffold_service.api_methods[@params['method']]
101
+ end
102
+ add_instance_variables_to_assigns
103
+ end
104
+
105
+ def render_invocation_scaffold(action)
106
+ customized_template = "\#{self.class.controller_path}/#{action_name}/\#{action}"
107
+ default_template = scaffold_path(action)
108
+ @content_for_layout = template_exists?(customized_template) ? @template.render_file(customized_template) : @template.render_file(default_template, false)
109
+ self.active_layout ? render_file(self.active_layout, "200 OK", true) : render_file(scaffold_path("layout"))
110
+ end
111
+
112
+ def scaffold_path(template_name)
113
+ Pathname.new(File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml").realpath.to_s
114
+ end
115
+
116
+ def reset_invocation_response
117
+ template = @response.template
118
+ if @invocation_cgi
119
+ @response = ::ActionController::CgiResponse.new(@invocation_cgi)
120
+ else
121
+ @response = ::ActionController::TestResponse.new
122
+ end
123
+ @response.template = template
124
+ @performed_render = false
125
+ end
126
+
127
+ def public_method_name(service_name, method_name)
128
+ if web_service_dispatching_mode == :layered && @protocol.is_a?(ActionWebService::Protocol::XmlRpc::XmlRpcProtocol)
129
+ service_name + '.' + method_name
130
+ else
131
+ method_name
132
+ end
133
+ end
134
+
135
+ def prepare_request(request, service_name, method_name)
136
+ request.parameters.update(@request.parameters)
137
+ if web_service_dispatching_mode == :layered && @protocol.is_a?(ActionWebService::Protocol::Soap::SoapProtocol)
138
+ request.env['HTTP_SOAPACTION'] = "/\#{controller_name()}/\#{service_name}/\#{method_name}"
139
+ end
140
+ end
141
+
142
+ def handle_invocation_exception(obj)
143
+ exception = nil
144
+ if obj.respond_to?(:detail) && obj.detail.respond_to?(:cause) && obj.detail.cause.is_a?(Exception)
145
+ exception = obj.detail.cause
146
+ elsif obj.is_a?(XMLRPC::FaultException)
147
+ exception = obj
148
+ end
149
+ return unless exception
150
+ reset_invocation_response
151
+ rescue_action(exception)
152
+ true
153
+ end
154
+ END
155
+ end
156
+ end
157
+
158
+ module Helpers # :nodoc:
159
+ def method_parameter_input_fields(method, type)
160
+ name = type.name.to_s
161
+ type_name = type.type
162
+ unless type_name.is_a?(Symbol)
163
+ raise "Parameter #{name}: Structured/array types not supported in scaffolding input fields yet"
164
+ end
165
+ field_name = "method_params[]"
166
+ case type_name
167
+ when :int
168
+ text_field_tag field_name
169
+ when :string
170
+ text_field_tag field_name
171
+ when :bool
172
+ radio_button_tag field_name, "True"
173
+ radio_button_tag field_name, "False"
174
+ when :float
175
+ text_field_tag field_name
176
+ when :time
177
+ select_datetime Time.now, 'name' => field_name
178
+ when :date
179
+ select_date Date.today, 'name' => field_name
180
+ end
181
+ end
182
+
183
+ def service_method_list(service)
184
+ action = @scaffold_action_name + '_method_params'
185
+ methods = service.api_methods_full.map do |desc, name|
186
+ content_tag("li", link_to(desc, :action => action, :service => service.name, :method => name))
187
+ end
188
+ content_tag("ul", methods.join("\n"))
189
+ end
190
+ end
191
+
192
+ module WebServiceModel # :nodoc:
193
+ class Container # :nodoc:
194
+ attr :services
195
+
196
+ def initialize(real_container)
197
+ @real_container = real_container
198
+ @services = []
199
+ if @real_container.class.web_service_dispatching_mode == :direct
200
+ @services << Service.new(@real_container.controller_name, @real_container)
201
+ else
202
+ @real_container.class.web_services.each do |name, obj|
203
+ @services << Service.new(name, @real_container.instance_eval{ web_service_object(name) })
204
+ end
205
+ end
206
+ end
207
+ end
208
+
209
+ class Service # :nodoc:
210
+ attr :name
211
+ attr :object
212
+ attr :api
213
+ attr :api_methods
214
+ attr :api_methods_full
215
+
216
+ def initialize(name, real_service)
217
+ @name = name.to_s
218
+ @object = real_service
219
+ @api = @object.class.web_service_api
220
+ if @api.nil?
221
+ raise ScaffoldingError, "No web service API attached to #{object.class}"
222
+ end
223
+ @api_methods = {}
224
+ @api_methods_full = []
225
+ @api.api_methods.each do |name, method|
226
+ @api_methods[method.public_name.to_s] = method
227
+ @api_methods_full << [method.to_s, method.public_name.to_s]
228
+ end
229
+ end
230
+
231
+ def to_s
232
+ self.name.camelize
233
+ end
234
+ end
235
+ end
236
+ end
237
+ end
@@ -16,15 +16,15 @@ module ActionWebService
16
16
  # end
17
17
  # person = Person.new(:id => 5, :firstname => 'john', :lastname => 'doe')
18
18
  #
19
- # Active Record model classes are already implicitly supported for method
20
- # return signatures.
19
+ # Active Record model classes are already implicitly supported in method
20
+ # signatures.
21
21
  class Struct
22
22
 
23
23
  # If a Hash is given as argument to an ActionWebService::Struct constructor,
24
24
  # it can contain initial values for the structure member.
25
25
  def initialize(values={})
26
26
  if values.is_a?(Hash)
27
- values.map{|k,v| send('%s=' % k.to_s, v)}
27
+ values.map{|k,v| __send__('%s=' % k.to_s, v)}
28
28
  end
29
29
  end
30
30
 
@@ -33,11 +33,20 @@ module ActionWebService
33
33
  send(name.to_s)
34
34
  end
35
35
 
36
+ # Iterates through each member
37
+ def each_pair(&block)
38
+ self.class.members.each do |name, type|
39
+ yield name, self.__send__(name)
40
+ end
41
+ end
42
+
36
43
  class << self
37
44
  # Creates a structure member with the specified +name+ and +type+. Generates
38
45
  # accessor methods for reading and writing the member value.
39
46
  def member(name, type)
40
- write_inheritable_hash("struct_members", name => WS::BaseTypes.canonical_param_type_class(type))
47
+ name = name.to_sym
48
+ type = ActionWebService::SignatureTypes.canonical_signature_entry({ name => type }, 0)
49
+ write_inheritable_hash("struct_members", name => type)
41
50
  class_eval <<-END
42
51
  def #{name}; @#{name}; end
43
52
  def #{name}=(value); @#{name} = value; end
@@ -47,6 +56,10 @@ module ActionWebService
47
56
  def members # :nodoc:
48
57
  read_inheritable_attribute("struct_members") || {}
49
58
  end
59
+
60
+ def member_type(name) # :nodoc:
61
+ members[name.to_sym]
62
+ end
50
63
  end
51
64
  end
52
65
  end
@@ -0,0 +1,194 @@
1
+ module ActionWebService # :nodoc:
2
+ module SignatureTypes # :nodoc:
3
+ def canonical_signature(signature)
4
+ return nil if signature.nil?
5
+ unless signature.is_a?(Array)
6
+ raise(ActionWebServiceError, "Expected signature to be an Array")
7
+ end
8
+ i = -1
9
+ signature.map{ |spec| canonical_signature_entry(spec, i += 1) }
10
+ end
11
+
12
+ def canonical_signature_entry(spec, i)
13
+ orig_spec = spec
14
+ name = "param#{i}"
15
+ if spec.is_a?(Hash)
16
+ name, spec = spec.keys.first, spec.values.first
17
+ end
18
+ type = spec
19
+ if spec.is_a?(Array)
20
+ ArrayType.new(orig_spec, canonical_signature_entry(spec[0], 0), name)
21
+ else
22
+ type = canonical_type(type)
23
+ if type.is_a?(Symbol)
24
+ BaseType.new(orig_spec, type, name)
25
+ else
26
+ StructuredType.new(orig_spec, type, name)
27
+ end
28
+ end
29
+ end
30
+
31
+ def canonical_type(type)
32
+ type_name = symbol_name(type) || class_to_type_name(type)
33
+ type = type_name || type
34
+ return canonical_type_name(type) if type.is_a?(Symbol)
35
+ type
36
+ end
37
+
38
+ def canonical_type_name(name)
39
+ name = name.to_sym
40
+ case name
41
+ when :int, :integer, :fixnum, :bignum
42
+ :int
43
+ when :string, :base64
44
+ :string
45
+ when :bool, :boolean
46
+ :bool
47
+ when :float, :double
48
+ :float
49
+ when :time, :timestamp
50
+ :time
51
+ when :datetime
52
+ :datetime
53
+ when :date
54
+ :date
55
+ else
56
+ raise(TypeError, "#{name} is not a valid base type")
57
+ end
58
+ end
59
+
60
+ def canonical_type_class(type)
61
+ type = canonical_type(type)
62
+ type.is_a?(Symbol) ? type_name_to_class(type) : type
63
+ end
64
+
65
+ def symbol_name(name)
66
+ return name.to_sym if name.is_a?(Symbol) || name.is_a?(String)
67
+ nil
68
+ end
69
+
70
+ def class_to_type_name(klass)
71
+ klass = klass.class unless klass.is_a?(Class)
72
+ if derived_from?(Integer, klass) || derived_from?(Fixnum, klass) || derived_from?(Bignum, klass)
73
+ :int
74
+ elsif klass == String
75
+ :string
76
+ elsif klass == TrueClass || klass == FalseClass
77
+ :bool
78
+ elsif derived_from?(Float, klass) || derived_from?(Precision, klass) || derived_from?(Numeric, klass)
79
+ :float
80
+ elsif klass == Time
81
+ :time
82
+ elsif klass == DateTime
83
+ :datetime
84
+ elsif klass == Date
85
+ :date
86
+ else
87
+ nil
88
+ end
89
+ end
90
+
91
+ def type_name_to_class(name)
92
+ case canonical_type_name(name)
93
+ when :int
94
+ Integer
95
+ when :string
96
+ String
97
+ when :bool
98
+ TrueClass
99
+ when :float
100
+ Float
101
+ when :time
102
+ Time
103
+ when :date
104
+ Date
105
+ when :datetime
106
+ DateTime
107
+ else
108
+ nil
109
+ end
110
+ end
111
+
112
+ def derived_from?(ancestor, child)
113
+ child.ancestors.include?(ancestor)
114
+ end
115
+
116
+ module_function :type_name_to_class
117
+ module_function :class_to_type_name
118
+ module_function :symbol_name
119
+ module_function :canonical_type_class
120
+ module_function :canonical_type_name
121
+ module_function :canonical_type
122
+ module_function :canonical_signature_entry
123
+ module_function :canonical_signature
124
+ module_function :derived_from?
125
+ end
126
+
127
+ class BaseType # :nodoc:
128
+ include SignatureTypes
129
+
130
+ attr :spec
131
+ attr :type
132
+ attr :type_class
133
+ attr :name
134
+
135
+ def initialize(spec, type, name)
136
+ @spec = spec
137
+ @type = canonical_type(type)
138
+ @type_class = canonical_type_class(@type)
139
+ @name = name
140
+ end
141
+
142
+ def custom?
143
+ false
144
+ end
145
+
146
+ def array?
147
+ false
148
+ end
149
+
150
+ def structured?
151
+ false
152
+ end
153
+ end
154
+
155
+ class ArrayType < BaseType # :nodoc:
156
+ attr :element_type
157
+
158
+ def initialize(spec, element_type, name)
159
+ super(spec, Array, name)
160
+ @element_type = element_type
161
+ end
162
+
163
+ def custom?
164
+ true
165
+ end
166
+
167
+ def array?
168
+ true
169
+ end
170
+ end
171
+
172
+ class StructuredType < BaseType # :nodoc:
173
+ def each_member
174
+ if @type_class.respond_to?(:members)
175
+ @type_class.members.each do |name, type|
176
+ yield name, type
177
+ end
178
+ elsif @type_class.respond_to?(:columns)
179
+ i = -1
180
+ @type_class.columns.each do |column|
181
+ yield column.name, canonical_signature_entry(column.klass, i += 1)
182
+ end
183
+ end
184
+ end
185
+
186
+ def custom?
187
+ true
188
+ end
189
+
190
+ def structured?
191
+ true
192
+ end
193
+ end
194
+ end