actionwebservice 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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