johnreitano-savon 0.7.2.1 → 0.7.7.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,10 +1,41 @@
1
+ == 0.7.6 (2010-03-21)
2
+ * Renamed
3
+ * Moved documentation from the Github Wiki to the actual class files and established a much nicer
4
+ documentation combining examples and implementation (using Hanna) at: http://savon.rubiii.com
5
+ * Added Savon::Client#call as a workaround for dispatching calls to SOAP actions named after
6
+ existing methods. Fix for issue #48.
7
+ * Add support for specifying attributes for duplicate tags (via Hash values as Arrays). Fix for issue #45.
8
+ * Fix for issue #41 (Escape special characters (e.g. &) for XML requests).
9
+ * Fix for issue #39 and #49. Added Savon::SOAP#xml which let's you specify completely custom SOAP request XML.
10
+
11
+ == 0.7.5 (2010-02-19)
12
+ * Fix for issue #34 (soap_actions returns empty for wsdl12).
13
+ * Fix for issue #36 (Custom WSDL actions broken).
14
+ * Added feature requested in issue #35 (Setting an attribute on an element?).
15
+ * Changed the key for specifying the order of tags from :@inorder to :order!
16
+
17
+ == 0.7.4 (2010-02-02)
18
+ * Fix for issue #33 (undefined method 'start_with?').
19
+
20
+ == 0.7.3 (2010-01-31)
21
+ * Added support for Geotrust-style WSDL documents (Julian Kornberger <github.corny@digineo.de>).
22
+ * Make HTTP requests include path and query only. This was breaking requests via proxy as scheme and host
23
+ were repeated (Adrian Mugnolo <adrian@mugnolo.com>)
24
+ * Avoid warning on 1.8.7 and 1.9.1 (Adrian Mugnolo <adrian@mugnolo.com>).
25
+ * Fix for issue #29 (WSSE Created Bug?). Default to UTC to xs:dateTime value for WSSE authentication.
26
+ * Fix for issue #28 (Undefined Method ssl? on URI::Generic).
27
+ * Fix for issue #27 (http content-type defaults to utf-8). The Content-Type now defaults to UTF-8.
28
+ * Modification to allow assignment of an Array with an input name and an optional Hash of values to soap.input.
29
+ Patches issue #30 (stanleydrew <andrewmbenton@gmail.com>).
30
+ * Fix for issue #25 (header-tag should not be sent if not set).
31
+
1
32
  == 0.7.2 (2010-01-17)
2
33
  * Exposed the Net::HTTP response (added by Kevin Ingolfsland). Use the "http" accessor (response.http) on your
3
34
  Savon::Response to access the Net::HTTP response object.
4
- * Fix for Github issue #21 (savon is stripping ?SOAP off the end of WSDL locations).
5
- * Fix for Github issue #22 (REXML::ParseException parsing 401 Unauthorized response body).
6
- * Fix for Github issue #19 (Unable to set attribute in name-spaced WSSE password element).
7
- * Added support for global header and namespaces. See Github issue #9 (Setting headers and namespaces).
35
+ * Fix for issue #21 (savon is stripping ?SOAP off the end of WSDL locations).
36
+ * Fix for issue #22 (REXML::ParseException parsing 401 Unauthorized response body).
37
+ * Fix for issue #19 (Unable to set attribute in name-spaced WSSE password element).
38
+ * Added support for global header and namespaces. See issue #9 (Setting headers and namespaces).
8
39
 
9
40
  == 0.7.1 (2010-01-10)
10
41
  * The Hash of HTTP headers for SOAP calls is now public via Savon::Request#headers.
@@ -122,3 +153,4 @@
122
153
 
123
154
  == 0.5.0 (2009-11-29)
124
155
  * Complete rewrite.
