nogara-wash_out 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/.travis.yml +3 -0
- data/Appraisals +7 -0
- data/CHANGELOG.md +79 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +118 -0
- data/LICENSE +22 -0
- data/README.md +181 -0
- data/Rakefile +14 -0
- data/app/helpers/wash_out_helper.rb +64 -0
- data/app/views/wash_with_soap/error.builder +10 -0
- data/app/views/wash_with_soap/response.builder +13 -0
- data/app/views/wash_with_soap/wsdl.builder +68 -0
- data/gemfiles/rails-3.0.11.gemfile +8 -0
- data/gemfiles/rails-3.0.11.gemfile.lock +130 -0
- data/gemfiles/rails-3.1.3.gemfile +8 -0
- data/gemfiles/rails-3.1.3.gemfile.lock +141 -0
- data/init.rb +1 -0
- data/lib/wash_out.rb +35 -0
- data/lib/wash_out/dispatcher.rb +206 -0
- data/lib/wash_out/engine.rb +30 -0
- data/lib/wash_out/model.rb +25 -0
- data/lib/wash_out/param.rb +176 -0
- data/lib/wash_out/router.rb +36 -0
- data/lib/wash_out/soap.rb +40 -0
- data/lib/wash_out/type.rb +28 -0
- data/lib/wash_out/version.rb +3 -0
- data/lib/wash_out/wsse.rb +79 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +42 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +23 -0
- data/spec/dummy/config/environments/test.rb +30 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +58 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +26 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/stylesheets/.gitkeep +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/support/httpi-rack.rb +46 -0
- data/spec/wash_out/dispatcher_spec.rb +65 -0
- data/spec/wash_out/param_spec.rb +26 -0
- data/spec/wash_out/type_spec.rb +23 -0
- data/spec/wash_out_spec.rb +686 -0
- data/wash_out.gemspec +21 -0
- metadata +183 -0
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'wash_out'
|
data/lib/wash_out.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'wash_out/engine'
|
2
|
+
require 'wash_out/param'
|
3
|
+
require 'wash_out/dispatcher'
|
4
|
+
require 'wash_out/soap'
|
5
|
+
require 'wash_out/router'
|
6
|
+
require 'wash_out/type'
|
7
|
+
require 'wash_out/model'
|
8
|
+
require 'wash_out/wsse'
|
9
|
+
|
10
|
+
module ActionDispatch::Routing
|
11
|
+
class Mapper
|
12
|
+
# Adds the routes for a SOAP endpoint at +controller+.
|
13
|
+
def wash_out(controller_name, options={})
|
14
|
+
options.reverse_merge!(@scope) if @scope
|
15
|
+
controller_class_name = [options[:module], controller_name].compact.join("/")
|
16
|
+
|
17
|
+
match "#{controller_name}/wsdl" => "#{controller_name}#_generate_wsdl", :via => :get, :format => false
|
18
|
+
match "#{controller_name}/action" => WashOut::Router.new(controller_class_name), :defaults => { :controller => controller_class_name, :action => '_action' }, :format => false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
Mime::Type.register "application/soap+xml", :soap
|
24
|
+
ActiveRecord::Base.send :extend, WashOut::Model if defined?(ActiveRecord)
|
25
|
+
|
26
|
+
ActionController::Renderers.add :soap do |what, options|
|
27
|
+
_render_soap(what, options)
|
28
|
+
end
|
29
|
+
|
30
|
+
module ActionView
|
31
|
+
class Base
|
32
|
+
cattr_accessor :washout_namespace
|
33
|
+
@@washout_namespace = false
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
require 'nori'
|
2
|
+
|
3
|
+
module WashOut
|
4
|
+
# The WashOut::Dispatcher module should be included in a controller acting
|
5
|
+
# as a SOAP endpoint. It includes actions for generating WSDL and handling
|
6
|
+
# SOAP requests.
|
7
|
+
module Dispatcher
|
8
|
+
# A SOAPError exception can be raised to return a correct SOAP error
|
9
|
+
# response.
|
10
|
+
class SOAPError < Exception; end
|
11
|
+
class ProgrammerError < Exception; end
|
12
|
+
|
13
|
+
# This filter parses the SOAP request and puts it into +params+ array.
|
14
|
+
def _parse_soap_parameters
|
15
|
+
# Do not interfere with project-space Nori setup
|
16
|
+
strip = Nori.strip_namespaces?
|
17
|
+
convert = Nori.convert_tags?
|
18
|
+
typecast = Nori.advanced_typecasting?
|
19
|
+
|
20
|
+
Nori.strip_namespaces = true
|
21
|
+
Nori.advanced_typecasting = false
|
22
|
+
|
23
|
+
if WashOut::Engine.snakecase_input
|
24
|
+
Nori.convert_tags_to { |tag| tag.snakecase.to_sym }
|
25
|
+
else
|
26
|
+
Nori.convert_tags_to { |tag| tag.to_sym }
|
27
|
+
end
|
28
|
+
|
29
|
+
request_body = request.body.read
|
30
|
+
@_params = Nori.parse(request_body)
|
31
|
+
|
32
|
+
references = WashOut::Dispatcher.deep_select(@_params){|k,v| v.is_a?(Hash) && v.has_key?(:@id)}
|
33
|
+
|
34
|
+
unless references.blank?
|
35
|
+
replaces = {}; references.each{|r| replaces['#'+r[:@id]] = r}
|
36
|
+
@_params = WashOut::Dispatcher.deep_replace_href(@_params, replaces)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Reset Nori setup to project-space
|
40
|
+
Nori.strip_namespaces = strip
|
41
|
+
Nori.advanced_typecasting = typecast
|
42
|
+
Nori.convert_tags_to convert
|
43
|
+
end
|
44
|
+
|
45
|
+
def _authenticate_wsse
|
46
|
+
begin
|
47
|
+
xml_security = @_params.values_at(:envelope, :Envelope).compact.first
|
48
|
+
xml_security = xml_security.values_at(:header, :Header).compact.first
|
49
|
+
xml_security = xml_security.values_at(:security, :Security).compact.first
|
50
|
+
username_token = xml_security.values_at(:username_token, :UsernameToken).compact.first
|
51
|
+
rescue
|
52
|
+
username_token = nil
|
53
|
+
end
|
54
|
+
|
55
|
+
WashOut::Wsse.authenticate username_token
|
56
|
+
|
57
|
+
request.env['WSSE_TOKEN'] = username_token.with_indifferent_access unless username_token.blank?
|
58
|
+
end
|
59
|
+
|
60
|
+
def _map_soap_parameters
|
61
|
+
soap_action = request.env['wash_out.soap_action']
|
62
|
+
action_spec = self.class.soap_actions[soap_action]
|
63
|
+
|
64
|
+
xml_data = @_params.values_at(:envelope, :Envelope).compact.first
|
65
|
+
xml_data = xml_data.values_at(:body, :Body).compact.first
|
66
|
+
xml_data = xml_data.values_at(soap_action.underscore.to_sym,
|
67
|
+
soap_action.to_sym).compact.first || {}
|
68
|
+
|
69
|
+
strip_empty_nodes = lambda{|hash|
|
70
|
+
hash.each do |key, value|
|
71
|
+
if value.is_a? Hash
|
72
|
+
value = value.delete_if{|key, value| key.to_s[0] == '@'}
|
73
|
+
|
74
|
+
if value.length > 0
|
75
|
+
hash[key] = strip_empty_nodes.call(value)
|
76
|
+
else
|
77
|
+
hash[key] = nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
hash
|
83
|
+
}
|
84
|
+
xml_data = strip_empty_nodes.call(xml_data)
|
85
|
+
@_params = HashWithIndifferentAccess.new
|
86
|
+
|
87
|
+
action_spec[:in].each do |param|
|
88
|
+
key = param.raw_name.to_sym
|
89
|
+
|
90
|
+
if xml_data.has_key? key
|
91
|
+
@_params[param.raw_name] = param.load(xml_data, key)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# This action generates the WSDL for defined SOAP methods.
|
97
|
+
def _generate_wsdl
|
98
|
+
@map = self.class.soap_actions
|
99
|
+
@namespace = WashOut::Engine.namespace
|
100
|
+
@name = controller_path.gsub('/', '_')
|
101
|
+
|
102
|
+
render :template => 'wash_with_soap/wsdl', :layout => false
|
103
|
+
end
|
104
|
+
|
105
|
+
# Render a SOAP response.
|
106
|
+
def _render_soap(result, options)
|
107
|
+
@namespace = WashOut::Engine.namespace
|
108
|
+
@operation = soap_action = request.env['wash_out.soap_action']
|
109
|
+
action_spec = self.class.soap_actions[soap_action][:out]
|
110
|
+
|
111
|
+
result = { 'value' => result } unless result.is_a? Hash
|
112
|
+
result = HashWithIndifferentAccess.new(result)
|
113
|
+
|
114
|
+
inject = lambda {|data, map|
|
115
|
+
result_spec = []
|
116
|
+
|
117
|
+
map.each_with_index do |param, i|
|
118
|
+
result_spec[i] = param.flat_copy
|
119
|
+
|
120
|
+
# Inline complex structure
|
121
|
+
if param.struct? && !param.multiplied
|
122
|
+
result_spec[i].map = inject.call(data[param.raw_name], param.map)
|
123
|
+
|
124
|
+
# Inline array of complex structures
|
125
|
+
elsif param.struct? && param.multiplied
|
126
|
+
if data.nil?
|
127
|
+
data = {} # when no data is given
|
128
|
+
elsif data.is_a?(Array)
|
129
|
+
raise ProgrammerError,
|
130
|
+
"SOAP response used #{data.inspect} (which is an Array), " +
|
131
|
+
"in the context where a Hash with key of '#{param.raw_name}' " +
|
132
|
+
"was expected."
|
133
|
+
end
|
134
|
+
data[param.raw_name] = [] unless data[param.raw_name].is_a?(Array)
|
135
|
+
result_spec[i].map = data[param.raw_name].map{|e| inject.call(e, param.map)}
|
136
|
+
|
137
|
+
else
|
138
|
+
val = data[param.raw_name]
|
139
|
+
if param.multiplied and val and not val.is_a?(Array)
|
140
|
+
raise ProgrammerError,
|
141
|
+
"SOAP response tried to use '#{val.inspect}' " +
|
142
|
+
"(which is of type #{val.class}), as the value for " +
|
143
|
+
"'#{param.raw_name}' (which expects an Array)."
|
144
|
+
end
|
145
|
+
result_spec[i].value = val
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
return result_spec
|
150
|
+
}
|
151
|
+
|
152
|
+
render :template => 'wash_with_soap/response',
|
153
|
+
:layout => false,
|
154
|
+
:locals => { :result => inject.call(result, action_spec) },
|
155
|
+
:content_type => 'text/xml'
|
156
|
+
end
|
157
|
+
|
158
|
+
# This action is a fallback for all undefined SOAP actions.
|
159
|
+
def _invalid_action
|
160
|
+
render_soap_error("Cannot find SOAP action mapping for #{request.env['wash_out.soap_action']}")
|
161
|
+
end
|
162
|
+
|
163
|
+
def _render_soap_exception(error)
|
164
|
+
render_soap_error(error.message)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Render a SOAP error response.
|
168
|
+
#
|
169
|
+
# Rails do not support sequental rescue_from handling, that is, rescuing an
|
170
|
+
# exception from a rescue_from handler. Hence this function is a public API.
|
171
|
+
def render_soap_error(message)
|
172
|
+
render :template => 'wash_with_soap/error', :status => 500,
|
173
|
+
:layout => false,
|
174
|
+
:locals => { :error_message => message },
|
175
|
+
:content_type => 'text/xml'
|
176
|
+
end
|
177
|
+
|
178
|
+
def self.included(controller)
|
179
|
+
controller.send :rescue_from, SOAPError, :with => :_render_soap_exception
|
180
|
+
controller.send :helper, :wash_out
|
181
|
+
controller.send :before_filter, :_parse_soap_parameters, :except => [ :_generate_wsdl, :_invalid_action ]
|
182
|
+
controller.send :before_filter, :_authenticate_wsse, :except => [ :_generate_wsdl, :_invalid_action ]
|
183
|
+
controller.send :before_filter, :_map_soap_parameters, :except => [ :_generate_wsdl, :_invalid_action ]
|
184
|
+
end
|
185
|
+
|
186
|
+
def self.deep_select(hash, result=[], &blk)
|
187
|
+
result += Hash[hash.select(&blk)].values
|
188
|
+
|
189
|
+
hash.each do |key, value|
|
190
|
+
result = deep_select(value, result, &blk) if value.is_a? Hash
|
191
|
+
end
|
192
|
+
|
193
|
+
result
|
194
|
+
end
|
195
|
+
|
196
|
+
def self.deep_replace_href(hash, replace)
|
197
|
+
return replace[hash[:@href]] if hash.has_key?(:@href)
|
198
|
+
|
199
|
+
hash.keys.each do |key, value|
|
200
|
+
hash[key] = deep_replace_href(hash[key], replace) if hash[key].is_a?(Hash)
|
201
|
+
end
|
202
|
+
|
203
|
+
hash
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module WashOut
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
class << self
|
4
|
+
attr_accessor :namespace
|
5
|
+
attr_accessor :snakecase, :snakecase_input, :camelize_wsdl
|
6
|
+
attr_accessor :wsse_username, :wsse_password
|
7
|
+
end
|
8
|
+
|
9
|
+
self.namespace = 'urn:WashOut'
|
10
|
+
self.snakecase = nil
|
11
|
+
|
12
|
+
self.snakecase_input = false
|
13
|
+
self.camelize_wsdl = false
|
14
|
+
|
15
|
+
self.wsse_username = nil
|
16
|
+
self.wsse_password = nil
|
17
|
+
|
18
|
+
config.wash_out = ActiveSupport::OrderedOptions.new
|
19
|
+
|
20
|
+
initializer "wash_out.configuration" do |app|
|
21
|
+
app.config.wash_out.each do |key, value|
|
22
|
+
self.class.send "#{key}=", value
|
23
|
+
end
|
24
|
+
|
25
|
+
unless self.class.snakecase.nil?
|
26
|
+
raise "Usage of wash_out.snakecase is deprecated. You should use wash_out.snakecase_input and wash_out.camelize_wsdl"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module WashOut
|
2
|
+
module Model
|
3
|
+
def wash_out_param_map
|
4
|
+
types = {
|
5
|
+
:text => :string,
|
6
|
+
:float => :double,
|
7
|
+
:decimal => :double,
|
8
|
+
:timestamp => :string
|
9
|
+
}
|
10
|
+
map = {}
|
11
|
+
|
12
|
+
columns_hash.each do |key, column|
|
13
|
+
type = column.type
|
14
|
+
type = types[type] if types.has_key?(type)
|
15
|
+
map[key] = type
|
16
|
+
end
|
17
|
+
|
18
|
+
map
|
19
|
+
end
|
20
|
+
|
21
|
+
def wash_out_param_name
|
22
|
+
return name.underscore
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
module WashOut
|
2
|
+
class Param
|
3
|
+
attr_accessor :raw_name
|
4
|
+
attr_accessor :name
|
5
|
+
attr_accessor :map
|
6
|
+
attr_accessor :type
|
7
|
+
attr_accessor :multiplied
|
8
|
+
attr_accessor :value
|
9
|
+
attr_accessor :source_class
|
10
|
+
|
11
|
+
# Defines a WSDL parameter with name +name+ and type specifier +type+.
|
12
|
+
# The type specifier format is described in #parse_def.
|
13
|
+
def initialize(name, type, multiplied = false)
|
14
|
+
type ||= {}
|
15
|
+
|
16
|
+
@name = name.to_s
|
17
|
+
@raw_name = name.to_s
|
18
|
+
@map = {}
|
19
|
+
@multiplied = multiplied
|
20
|
+
|
21
|
+
if WashOut::Engine.camelize_wsdl.to_s == 'lower'
|
22
|
+
@name = @name.camelize(:lower)
|
23
|
+
elsif WashOut::Engine.camelize_wsdl
|
24
|
+
@name = @name.camelize
|
25
|
+
end
|
26
|
+
|
27
|
+
if type.is_a?(Symbol)
|
28
|
+
@type = type.to_s
|
29
|
+
elsif type.is_a?(Class)
|
30
|
+
@type = 'struct'
|
31
|
+
@map = self.class.parse_def(type.wash_out_param_map)
|
32
|
+
@source_class = type
|
33
|
+
else
|
34
|
+
@type = 'struct'
|
35
|
+
@map = self.class.parse_def(type)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Converts a generic externally derived Ruby value, such as String or
|
40
|
+
# Hash, to a native Ruby object according to the definition of this type.
|
41
|
+
def load(data, key)
|
42
|
+
if !data.has_key? key
|
43
|
+
raise WashOut::Dispatcher::SOAPError, "Required SOAP parameter '#{key}' is missing"
|
44
|
+
end
|
45
|
+
|
46
|
+
data = data[key]
|
47
|
+
data = [data] if @multiplied && !data.is_a?(Array)
|
48
|
+
|
49
|
+
if struct?
|
50
|
+
if @multiplied
|
51
|
+
data.map do |x|
|
52
|
+
map_struct x do |param, data, elem|
|
53
|
+
param.load(data, elem)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
else
|
57
|
+
map_struct data do |param, data, elem|
|
58
|
+
param.load(data, elem)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
else
|
62
|
+
operation = case type
|
63
|
+
when 'string'; :to_s
|
64
|
+
when 'integer'; :to_i
|
65
|
+
when 'double'; :to_f
|
66
|
+
when 'boolean'; lambda { |v| v == 'true' }
|
67
|
+
when 'date'; :to_date
|
68
|
+
when 'datetime'; :to_datetime
|
69
|
+
when 'time'; :to_time
|
70
|
+
else raise RuntimeError, "Invalid WashOut simple type: #{type}"
|
71
|
+
end
|
72
|
+
|
73
|
+
if operation.nil? || data.nil?
|
74
|
+
data
|
75
|
+
elsif @multiplied
|
76
|
+
data.map{|x| x.send(operation)}
|
77
|
+
elsif operation.is_a? Symbol
|
78
|
+
data.send(operation)
|
79
|
+
else
|
80
|
+
operation.call(data)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Checks if this Param defines a complex type.
|
86
|
+
def struct?
|
87
|
+
type == 'struct'
|
88
|
+
end
|
89
|
+
|
90
|
+
def classified?
|
91
|
+
!source_class.nil?
|
92
|
+
end
|
93
|
+
|
94
|
+
def basic_type
|
95
|
+
return name unless classified?
|
96
|
+
return source_class.wash_out_param_name
|
97
|
+
end
|
98
|
+
|
99
|
+
def xsd_type
|
100
|
+
return 'int' if type.to_s == 'integer'
|
101
|
+
return 'dateTime' if type.to_s == 'datetime'
|
102
|
+
return type
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns a WSDL namespaced identifier for this type.
|
106
|
+
def namespaced_type
|
107
|
+
struct? ? "tns:#{basic_type}" : "xsd:#{xsd_type}"
|
108
|
+
end
|
109
|
+
|
110
|
+
# Parses a +definition+. The format of the definition is best described
|
111
|
+
# by the following BNF-like grammar.
|
112
|
+
#
|
113
|
+
# simple_type := :string | :integer | :double | :boolean
|
114
|
+
# nested_type := type_hash | simple_type | WashOut::Param instance
|
115
|
+
# type_hash := { :parameter_name => nested_type, ... }
|
116
|
+
# definition := [ WashOut::Param, ... ] |
|
117
|
+
# type_hash |
|
118
|
+
# simple_type
|
119
|
+
#
|
120
|
+
# If a simple type is passed as the +definition+, a single Param is returned
|
121
|
+
# with the +name+ set to "value".
|
122
|
+
# If a WashOut::Param instance is passed as a +nested_type+, the corresponding
|
123
|
+
# +:parameter_name+ is ignored.
|
124
|
+
#
|
125
|
+
# This function returns an array of WashOut::Param objects.
|
126
|
+
def self.parse_def(definition)
|
127
|
+
raise RuntimeError, "[] should not be used in your params. Use nil if you want to mark empty set." if definition == []
|
128
|
+
return [] if definition == nil
|
129
|
+
|
130
|
+
if [Array, Symbol, Class].include?(definition.class)
|
131
|
+
definition = { :value => definition }
|
132
|
+
end
|
133
|
+
|
134
|
+
if definition.is_a? Hash
|
135
|
+
definition.map do |name, opt|
|
136
|
+
if opt.is_a? WashOut::Param
|
137
|
+
opt
|
138
|
+
elsif opt.is_a? Array
|
139
|
+
WashOut::Param.new(name, opt[0], true)
|
140
|
+
else
|
141
|
+
WashOut::Param.new(name, opt)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
else
|
145
|
+
raise RuntimeError, "Wrong definition: #{definition.inspect}"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def flat_copy
|
150
|
+
copy = self.class.new(@name, @type.to_sym, @multiplied)
|
151
|
+
copy.raw_name = raw_name
|
152
|
+
copy
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
# Used to load an entire structure.
|
158
|
+
def map_struct(data)
|
159
|
+
unless data.is_a?(Hash)
|
160
|
+
raise WashOut::Dispatcher::SOAPError, "SOAP message structure is broken"
|
161
|
+
end
|
162
|
+
|
163
|
+
data = data.with_indifferent_access
|
164
|
+
struct = {}.with_indifferent_access
|
165
|
+
|
166
|
+
# RUBY18 Enumerable#each_with_object is better, but 1.9 only.
|
167
|
+
@map.map do |param|
|
168
|
+
if data.has_key? param.raw_name
|
169
|
+
struct[param.raw_name] = yield param, data, param.raw_name
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
struct
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|