paypal-sdk-rest 0.10.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|