156
+
data/README.rdoc ADDED
@@ -0,0 +1,78 @@
1
+ = Savon
2
+
3
+ ==== Heavy metal Ruby SOAP client library
4
+
5
+ {Documentation}[http://savon.rubiii.com] | {Metrics}[http://getcaliper.com/caliper/project?repo=git://github.com/rubiii/savon.git]
6
+
7
+ == Installation
8
+
9
+ $ gem install savon
10
+
11
+ Savon expects you to be familiar with SOAP, WSDL and tools like soapUI.
12
+
13
+ == Instantiate a client
14
+
15
+ Instantiate Savon::Client, passing in the WSDL of your service.
16
+
17
+ client = Savon::Client.new "http://example.com/UserService?wsdl"
18
+
19
+ For production, it is highly recommended to not use Savon::WSDL. Information on {how to disable the WSDL}[http://savon.rubiii.com/docs/latest/classes/Savon/WSDL.html].
20
+
21
+ == Choose authentication methods
22
+
23
+ After the initialization of the client you can choose an authentication method to the requests.
24
+
25
+ For Basic HTTP authentication:
26
+
27
+ client.request.basic_auth("username", "password")
28
+
29
+ For NTLM HTTP authentication:
30
+
31
+ client.request.ntlm_auth("username", "password")
32
+
33
+ Choose {Basic}[http://en.wikipedia.org/wiki/Basic_access_authentication] or {NTLM}[http://en.wikipedia.org/wiki/NTLM] according your needs.
34
+
35
+ == Calling a SOAP action
36
+
37
+ Assuming your service applies to the defaults, you can now call any available SOAP action.
38
+
39
+ response = client.get_all_users
40
+
41
+ Savon lets you call SOAP actions using snake_case, because even though they will propably be written in lowerCamelCase or CamelCase, it just feels much more natural.
42
+
43
+ == The WSDL object
44
+
45
+ Savon::WSDL represents the WSDL of your service, including information like the namespace URI and available SOAP actions.
46
+
47
+ client.wsdl.soap_actions
48
+ => [:get_all_users, :get_user_by_id, :user_magic]
49
+
50
+ == The SOAP object
51
+
52
+ Savon::SOAP represents the SOAP request. Pass a block to your SOAP call and the SOAP object is passed to it as the first argument. The object allows setting the SOAP version, header, body and namespaces per request.
53
+
54
+ response = client.get_user_by_id { |soap| soap.body = { :id => 666 } }
55
+
56
+ == The WSSE object
57
+
58
+ Savon::WSSE represents WSSE authentication. Pass a block to your SOAP call and the WSSE object is passed to it as the second argument. The object allows setting the WSSE username, password and whether to use digest authentication.
59
+
60
+ response = client.get_user_by_id do |soap, wsse|
61
+ wsse.username = "gorilla"
62
+ wsse.password = "secret"
63
+ soap.body = { :id => 666 }
64
+ end
65
+
66
+ == The Response object
67
+
68
+ Savon::Response represents the HTTP and SOAP response. It contains and raises errors in case of an HTTP error or SOAP fault (unless disabled). Also you can get the response as XML (for parsing it with an XML library) or translated into a Hash.
69
+
70
+ == HTTP errors and SOAP faults
71
+
72
+ Savon raises a Savon::SOAPFault in case of a SOAP fault and a Savon::HTTPError in case of an HTTP error.
73
+ More information: {Errors}[http://savon.rubiii.com/docs/latest/classes/Savon/Response.html]
74
+
75
+ == Logging
76
+
77
+ Savon logs each request and response to STDOUT. But there are a couple of options to change the default behavior.
78
+ More information: {Logging}[http://savon.rubiii.com/docs/latest/classes/Savon/Request.html]
data/Rakefile CHANGED
@@ -1,16 +1,14 @@
1
1
  require "rake"
2
2
  require "spec/rake/spectask"
3
3
  require "spec/rake/verify_rcov"
4
- require "rake/rdoctask"
5
4
 
6
- task :default => :spec_verify
5
+ task :default => :spec
7
6
 
8
7
  Spec::Rake::SpecTask.new do |spec|
9
8
  spec.spec_files = FileList["spec/{savon}/**/*_spec.rb"]
10
9
  spec.spec_opts << "--color"
11
10
  spec.libs += ["lib", "spec"]
12
11
  spec.rcov = true
13
- spec.rcov_dir = "rcov"
14
12
  end
15
13
 
16
14
  RCov::VerifyTask.new(:spec_verify => :spec) do |verify|
@@ -30,16 +28,24 @@ task :spec_integration do
30
28
  end
31
29
  end
32
30
 
33
- desc "" # make this task invisible for "rake -T"
31
+ desc "" # make this task invisible
34
32
  Spec::Rake::SpecTask.new(:run_integration_spec) do |spec|
35
33
  spec.spec_files = FileList["spec/{integration}/**/*_spec.rb"]
36
34
  spec.spec_opts << "--color"
37
35
  spec.libs += ["lib", "spec"]
38
36
  end
39
37
 
40
- Rake::RDocTask.new do |rdoc|
41
- rdoc.title = "Savon"
42
- rdoc.rdoc_dir = "rdoc"
43
- rdoc.rdoc_files.include("lib/**/*.rb")
44
- rdoc.options = ["--line-numbers", "--inline-source"]
38
+ begin
39
+ $:.unshift File.join(File.dirname(__FILE__), "..", "hanna", "lib")
40
+ require "hanna/rdoctask"
41
+
42
+ Rake::RDocTask.new do |rdoc|
43
+ rdoc.title = "Savon - Heavy metal Ruby SOAP client library"
44
+ rdoc.rdoc_dir = "doc"
45
+ rdoc.rdoc_files.include("**/*.rdoc").include("lib/**/*.rb")
46
+ rdoc.options << "--line-numbers"
47
+ rdoc.options << "--webcvs=http://github.com/rubiii/savon/tree/master/"
48
+ end
49
+ rescue LoadError
50
+ puts "'gem install hanna' for documentation"
45
51
  end
data/lib/savon.rb CHANGED
@@ -1,14 +1,5 @@
1
1
  module Savon
2
2
 
3
- # Supported SOAP versions.
4
- SOAPVersions = [1, 2]
5
-
6
- # SOAP xs:dateTime format.
7
- SOAPDateTimeFormat = "%Y-%m-%dT%H:%M:%S"
8
-
9
- # SOAP xs:dateTime Regexp.
10
- SOAPDateTimeRegexp = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/
11
-
12
3
  # Raised in case of an HTTP error.
13
4
  class HTTPError < StandardError; end
14
5
 
@@ -18,17 +9,24 @@ module Savon
18
9
  end
19
10
 
20
11
  # standard libs
21
- %w(logger net/https openssl base64 digest/sha1 rexml/document).each do |lib|
22
- require lib
23
- end
12
+ require "logger"
13
+ require "net/https"
14
+ require "base64"
15
+ require "digest/sha1"
16
+ require "rexml/document"
24
17
 
25
- # gems
26
- require "rubygems"
27
- %w(builder crack/xml).each do |gem|
28
- require gem
29
- end
18
+ # gem dependencies
19
+ require "builder"
20
+ require "crack/xml"
21
+ require 'net/ntlm_http'
30
22
 
31
23
  # core files
32
- %w(core_ext wsse soap request response wsdl client).each do |file|
33
- require File.dirname(__FILE__) + "/savon/#{file}"
34
- end
24
+ require "savon/core_ext"
25
+ require "savon/wsse"
26
+ require "savon/soap"
27
+ require "savon/logger"
28
+ require "savon/request"
29
+ require "savon/response"
30
+ require "savon/wsdl_stream"
31
+ require "savon/wsdl"
32
+ require "savon/client"
data/lib/savon/client.rb CHANGED
@@ -1,13 +1,47 @@
1
1
  module Savon
2
2
 
3
- # == Savon::Client
3
+ # = Savon::Client
4
4
  #
5
- # Heavy metal Ruby SOAP client library. Minimizes the overhead of working
6
- # with SOAP services and XML.
5
+ # Savon::Client is the main object for connecting to a SOAP service. It includes methods to access
6
+ # both the Savon::WSDL and Savon::Request object.
7
+ #
8
+ # == Instantiation
9
+ #
10
+ # Depending on whether you aim to use Savon with or without Savon::WSDL, you need to instantiate
11
+ # Savon::Client by passing in the WSDL or SOAP endpoint.
12
+ #
13
+ # Client instance with a WSDL endpoint:
14
+ #
15
+ # client = Savon::Client.new "http://example.com/UserService?wsdl"
16
+ #
17
+ # Client instance with a SOAP endpoint (for using Savon without a WSDL):
18
+ #
19
+ # client = Savon::Client.new "http://example.com/UserService"
20
+ #
21
+ # It is recommended to not use Savon::WSDL for production. Please take a look at the Documentation
22
+ # for Savon::WSDL for more information about how to disable it.
23
+ #
24
+ # == Using a proxy server
25
+ #
26
+ # You can specify the URI to a proxy server via optional hash arguments.
27
+ #
28
+ # client = Savon::Client.new "http://example.com/UserService?wsdl", :proxy => "http://proxy.example.com"
29
+ #
30
+ # == Savon::WSDL
31
+ #
32
+ # You can access Savon::WSDL via:
33
+ #
34
+ # client.wsdl
35
+ #
36
+ # == Savon::Request
37
+ #
38
+ # You can also access Savon::Request via:
39
+ #
40
+ # client.request
7
41
  class Client
8
42
 
9
- # Expects a SOAP +endpoint+ String. Also accepts an optional Hash of
10
- # +options+ for specifying a proxy server and SSL client authentication.
43
+ # Expects a SOAP +endpoint+ string. Also accepts an optional hash of +options+ for specifying
44
+ # a +:proxy+ server to use.
11
45
  def initialize(endpoint, options = {})
12
46
  @request = Request.new endpoint, options
13
47
  @wsdl = WSDL.new @request
@@ -25,34 +59,30 @@ module Savon
25
59
  super
26
60
  end
27
61
 
62
+ # Same as method_missing. Workaround for SOAP actions that method_missing does not catch
63
+ # because the method does exist.
64
+ def call(method, *args, &block)
65
+ method_missing method, *args, &block
66
+ end
67
+
28
68
  private
29
69
 
30
70
  # Dispatches requests to SOAP actions matching a given +method+ name.
31
71
  def method_missing(method, *args, &block) #:doc:
32
- soap_call = soap_call_from method.to_s
33
- super if @wsdl.enabled? && !@wsdl.respond_to?(soap_call)
72
+ soap_action = soap_action_from method.to_s
73
+ super unless @wsdl.respond_to? soap_action
34
74
 
35
- setup_objects operation_from(soap_call), &block
75
+ setup_objects *@wsdl.operation_from(soap_action), &block
36
76
  Response.new @request.soap(@soap)
37
77
  end
38
78
 
39
- # Sets whether to use Savon::WSDL by a given +method+ name and
40
- # removes exclamation marks from the given +method+ name.
41
- def soap_call_from(method)
42
- if method[-1, 1] == "!"
43
- @wsdl.enabled = false
44
- method[0, method.length-1].to_sym
45
- else
46
- @wsdl.enabled = true
47
- method.to_sym
48
- end
49
- end
79
+ # Sets whether to use Savon::WSDL by a given +method+ name and returns the original method name
80
+ # without exclamation marks.
81
+ def soap_action_from(method)
82
+ @wsdl.enabled = !method.ends_with?("!")
50
83
 
51
- # Returns a SOAP operation Hash containing the SOAP action and input
52
- # for a given +soap_call+.
53
- def operation_from(soap_call)
54
- return @wsdl.operations[soap_call] if @wsdl.enabled?
55
- { :action => soap_call.to_soap_key, :input => soap_call.to_soap_key }
84
+ method.chop! if method.ends_with?("!")
85
+ method.to_sym
56
86
  end
57
87
 
58
88
  # Returns the SOAP endpoint.
@@ -60,19 +90,17 @@ module Savon
60
90
  @wsdl.enabled? ? @wsdl.soap_endpoint : @request.endpoint
61
91
  end
62
92
 
63
- # Expects a SOAP operation Hash and sets up Savon::SOAP and Savon::WSSE.
64
- # Yields them to a given +block+ in case one was given.
65
- def setup_objects(operation, &block)
66
- @soap, @wsse = SOAP.new, WSSE.new
67
- @soap.action, @soap.input, @soap.endpoint = operation[:action], operation[:input], soap_endpoint
68
-
93
+ # Expects a SOAP operation Hash and sets up Savon::SOAP and Savon::WSSE. Yields them to a given
94
+ # +block+ in case one was given.
95
+ def setup_objects(action, input, &block)
96
+ @soap, @wsse = SOAP.new(action, input, soap_endpoint), WSSE.new
69
97
  yield_objects &block if block
70
-
71
98
  @soap.namespaces["xmlns:wsdl"] ||= @wsdl.namespace_uri if @wsdl.enabled?
72
99
  @soap.wsse = @wsse
73
100
  end
74
101
 
75
- # Yields Savon::SOAP and Savon::WSSE to a given +block+.
102
+ # Yields either Savon::SOAP or Savon::SOAP and Savon::WSSE to a given +block+, depending on
103
+ # the number of arguments expected by the block.
76
104
  def yield_objects(&block)
77
105
  case block.arity
78
106
  when 1 then yield @soap
@@ -1,3 +1,8 @@
1
- %w(object string symbol datetime hash uri net_http).each do |file|
2
- require File.dirname(__FILE__) + "/core_ext/#{file}"
3
- end
1
+ require "savon/core_ext/object"
2
+ require "savon/core_ext/string"
3
+ require "savon/core_ext/symbol"
4
+ require "savon/core_ext/datetime"
5
+ require "savon/core_ext/array"
6
+ require "savon/core_ext/hash"
7
+ require "savon/core_ext/uri"
8
+ require "savon/core_ext/net_http"
@@ -0,0 +1,31 @@
1
+ class Array
2
+
3
+ # Translates the Array into SOAP compatible XML. See: Hash.to_soap_xml.
4
+ def to_soap_xml(key, attributes = {})
5
+ xml = Builder::XmlMarkup.new
6
+
7
+ each_with_index do |item, index|
8
+ attrs = tag_attributes attributes, index
9
+ case item
10
+ when Hash then xml.tag!(key, attrs) { xml << item.to_soap_xml }
11
+ else xml.tag!(key, attrs) { xml << item.to_soap_value }
12
+ end
13
+ end
14
+
15
+ xml.target!
16
+ end
17
+
18
+ private
19
+
20
+ # Takes a Hash of +attributes+ and the +index+ for which to return attributes
21
+ # for duplicate tags.
22
+ def tag_attributes(attributes, index)
23
+ return {} if attributes.empty?
24
+
25
+ attributes.inject({}) do |hash, (key, value)|
26
+ value = value[index] if value.kind_of? Array
27
+ hash.merge key => value
28
+ end
29
+ end
30
+
31
+ end
@@ -2,7 +2,7 @@ class DateTime
2
2
 
3
3
  # Returns the DateTime as an xs:dateTime formatted String.
4
4
  def to_soap_value
5
- strftime Savon::SOAPDateTimeFormat
5
+ strftime Savon::SOAP::DateTimeFormat
6
6
  end
7
7
 
8
8
  end
@@ -1,78 +1,102 @@
1
1
  class Hash
2
2
 
3
- # Error message for missing :@inorder elements.
4
- InOrderMissing = "Missing elements in :@inorder %s"
5
-
6
- # Error message for spurious :@inorder elements.
7
- InOrderSpurious = "Spurious elements in :@inorder %s"
8
-
9
- # Returns the values from the 'soap:Body' element or an empty Hash
10
- # in case the node could not be found.
3
+ # Returns the values from the soap:Body element or an empty Hash in case the soap:Body tag could
4
+ # not be found.
11
5
  def find_soap_body
12
- envelope = self[self.keys.first] || {}
6
+ envelope = self[keys.first] || {}
13
7
  body_key = envelope.keys.find { |key| /.+:Body/ =~ key } rescue nil
14
8
  body_key ? envelope[body_key].map_soap_response : {}
15
9
  end
16
10
 
17
- # Returns the Hash translated into SOAP request compatible XML.
11
+ # Translates the Hash into SOAP request compatible XML.
12
+ #
13
+ # { :find_user => { :id => 123, "wsdl:Key" => "api" } }.to_soap_xml
14
+ # # => "<findUser><id>123</id><wsdl:Key>api</wsdl:Key></findUser>"
15
+ #
16
+ # ==== Mapping
17
+ #
18
+ # * Hash keys specified as Symbols are converted to lowerCamelCase Strings
19
+ # * Hash keys specified as Strings are not converted and may contain namespaces
20
+ # * DateTime values are converted to xs:dateTime Strings
21
+ # * Objects responding to to_datetime (except Strings) are converted to xs:dateTime Strings
22
+ # * TrueClass and FalseClass objects are converted to "true" and "false" Strings
23
+ # * All other objects are expected to be converted to Strings using to_s
24
+ #
25
+ # An example:
26
+ #
27
+ # { :magic_request => {
28
+ # :perform_move => true,
29
+ # "perform_at" => DateTime.new(2010, 11, 22, 11, 22, 33)
30
+ # }
31
+ # }.to_soap_xml
32
+ #
33
+ # <magicRequest>
34
+ # <performMove>true</performMove>
35
+ # <perform_at>2012-06-11T10:42:21</perform_at>
36
+ # </magicRequest>
37
+ #
38
+ # ==== :order!
39
+ #
40
+ # In case your service requires the tags to be in a specific order (parameterOrder), you have two
41
+ # options. The first is to specify your body as an XML string. The second is to specify the order
42
+ # through an additional array stored under the +:order!+ key.
18
43
  #
19
- # To control the order of output, add a key of :@inorder with
20
- # the value being an Array listing keys in order.
44
+ # { :name => "Eve", :id => 123, :order! => [:id, :name] }.to_soap_xml
45
+ # # => "<id>123</id><name>Eve</name>"
21
46
  #
22
- # === Examples
47
+ # ==== :attributes!
23
48
  #
24
- # { :find_user => { :id => 666 } }.to_soap_xml
25
- # => "<findUser><id>666</id></findUser>"
49
+ # If you need attributes, you could either go with an XML string or add another hash under the
50
+ # +:attributes!+ key.
26
51
  #
27
- # { :find_user => { :name => "Lucy", :id => 666, :@inorder => [:id, :name] } }.to_soap_xml
28
- # => "<findUser><id>666</id><name>Lucy</name></findUser>"
52
+ # { :person => "Eve", :attributes! => { :person => { :id => 666 } } }.to_soap_xml
53
+ # # => '<person id="666">Eve</person>'
29
54
  def to_soap_xml
30
- @soap_xml = Builder::XmlMarkup.new
31
- inorder(self).each { |key| nested_data_to_soap_xml key, self[key] }
32
- @soap_xml.target!
55
+ xml = Builder::XmlMarkup.new
56
+ attributes = delete(:attributes!) || {}
57
+
58
+ order.each do |key|
59
+ attrs = attributes[key] || {}
60
+ value = self[key]
61
+ key = key.to_soap_key
62
+
63
+ case value
64
+ when Array then xml << value.to_soap_xml(key, attrs)
65
+ when Hash then xml.tag!(key, attrs) { xml << value.to_soap_xml }
66
+ else xml.tag!(key, attrs) { xml << value.to_soap_value }
67
+ end
68
+ end
69
+
70
+ xml.target!
33
71
  end
34
72
 
35
- # Maps keys and values of a Hash created from SOAP response XML to
36
- # more convenient Ruby Objects.
73
+ # Maps keys and values of a Hash created from SOAP response XML to more convenient Ruby Objects.
37
74
  def map_soap_response
38
75
  inject({}) do |hash, (key, value)|
39
- key = key.strip_namespace.snakecase.to_sym
40
-
41
76
  value = case value
42
77
  when Hash then value["xsi:nil"] ? nil : value.map_soap_response
43
- when Array then value.map { |a_value| a_value.map_soap_response rescue a_value }
78
+ when Array then value.map { |val| val.map_soap_response rescue val }
44
79
  when String then value.map_soap_response
45
80
  end
46
- hash.merge key => value
81
+
82
+ hash.merge key.strip_namespace.snakecase.to_sym => value
47
83
  end
48
84
  end
49
85
 
50
86
  private
51
87
 
52
- # Expects a Hash +key+ and +value+ and recursively creates an XML structure
53
- # representing the Hash content.
54
- def nested_data_to_soap_xml(key, value)
55
- case value
56
- when Array
57
- value.map { |subitem| nested_data_to_soap_xml key, subitem }
58
- when Hash
59
- @soap_xml.tag!(key.to_soap_key) do
60
- inorder(value).each { |subkey| nested_data_to_soap_xml subkey, value[subkey] }
61
- end
62
- else
63
- @soap_xml.tag!(key.to_soap_key) { @soap_xml << value.to_soap_value }
64
- end
65
- end
88
+ # Deletes and returns an Array of keys stored under the :order! key. Defaults to return the actual
89
+ # keys of this Hash if no :order! key could be found. Raises an ArgumentError in case the :order!
90
+ # Array does not match the Hash keys.
91
+ def order
92
+ order = delete :order!
93
+ order = keys unless order.kind_of? Array
94
+
95
+ missing, spurious = keys - order, order - keys
96
+ raise ArgumentError, "Missing elements in :order! #{missing.inspect}" unless missing.empty?
97
+ raise ArgumentError, "Spurious elements in :order! #{spurious.inspect}" unless spurious.empty?
66
98
 
67
- # Takes a +hash+, removes the :@inorder marker and returns its keys.
68
- # Raises an error in case an :@inorder Array does not match the Hash keys.
69
- def inorder(hash)
70
- inorder = hash.delete :@inorder
71
- hash_keys = hash.keys
72
- inorder = hash_keys unless inorder.kind_of? Array
73
- raise InOrderMissing % (hash_keys - inorder).inspect unless (hash_keys - inorder).empty?
74
- raise InOrderSpurious % (inorder - hash_keys).inspect unless (inorder - hash_keys).empty?
75
- inorder
99
+ order
76
100
  end
77
101
 
78
- end
102
+ end