paypal-sdk-rest 0.10.0 → 1.0.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/Gemfile +1 -1
- data/README.md +44 -0
- data/lib/generators/paypal/sdk/USAGE +3 -0
- data/lib/generators/paypal/sdk/install_generator.rb +17 -0
- data/lib/generators/paypal/sdk/templates/paypal.rb +2 -0
- data/lib/generators/paypal/sdk/templates/paypal.yml +31 -0
- data/lib/paypal-sdk-core.rb +38 -0
- data/lib/paypal-sdk/core/api.rb +20 -0
- data/lib/paypal-sdk/core/api/base.rb +162 -0
- data/lib/paypal-sdk/core/api/data_types/array_with_block.rb +44 -0
- data/lib/paypal-sdk/core/api/data_types/base.rb +224 -0
- data/lib/paypal-sdk/core/api/data_types/enum.rb +26 -0
- data/lib/paypal-sdk/core/api/data_types/simple_types.rb +52 -0
- data/lib/paypal-sdk/core/api/ipn.rb +66 -0
- data/lib/paypal-sdk/core/api/rest.rb +163 -0
- data/lib/paypal-sdk/core/authentication.rb +66 -0
- data/lib/paypal-sdk/core/config.rb +249 -0
- data/lib/paypal-sdk/core/credential.rb +16 -0
- data/lib/paypal-sdk/core/credential/base.rb +27 -0
- data/lib/paypal-sdk/core/credential/certificate.rb +32 -0
- data/lib/paypal-sdk/core/credential/signature.rb +22 -0
- data/lib/paypal-sdk/core/credential/third_party/subject.rb +25 -0
- data/lib/paypal-sdk/core/credential/third_party/token.rb +39 -0
- data/lib/paypal-sdk/core/exceptions.rb +96 -0
- data/lib/paypal-sdk/core/logging.rb +45 -0
- data/lib/paypal-sdk/core/openid_connect.rb +122 -0
- data/lib/paypal-sdk/core/openid_connect/api.rb +49 -0
- data/lib/paypal-sdk/core/openid_connect/data_types.rb +73 -0
- data/lib/paypal-sdk/core/openid_connect/get_api.rb +28 -0
- data/lib/paypal-sdk/core/openid_connect/request_data_type.rb +52 -0
- data/lib/paypal-sdk/core/openid_connect/set_api.rb +36 -0
- data/lib/paypal-sdk/core/util.rb +11 -0
- data/lib/paypal-sdk/core/util/http_helper.rb +159 -0
- data/lib/paypal-sdk/core/util/oauth_signature.rb +64 -0
- data/lib/paypal-sdk/core/util/ordered_hash.rb +165 -0
- data/lib/paypal-sdk/rest/data_types.rb +1 -0
- data/lib/paypal-sdk/rest/version.rb +1 -1
- data/spec/config/paypal.yml +27 -0
- data/spec/config/sample_data.yml +3 -0
- data/spec/core/api/data_type_spec.rb +189 -0
- data/spec/core/api/rest_spec.rb +147 -0
- data/spec/core/config_spec.rb +192 -0
- data/spec/core/logging_spec.rb +28 -0
- data/spec/core/openid_connect_spec.rb +144 -0
- data/spec/log/http.log +71 -32
- data/spec/log/rest_http.log +133 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/support/sample_data.rb +5 -0
- metadata +82 -5
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'paypal-sdk-core'
|
2
|
+
|
3
|
+
module PayPal::SDK::Core
|
4
|
+
module OpenIDConnect
|
5
|
+
module DataTypes
|
6
|
+
class Base < PayPal::SDK::Core::API::DataTypes::Base
|
7
|
+
end
|
8
|
+
|
9
|
+
class Address < Base
|
10
|
+
def self.load_members
|
11
|
+
object_of :street_address, String
|
12
|
+
object_of :locality, String
|
13
|
+
object_of :region, String
|
14
|
+
object_of :postal_code, String
|
15
|
+
object_of :country, String
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Userinfo < Base
|
20
|
+
def self.load_members
|
21
|
+
object_of :user_id, String
|
22
|
+
object_of :sub, String
|
23
|
+
object_of :name, String
|
24
|
+
object_of :given_name, String
|
25
|
+
object_of :family_name, String
|
26
|
+
object_of :middle_name, String
|
27
|
+
object_of :picture, String
|
28
|
+
object_of :email, String
|
29
|
+
object_of :email_verified, Boolean
|
30
|
+
object_of :gender, String
|
31
|
+
object_of :birthday, String
|
32
|
+
object_of :zoneinfo, String
|
33
|
+
object_of :locale, String
|
34
|
+
object_of :language, String
|
35
|
+
object_of :verified, Boolean
|
36
|
+
object_of :phone_number, String
|
37
|
+
object_of :address, Address
|
38
|
+
object_of :verified_account, Boolean
|
39
|
+
object_of :account_type, String
|
40
|
+
object_of :account_creation_date, String
|
41
|
+
object_of :age_range, String
|
42
|
+
object_of :payer_id, String
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Tokeninfo < Base
|
47
|
+
def self.load_members
|
48
|
+
object_of :scope, String
|
49
|
+
object_of :access_token, String
|
50
|
+
object_of :refresh_token, String
|
51
|
+
object_of :token_type, String
|
52
|
+
object_of :id_token, String
|
53
|
+
object_of :expires_in, Integer
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Error < Base
|
58
|
+
def self.load_members
|
59
|
+
object_of :error, String
|
60
|
+
object_of :error_description, String
|
61
|
+
object_of :error_uri, String
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
constants.each do |data_type_klass|
|
67
|
+
data_type_klass = const_get(data_type_klass)
|
68
|
+
data_type_klass.load_members if defined? data_type_klass.load_members
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module PayPal::SDK
|
2
|
+
module Core
|
3
|
+
module OpenIDConnect
|
4
|
+
module GetAPI
|
5
|
+
# Get API object
|
6
|
+
# === Example
|
7
|
+
# Payment.api
|
8
|
+
# payment.api
|
9
|
+
def api
|
10
|
+
@api || parent_api
|
11
|
+
end
|
12
|
+
|
13
|
+
# Parent API object
|
14
|
+
def parent_api
|
15
|
+
superclass.respond_to?(:api) ? superclass.api : RequestDataType.api
|
16
|
+
end
|
17
|
+
|
18
|
+
def client_id
|
19
|
+
api.config.openid_client_id || api.config.client_id
|
20
|
+
end
|
21
|
+
|
22
|
+
def client_secret
|
23
|
+
api.config.openid_client_secret || api.config.client_secret
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module PayPal::SDK
|
2
|
+
module Core
|
3
|
+
module OpenIDConnect
|
4
|
+
module RequestDataType
|
5
|
+
|
6
|
+
# Get a local API object or Class level API object
|
7
|
+
def api
|
8
|
+
@api || self.class.api
|
9
|
+
end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
# Global API object
|
13
|
+
# === Example
|
14
|
+
# RequestDataType.api
|
15
|
+
def api
|
16
|
+
@api ||= API.new({})
|
17
|
+
end
|
18
|
+
|
19
|
+
def client_id
|
20
|
+
api.config.openid_client_id || api.config.client_id
|
21
|
+
end
|
22
|
+
|
23
|
+
def client_secret
|
24
|
+
api.config.openid_client_secret || api.config.client_secret
|
25
|
+
end
|
26
|
+
|
27
|
+
# Setter for RequestDataType.api
|
28
|
+
# === Example
|
29
|
+
# RequestDataType.set_config(..)
|
30
|
+
include SetAPI
|
31
|
+
|
32
|
+
# Configure depended module, when RequestDataType is include.
|
33
|
+
# === Example
|
34
|
+
# class Payment < DataTypes
|
35
|
+
# include RequestDataType
|
36
|
+
# end
|
37
|
+
# Payment.set_config(..)
|
38
|
+
# payment.set_config(..)
|
39
|
+
# Payment.api
|
40
|
+
# payment.api
|
41
|
+
def included(klass)
|
42
|
+
klass.class_eval do
|
43
|
+
extend GetAPI
|
44
|
+
extend SetAPI
|
45
|
+
include SetAPI
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module PayPal::SDK
|
2
|
+
module Core
|
3
|
+
module OpenIDConnect
|
4
|
+
module SetAPI
|
5
|
+
# Set new api
|
6
|
+
# === Examples
|
7
|
+
# payment.set_config(:development)
|
8
|
+
# payment.set_config(:client_id => "XYZ", :client_secret => "SECRET")
|
9
|
+
# payment.set_config
|
10
|
+
# payment.api = API.new(:development)
|
11
|
+
def set_config(*args)
|
12
|
+
if args[0].is_a?(API)
|
13
|
+
@api = args[0]
|
14
|
+
else
|
15
|
+
@api ||= API.new({})
|
16
|
+
@api.set_config(*args) # Just override the configuration and Not
|
17
|
+
@api
|
18
|
+
end
|
19
|
+
end
|
20
|
+
alias_method :config=, :set_config
|
21
|
+
alias_method :set_api, :set_config
|
22
|
+
alias_method :api=, :set_config
|
23
|
+
|
24
|
+
# Override client id
|
25
|
+
def client_id=(client_id)
|
26
|
+
set_config(:client_id => client_id)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Override client secret
|
30
|
+
def client_secret=(client_secret)
|
31
|
+
set_config(:client_secret => client_secret)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module PayPal
|
2
|
+
module SDK
|
3
|
+
module Core
|
4
|
+
module Util
|
5
|
+
autoload :OauthSignature, "paypal-sdk/core/util/oauth_signature"
|
6
|
+
autoload :OrderedHash, "paypal-sdk/core/util/ordered_hash"
|
7
|
+
autoload :HTTPHelper, "paypal-sdk/core/util/http_helper"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
require 'uri'
|
3
|
+
require 'cgi'
|
4
|
+
|
5
|
+
module PayPal::SDK::Core
|
6
|
+
module Util
|
7
|
+
module HTTPHelper
|
8
|
+
|
9
|
+
include Configuration
|
10
|
+
include Logging
|
11
|
+
include Authentication
|
12
|
+
include Exceptions
|
13
|
+
|
14
|
+
# Create HTTP connection based on given service name or configured end point
|
15
|
+
def create_http_connection(uri)
|
16
|
+
new_http(uri).tap do |http|
|
17
|
+
if config.http_timeout
|
18
|
+
http.open_timeout = config.http_timeout
|
19
|
+
http.read_timeout = config.http_timeout
|
20
|
+
end
|
21
|
+
configure_ssl(http) if uri.scheme == "https"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# New raw HTTP object
|
26
|
+
def new_http(uri)
|
27
|
+
if config.http_proxy
|
28
|
+
proxy = URI.parse(config.http_proxy)
|
29
|
+
Net::HTTP.new(uri.host, uri.port, proxy.host, proxy.port, proxy.user, proxy.password)
|
30
|
+
else
|
31
|
+
Net::HTTP.new(uri.host, uri.port)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Default ca file
|
36
|
+
def default_ca_file
|
37
|
+
File.expand_path("../../../../../data/paypal.crt", __FILE__)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Apply ssl configuration to http object
|
41
|
+
def configure_ssl(http)
|
42
|
+
http.tap do |https|
|
43
|
+
https.use_ssl = true
|
44
|
+
https.ca_file = default_ca_file
|
45
|
+
https.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
46
|
+
config.ssl_options.each do |key, value|
|
47
|
+
http.send("#{key}=", value)
|
48
|
+
end
|
49
|
+
add_certificate(https)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Join url
|
54
|
+
def url_join(path, action)
|
55
|
+
path.sub(/\/?$/, "/#{action}")
|
56
|
+
end
|
57
|
+
|
58
|
+
# Make Http call
|
59
|
+
# * payload - Hash(:http, :method, :uri, :body, :header)
|
60
|
+
def http_call(payload)
|
61
|
+
if Config.config.verbose_logging
|
62
|
+
logger.info payload.inspect
|
63
|
+
end
|
64
|
+
|
65
|
+
response =
|
66
|
+
log_http_call(payload) do
|
67
|
+
http = payload[:http] || create_http_connection(payload[:uri])
|
68
|
+
http.start do |session|
|
69
|
+
if [ :get, :delete, :head ].include? payload[:method]
|
70
|
+
session.send(payload[:method], payload[:uri].request_uri, payload[:header])
|
71
|
+
else
|
72
|
+
session.send(payload[:method], payload[:uri].request_uri, payload[:body], payload[:header])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
if Config.config.verbose_logging
|
78
|
+
if response.code.to_i == 200
|
79
|
+
logger.info(response.body)
|
80
|
+
else
|
81
|
+
logger.warn(response.body)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
handle_response(response)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Log Http call
|
89
|
+
# * payload - Hash(:http, :method, :uri, :body, :header)
|
90
|
+
def log_http_call(payload)
|
91
|
+
logger.info "Request[#{payload[:method]}]: #{payload[:uri].to_s}"
|
92
|
+
start_time = Time.now
|
93
|
+
response = yield
|
94
|
+
logger.info sprintf("Response[%s]: %s, Duration: %.3fs", response.code,
|
95
|
+
response.message, Time.now - start_time)
|
96
|
+
response
|
97
|
+
end
|
98
|
+
|
99
|
+
# Generate header based on given header keys and properties
|
100
|
+
# === Arguments
|
101
|
+
# * <tt>header_keys</tt> -- List of Header keys for the properties
|
102
|
+
# * <tt>properties</tt> -- properties
|
103
|
+
# === Return
|
104
|
+
# Hash with header as key property as value
|
105
|
+
# === Example
|
106
|
+
# map_header_value( { :username => "X-PAYPAL-USERNAME"}, { :username => "guest" })
|
107
|
+
# # Return: { "X-PAYPAL-USERNAME" => "guest" }
|
108
|
+
def map_header_value(header_keys, properties)
|
109
|
+
header = {}
|
110
|
+
properties.each do |key, value|
|
111
|
+
key = header_keys[key]
|
112
|
+
header[key] = value.to_s if key and value
|
113
|
+
end
|
114
|
+
header
|
115
|
+
end
|
116
|
+
|
117
|
+
def encode_www_form(hash)
|
118
|
+
if defined? URI.encode_www_form
|
119
|
+
URI.encode_www_form(hash)
|
120
|
+
else
|
121
|
+
hash.map{|key, value| "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}" }.join("&")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Handles response and error codes from the remote service.
|
126
|
+
def handle_response(response)
|
127
|
+
case response.code.to_i
|
128
|
+
when 301, 302, 303, 307
|
129
|
+
raise(Redirection.new(response))
|
130
|
+
when 200...400
|
131
|
+
response
|
132
|
+
when 400
|
133
|
+
raise(BadRequest.new(response))
|
134
|
+
when 401
|
135
|
+
raise(UnauthorizedAccess.new(response))
|
136
|
+
when 403
|
137
|
+
raise(ForbiddenAccess.new(response))
|
138
|
+
when 404
|
139
|
+
raise(ResourceNotFound.new(response))
|
140
|
+
when 405
|
141
|
+
raise(MethodNotAllowed.new(response))
|
142
|
+
when 409
|
143
|
+
raise(ResourceConflict.new(response))
|
144
|
+
when 410
|
145
|
+
raise(ResourceGone.new(response))
|
146
|
+
when 422
|
147
|
+
raise(ResourceInvalid.new(response))
|
148
|
+
when 401...500
|
149
|
+
raise(ClientError.new(response))
|
150
|
+
when 500...600
|
151
|
+
raise(ServerError.new(response))
|
152
|
+
else
|
153
|
+
raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'cgi'
|
3
|
+
require 'openssl'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
module PayPal::SDK::Core
|
7
|
+
module Util
|
8
|
+
class OauthSignature
|
9
|
+
attr_accessor :username, :password, :token, :token_secret, :url, :timestamp
|
10
|
+
|
11
|
+
def initialize(username, password, token, token_secret, url, timestamp = nil)
|
12
|
+
@username = username
|
13
|
+
@password = password
|
14
|
+
@token = token
|
15
|
+
@token_secret = token_secret
|
16
|
+
@url = url
|
17
|
+
@timestamp = timestamp || Time.now.to_i.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
def authorization_string
|
21
|
+
signature = oauth_signature
|
22
|
+
"token=#{token},signature=#{signature},timestamp=#{timestamp}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def oauth_signature
|
26
|
+
key = [
|
27
|
+
paypal_encode(password),
|
28
|
+
paypal_encode(token_secret),
|
29
|
+
].join("&").gsub(/%[0-9A-F][0-9A-F]/, &:downcase )
|
30
|
+
|
31
|
+
digest = OpenSSL::HMAC.digest('sha1', key, base_string)
|
32
|
+
Base64.encode64(digest).chomp
|
33
|
+
end
|
34
|
+
|
35
|
+
def base_string
|
36
|
+
params = {
|
37
|
+
"oauth_consumer_key" => username,
|
38
|
+
"oauth_version" => "1.0",
|
39
|
+
"oauth_signature_method" => "HMAC-SHA1",
|
40
|
+
"oauth_token" => token,
|
41
|
+
"oauth_timestamp" => timestamp,
|
42
|
+
}
|
43
|
+
sorted_query_string = params.sort.map{|v| v.join("=") }.join("&")
|
44
|
+
|
45
|
+
base = [
|
46
|
+
"POST",
|
47
|
+
paypal_encode(url),
|
48
|
+
paypal_encode(sorted_query_string)
|
49
|
+
].join("&")
|
50
|
+
base = base.gsub(/%[0-9A-F][0-9A-F]/, &:downcase )
|
51
|
+
end
|
52
|
+
|
53
|
+
# The PayPalURLEncoder java class percent encodes everything other than 'a-zA-Z0-9 _'.
|
54
|
+
# Then it converts ' ' to '+'.
|
55
|
+
# Ruby's CGI.encode takes care of the ' ' and '*' to satisfy PayPal
|
56
|
+
# (but beware, URI.encode percent encodes spaces, and does nothing with '*').
|
57
|
+
# Finally, CGI.encode does not encode '.-', which we need to do here.
|
58
|
+
def paypal_encode str
|
59
|
+
CGI.escape(str.to_s).gsub('.', '%2E').gsub('-', '%2D')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
@@ -0,0 +1,165 @@
|
|
1
|
+
module PayPal::SDK::Core
|
2
|
+
module Util
|
3
|
+
class OrderedHash < ::Hash #:nodoc:
|
4
|
+
|
5
|
+
def to_yaml_type
|
6
|
+
"!tag:yaml.org,2002:map"
|
7
|
+
end
|
8
|
+
|
9
|
+
# Hash is ordered in Ruby 1.9!
|
10
|
+
if RUBY_VERSION < '1.9'
|
11
|
+
|
12
|
+
# In MRI the Hash class is core and written in C. In particular, methods are
|
13
|
+
# programmed with explicit C function calls and polymorphism is not honored.
|
14
|
+
#
|
15
|
+
# For example, []= is crucial in this implementation to maintain the @keys
|
16
|
+
# array but hash.c invokes rb_hash_aset() originally. This prevents method
|
17
|
+
# reuse through inheritance and forces us to reimplement stuff.
|
18
|
+
#
|
19
|
+
# For instance, we cannot use the inherited #merge! because albeit the algorithm
|
20
|
+
# itself would work, our []= is not being called at all by the C code.
|
21
|
+
|
22
|
+
def initialize(*args, &block)
|
23
|
+
super
|
24
|
+
@keys = []
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.[](*args)
|
28
|
+
ordered_hash = new
|
29
|
+
|
30
|
+
if (args.length == 1 && args.first.is_a?(Array))
|
31
|
+
args.first.each do |key_value_pair|
|
32
|
+
next unless (key_value_pair.is_a?(Array))
|
33
|
+
ordered_hash[key_value_pair[0]] = key_value_pair[1]
|
34
|
+
end
|
35
|
+
|
36
|
+
return ordered_hash
|
37
|
+
end
|
38
|
+
|
39
|
+
unless (args.size % 2 == 0)
|
40
|
+
raise ArgumentError.new("odd number of arguments for Hash")
|
41
|
+
end
|
42
|
+
|
43
|
+
args.each_with_index do |val, ind|
|
44
|
+
next if (ind % 2 != 0)
|
45
|
+
ordered_hash[val] = args[ind + 1]
|
46
|
+
end
|
47
|
+
|
48
|
+
ordered_hash
|
49
|
+
end
|
50
|
+
|
51
|
+
def initialize_copy(other)
|
52
|
+
super
|
53
|
+
# make a deep copy of keys
|
54
|
+
@keys = other.keys
|
55
|
+
end
|
56
|
+
|
57
|
+
def []=(key, value)
|
58
|
+
@keys << key if !has_key?(key)
|
59
|
+
super
|
60
|
+
end
|
61
|
+
|
62
|
+
def delete(key)
|
63
|
+
if has_key? key
|
64
|
+
index = @keys.index(key)
|
65
|
+
@keys.delete_at index
|
66
|
+
end
|
67
|
+
super
|
68
|
+
end
|
69
|
+
|
70
|
+
def delete_if
|
71
|
+
super
|
72
|
+
sync_keys!
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
def reject!
|
77
|
+
super
|
78
|
+
sync_keys!
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
def reject(&block)
|
83
|
+
dup.reject!(&block)
|
84
|
+
end
|
85
|
+
|
86
|
+
def keys
|
87
|
+
@keys.dup
|
88
|
+
end
|
89
|
+
|
90
|
+
def values
|
91
|
+
@keys.collect { |key| self[key] }
|
92
|
+
end
|
93
|
+
|
94
|
+
def to_hash
|
95
|
+
self
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_a
|
99
|
+
@keys.map { |key| [ key, self[key] ] }
|
100
|
+
end
|
101
|
+
|
102
|
+
def each_key
|
103
|
+
@keys.each { |key| yield key }
|
104
|
+
end
|
105
|
+
|
106
|
+
def each_value
|
107
|
+
@keys.each { |key| yield self[key]}
|
108
|
+
end
|
109
|
+
|
110
|
+
def each
|
111
|
+
@keys.each {|key| yield [key, self[key]]}
|
112
|
+
end
|
113
|
+
|
114
|
+
alias_method :each_pair, :each
|
115
|
+
|
116
|
+
def clear
|
117
|
+
super
|
118
|
+
@keys.clear
|
119
|
+
self
|
120
|
+
end
|
121
|
+
|
122
|
+
def shift
|
123
|
+
k = @keys.first
|
124
|
+
v = delete(k)
|
125
|
+
[k, v]
|
126
|
+
end
|
127
|
+
|
128
|
+
def merge!(other_hash)
|
129
|
+
if block_given?
|
130
|
+
other_hash.each { |k, v| self[k] = key?(k) ? yield(k, self[k], v) : v }
|
131
|
+
else
|
132
|
+
other_hash.each { |k, v| self[k] = v }
|
133
|
+
end
|
134
|
+
self
|
135
|
+
end
|
136
|
+
|
137
|
+
alias_method :update, :merge!
|
138
|
+
|
139
|
+
def merge(other_hash, &block)
|
140
|
+
dup.merge!(other_hash, &block)
|
141
|
+
end
|
142
|
+
|
143
|
+
# When replacing with another hash, the initial order of our keys must come from the other hash -ordered or not.
|
144
|
+
def replace(other)
|
145
|
+
super
|
146
|
+
@keys = other.keys
|
147
|
+
self
|
148
|
+
end
|
149
|
+
|
150
|
+
def invert
|
151
|
+
OrderedHash[self.to_a.map!{|key_value_pair| key_value_pair.reverse}]
|
152
|
+
end
|
153
|
+
|
154
|
+
def inspect
|
155
|
+
"#<OrderedHash #{super}>"
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
def sync_keys!
|
160
|
+
@keys.delete_if {|k| !has_key?(k)}
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|