oanda_api 0.8.1 → 0.8.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +38 -0
- data/.rspec_non_jruby +2 -0
- data/.yardopts +6 -0
- data/Gemfile +13 -0
- data/Guardfile +7 -0
- data/LICENSE +22 -0
- data/README.md +218 -0
- data/Rakefile +23 -0
- data/lib/oanda_api.rb +25 -0
- data/lib/oanda_api/client/client.rb +175 -0
- data/lib/oanda_api/client/namespace_proxy.rb +112 -0
- data/lib/oanda_api/client/resource_descriptor.rb +52 -0
- data/lib/oanda_api/client/token_client.rb +69 -0
- data/lib/oanda_api/client/username_client.rb +53 -0
- data/lib/oanda_api/configuration.rb +167 -0
- data/lib/oanda_api/errors.rb +4 -0
- data/lib/oanda_api/resource/account.rb +37 -0
- data/lib/oanda_api/resource/candle.rb +29 -0
- data/lib/oanda_api/resource/instrument.rb +21 -0
- data/lib/oanda_api/resource/order.rb +74 -0
- data/lib/oanda_api/resource/position.rb +18 -0
- data/lib/oanda_api/resource/price.rb +16 -0
- data/lib/oanda_api/resource/trade.rb +23 -0
- data/lib/oanda_api/resource/transaction.rb +67 -0
- data/lib/oanda_api/resource_base.rb +35 -0
- data/lib/oanda_api/resource_collection.rb +77 -0
- data/lib/oanda_api/utils/utils.rb +101 -0
- data/lib/oanda_api/version.rb +3 -0
- data/oanda_api.gemspec +32 -0
- data/spec/fixtures/vcr_cassettes/account_id_order_id_close.yml +264 -0
- data/spec/fixtures/vcr_cassettes/account_id_order_id_get.yml +114 -0
- data/spec/fixtures/vcr_cassettes/account_id_order_options_create.yml +74 -0
- data/spec/fixtures/vcr_cassettes/account_id_order_options_update.yml +112 -0
- data/spec/fixtures/vcr_cassettes/account_id_orders_get.yml +118 -0
- data/spec/fixtures/vcr_cassettes/account_id_orders_options_get.yml +123 -0
- data/spec/fixtures/vcr_cassettes/account_id_positions_get.yml +112 -0
- data/spec/fixtures/vcr_cassettes/account_id_positions_instrument_close.yml +214 -0
- data/spec/fixtures/vcr_cassettes/account_id_positions_instrument_get.yml +110 -0
- data/spec/fixtures/vcr_cassettes/account_id_trade_id_close.yml +252 -0
- data/spec/fixtures/vcr_cassettes/account_id_trade_id_get.yml +112 -0
- data/spec/fixtures/vcr_cassettes/account_id_trade_options_modify.yml +110 -0
- data/spec/fixtures/vcr_cassettes/account_id_trades_filter_get.yml +118 -0
- data/spec/fixtures/vcr_cassettes/account_id_trades_get.yml +118 -0
- data/spec/fixtures/vcr_cassettes/account_id_transaction_id_get.yml +283 -0
- data/spec/fixtures/vcr_cassettes/account_id_transactions_options_get.yml +205 -0
- data/spec/fixtures/vcr_cassettes/accounts_create.yml +75 -0
- data/spec/fixtures/vcr_cassettes/accounts_get.yml +111 -0
- data/spec/fixtures/vcr_cassettes/accounts_id_get.yml +187 -0
- data/spec/fixtures/vcr_cassettes/candles_options_get.yml +79 -0
- data/spec/fixtures/vcr_cassettes/instruments_get.yml +501 -0
- data/spec/fixtures/vcr_cassettes/instruments_options_get.yml +81 -0
- data/spec/fixtures/vcr_cassettes/prices_options_get.yml +81 -0
- data/spec/fixtures/vcr_cassettes/sandbox_client.yml +116 -0
- data/spec/fixtures/vcr_cassettes/sandbox_client_account.yml +111 -0
- data/spec/fixtures/vcr_cassettes/sandbox_instrument_EUR_USD.yml +77 -0
- data/spec/oanda_api/client/client_spec.rb +107 -0
- data/spec/oanda_api/client/namespace_proxy_spec.rb +16 -0
- data/spec/oanda_api/client/resource_descriptor_spec.rb +39 -0
- data/spec/oanda_api/client/token_client_spec.rb +60 -0
- data/spec/oanda_api/client/username_client_spec.rb +31 -0
- data/spec/oanda_api/configuration_spec.rb +138 -0
- data/spec/oanda_api/examples/accounts_spec.rb +28 -0
- data/spec/oanda_api/examples/orders_spec.rb +68 -0
- data/spec/oanda_api/examples/positions_spec.rb +38 -0
- data/spec/oanda_api/examples/rates_spec.rb +46 -0
- data/spec/oanda_api/examples/trades_spec.rb +58 -0
- data/spec/oanda_api/examples/transactions_spec.rb +24 -0
- data/spec/oanda_api/resource_collection_spec.rb +109 -0
- data/spec/oanda_api/utils/utils_spec.rb +109 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/client_helper.rb +60 -0
- data/spec/support/vcr.rb +7 -0
- metadata +124 -9
@@ -0,0 +1,37 @@
|
|
1
|
+
module OandaAPI
|
2
|
+
# Namespace for all resources.
|
3
|
+
module Resource
|
4
|
+
# Account value object.
|
5
|
+
# See the Oanda Developer Guide for information about {http://developer.oanda.com/rest-live/accounts/ Accounts}.
|
6
|
+
class Account < ResourceBase
|
7
|
+
attr_accessor :account_currency,
|
8
|
+
:account_id,
|
9
|
+
:account_name,
|
10
|
+
:balance,
|
11
|
+
:margin_available,
|
12
|
+
:margin_rate,
|
13
|
+
:margin_used,
|
14
|
+
:open_orders,
|
15
|
+
:open_trades,
|
16
|
+
:password,
|
17
|
+
:realized_pl,
|
18
|
+
:unrealized_pl,
|
19
|
+
:username
|
20
|
+
|
21
|
+
alias_method :id, :account_id
|
22
|
+
alias_method :id=, :account_id=
|
23
|
+
|
24
|
+
alias_method :currency, :account_currency
|
25
|
+
alias_method :currency=, :account_currency=
|
26
|
+
|
27
|
+
alias_method :name, :account_name
|
28
|
+
alias_method :name=, :account_name=
|
29
|
+
|
30
|
+
def initialize(attributes = {})
|
31
|
+
@open_orders = []
|
32
|
+
@open_trades = []
|
33
|
+
super
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module OandaAPI
|
2
|
+
module Resource
|
3
|
+
# Candle value object.
|
4
|
+
# See the Oanda Developer Guide for information about {http://developer.oanda.com/rest-live/rates/#retrieveInstrumentHistory Candles}.
|
5
|
+
class Candle < ResourceBase
|
6
|
+
attr_accessor :close_ask,
|
7
|
+
:close_bid,
|
8
|
+
:close_mid,
|
9
|
+
:complete,
|
10
|
+
:high_ask,
|
11
|
+
:high_bid,
|
12
|
+
:high_mid,
|
13
|
+
:low_ask,
|
14
|
+
:low_bid,
|
15
|
+
:low_mid,
|
16
|
+
:open_ask,
|
17
|
+
:open_bid,
|
18
|
+
:open_mid,
|
19
|
+
:time,
|
20
|
+
:volume
|
21
|
+
|
22
|
+
alias_method :complete?, :complete
|
23
|
+
|
24
|
+
def time=(v)
|
25
|
+
@time = Time.parse v.to_s
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module OandaAPI
|
2
|
+
module Resource
|
3
|
+
# Instrument value object.
|
4
|
+
# See the Oanda Developer Guide for information about {http://developer.oanda.com/rest-live/rates/#getInstrumentList Instruments}.
|
5
|
+
class Instrument < ResourceBase
|
6
|
+
attr_accessor :display_name,
|
7
|
+
:halted,
|
8
|
+
:instrument,
|
9
|
+
:margin_rate,
|
10
|
+
:max_trade_units,
|
11
|
+
:max_trailing_stop,
|
12
|
+
:min_trailing_stop,
|
13
|
+
:pip,
|
14
|
+
:precision
|
15
|
+
|
16
|
+
alias_method :halted?, :halted
|
17
|
+
alias_method :name, :instrument
|
18
|
+
alias_method :name=, :instrument=
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module OandaAPI
|
2
|
+
module Resource
|
3
|
+
# Order value object.
|
4
|
+
# See the Oanda Developer Guide for information about {http://developer.oanda.com/rest-live/orders/ Orders}.
|
5
|
+
class Order < ResourceBase
|
6
|
+
attr_accessor :expiry,
|
7
|
+
:id,
|
8
|
+
:instrument,
|
9
|
+
:lower_bound,
|
10
|
+
:order_opened,
|
11
|
+
:price,
|
12
|
+
:side,
|
13
|
+
:stop_loss,
|
14
|
+
:take_profit,
|
15
|
+
:time,
|
16
|
+
:trades_closed,
|
17
|
+
:trade_opened,
|
18
|
+
:trade_reduced,
|
19
|
+
:trailing_stop,
|
20
|
+
:type,
|
21
|
+
:units,
|
22
|
+
:upper_bound
|
23
|
+
|
24
|
+
def initialize(attributes = {})
|
25
|
+
attribs = attributes.dup
|
26
|
+
self.order_opened = attribs.delete(order_opened) || {}
|
27
|
+
self.trade_opened = attribs.delete(trade_opened) || {}
|
28
|
+
self.trade_reduced = attribs.delete(trade_reduced) || {}
|
29
|
+
self.trades_closed = attribs.delete(trades_closed) || []
|
30
|
+
super attribs
|
31
|
+
end
|
32
|
+
|
33
|
+
def expiry=(v)
|
34
|
+
@expiry = Time.parse v.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
def time=(v)
|
38
|
+
@time = Time.parse v.to_s
|
39
|
+
end
|
40
|
+
|
41
|
+
def order_opened=(v)
|
42
|
+
@order_opened = OrderOpened.new v
|
43
|
+
end
|
44
|
+
|
45
|
+
def trade_opened=(v)
|
46
|
+
@trade_opened = TradeOpened.new v
|
47
|
+
end
|
48
|
+
|
49
|
+
def trade_reduced=(v)
|
50
|
+
@trade_reduced = TradeReduced.new v
|
51
|
+
end
|
52
|
+
|
53
|
+
# See the Oanda Developer Guide for {http://developer.oanda.com/rest-live/orders/ Order} details.
|
54
|
+
class OrderOpened < ResourceBase
|
55
|
+
attr_accessor :expiry, :id, :lower_bound, :side, :stop_loss,
|
56
|
+
:take_profit, :trailing_stop, :units, :upper_bound
|
57
|
+
|
58
|
+
def expiry=(v)
|
59
|
+
@expiry = Time.parse v.to_s
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# See the Oanda Developer Guide for {http://developer.oanda.com/rest-live/orders/ Order} details.
|
64
|
+
class TradeOpened < ResourceBase
|
65
|
+
attr_accessor :id, :side, :stop_loss, :take_profit, :trailing_stop, :units
|
66
|
+
end
|
67
|
+
|
68
|
+
# See the Oanda Developer Guide for {http://developer.oanda.com/rest-live/orders/ Order} details.
|
69
|
+
class TradeReduced < ResourceBase
|
70
|
+
attr_accessor :id, :interest, :pl, :units
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module OandaAPI
|
2
|
+
module Resource
|
3
|
+
# Position value object.
|
4
|
+
# See the Oanda Developer Guide for information about {http://developer.oanda.com/rest-live/positions/ Positions}.
|
5
|
+
class Position < ResourceBase
|
6
|
+
attr_accessor :avg_price,
|
7
|
+
:ids,
|
8
|
+
:instrument,
|
9
|
+
:side,
|
10
|
+
:total_units
|
11
|
+
|
12
|
+
alias_method :price, :avg_price
|
13
|
+
alias_method :price=, :avg_price=
|
14
|
+
alias_method :units, :total_units
|
15
|
+
alias_method :units=, :total_units=
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module OandaAPI
|
2
|
+
module Resource
|
3
|
+
# Price value object.
|
4
|
+
# See the Oanda Developer Guide for information about {http://developer.oanda.com/rest-live/rates/#getCurrentPrices Prices}.
|
5
|
+
class Price < ResourceBase
|
6
|
+
attr_accessor :ask,
|
7
|
+
:bid,
|
8
|
+
:instrument,
|
9
|
+
:time
|
10
|
+
|
11
|
+
def time=(v)
|
12
|
+
@time = Time.parse v.to_s
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module OandaAPI
|
2
|
+
module Resource
|
3
|
+
# Trade value object.
|
4
|
+
# See the Oanda Developer Guide for information about {http://developer.oanda.com/rest-live/trades/ Trades}.
|
5
|
+
class Trade < ResourceBase
|
6
|
+
attr_accessor :id,
|
7
|
+
:instrument,
|
8
|
+
:price,
|
9
|
+
:profit,
|
10
|
+
:side,
|
11
|
+
:stop_loss,
|
12
|
+
:take_profit,
|
13
|
+
:time,
|
14
|
+
:trailing_amount,
|
15
|
+
:trailing_stop,
|
16
|
+
:units
|
17
|
+
|
18
|
+
def time=(v)
|
19
|
+
@time = Time.parse v.to_s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module OandaAPI
|
2
|
+
module Resource
|
3
|
+
# Transaction value object.
|
4
|
+
# See the Oanda Developer Guide for information about {http://developer.oanda.com/rest-live/transaction-history/ Transactions}.
|
5
|
+
class Transaction < ResourceBase
|
6
|
+
attr_accessor :account_balance,
|
7
|
+
:account_id,
|
8
|
+
:amount,
|
9
|
+
:expiry,
|
10
|
+
:id,
|
11
|
+
:instrument,
|
12
|
+
:interest,
|
13
|
+
:lower_bound,
|
14
|
+
:margin_rate,
|
15
|
+
:order_id,
|
16
|
+
:pl,
|
17
|
+
:price,
|
18
|
+
:rate,
|
19
|
+
:reason,
|
20
|
+
:side,
|
21
|
+
:stop_loss_price,
|
22
|
+
:stop_loss_price,
|
23
|
+
:take_profit_price,
|
24
|
+
:time,
|
25
|
+
:trade_id,
|
26
|
+
:trade_opened,
|
27
|
+
:trade_reduced,
|
28
|
+
:trailing_stop_loss_distance,
|
29
|
+
:type,
|
30
|
+
:units,
|
31
|
+
:upper_bound
|
32
|
+
|
33
|
+
def initialize(attributes = {})
|
34
|
+
attribs = attributes.dup
|
35
|
+
self.trade_opened = attribs.delete(:trade_opened) || {}
|
36
|
+
self.trade_reduced = attribs.delete(:trade_reduced) || {}
|
37
|
+
super attribs
|
38
|
+
end
|
39
|
+
|
40
|
+
def expiry=(v)
|
41
|
+
@expiry = Time.parse v.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
def time=(v)
|
45
|
+
@time = Time.parse v.to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
def trade_opened=(v)
|
49
|
+
@trade_opened = TradeOpened.new v
|
50
|
+
end
|
51
|
+
|
52
|
+
def trade_reduced=(v)
|
53
|
+
@trade_reduced = TradeReduced.new v
|
54
|
+
end
|
55
|
+
|
56
|
+
# See http://developer.oanda.com/rest-live/transaction-history/ for attribute details.
|
57
|
+
class TradeOpened < ResourceBase
|
58
|
+
attr_accessor :id, :units
|
59
|
+
end
|
60
|
+
|
61
|
+
# See http://developer.oanda.com/rest-live/transaction-history/ for attribute details.
|
62
|
+
class TradeReduced < ResourceBase
|
63
|
+
attr_accessor :id, :interest, :pl, :units
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module OandaAPI
|
2
|
+
# Base class for all Resources.
|
3
|
+
#
|
4
|
+
# @!attribute [rw] location
|
5
|
+
# @return [String] the +location+ header if one is returned in an API
|
6
|
+
# response.
|
7
|
+
# @example Using the +location+ attribute
|
8
|
+
# client = OandaAPI::Client::TokenClient.new :practice, token
|
9
|
+
# all_transactions = client.account(123).alltransactions.get
|
10
|
+
# all_transactions.location # => https://fxtrade.oanda.com/transactionhistory/d3aed6823c.json.zip
|
11
|
+
class ResourceBase
|
12
|
+
attr_accessor :location
|
13
|
+
|
14
|
+
# @param [Hash] attributes collection of resource attributes. See the
|
15
|
+
# {http://developer.oanda.com/rest-live/development-guide/ Oanda Developer Guide}
|
16
|
+
# for documentation about resource attributes.
|
17
|
+
def initialize(attributes = {})
|
18
|
+
initialize_attributes Utils.rubyize_keys(attributes.dup)
|
19
|
+
@location = attributes.location if attributes.respond_to? :location
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# @private
|
25
|
+
# Initializes attributes.
|
26
|
+
#
|
27
|
+
# @param [Hash] attributes collection of resource attributes.
|
28
|
+
# @return [void]
|
29
|
+
def initialize_attributes(attributes)
|
30
|
+
attributes.each do |key, value|
|
31
|
+
send("#{key}=", value) if respond_to? key
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module OandaAPI
|
2
|
+
# A collection of a specific resource. Returned by API requests that return
|
3
|
+
# collections. See the {http://developer.oanda.com/rest-live/development-guide/ Oanda Development Guide}
|
4
|
+
# for documentation about resource attributes expected for specific requests.
|
5
|
+
#
|
6
|
+
# @example Getting candle information
|
7
|
+
# client = OandaAPI::Client::TokenClient.new :practice, token
|
8
|
+
# candles = client.candles( instrument: "EUR_USD",
|
9
|
+
# granularity: "M1",
|
10
|
+
# count: 1,
|
11
|
+
# candle_format: "midpoint" ).get
|
12
|
+
#
|
13
|
+
# candles # => OandaAPI::ResourceCollection
|
14
|
+
# candles.granularity # => "M1"
|
15
|
+
# candles.instrument # => "EUR_USD"
|
16
|
+
# candles.first # => OandaAPI::Resource::Candle
|
17
|
+
#
|
18
|
+
# @!attribute [r] location
|
19
|
+
# @return [String] see {ResourceBase#location}
|
20
|
+
class ResourceCollection
|
21
|
+
include Enumerable
|
22
|
+
|
23
|
+
attr_reader :location
|
24
|
+
|
25
|
+
# @param [Hash] attributes collection of resource attributes
|
26
|
+
#
|
27
|
+
# @param [OandaAPI::Client::ResourceDescriptor] resource_descriptor metadata
|
28
|
+
# about the resource collection and its elements.
|
29
|
+
def initialize(attributes, resource_descriptor)
|
30
|
+
attributes = {} if attributes.nil? || attributes.respond_to?(:empty) && attributes.empty?
|
31
|
+
fail ArgumentError, "Expecting a Hash" unless attributes.respond_to? :each_pair
|
32
|
+
@attributes = Utils.rubyize_keys attributes
|
33
|
+
@collection = @attributes.delete(resource_descriptor.collection_name) || []
|
34
|
+
@collection.map! { |resource| resource_descriptor.resource_klass.new resource }
|
35
|
+
@location = attributes.location if attributes.respond_to? :location
|
36
|
+
end
|
37
|
+
|
38
|
+
# @yield [OandaAPI::ResourceBase]
|
39
|
+
# @return [Enumerator]
|
40
|
+
def each
|
41
|
+
if block_given?
|
42
|
+
@collection.each { |el| yield el }
|
43
|
+
else
|
44
|
+
@collection.each
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# @private
|
49
|
+
# Responds to collection-scoped accessor methods that are specific to the
|
50
|
+
# type of resource collection. For example, a +Candle+ collection includes
|
51
|
+
# the collection-scoped methods +granularity+ and +instrument+.
|
52
|
+
def method_missing(sym, *args)
|
53
|
+
case
|
54
|
+
when @attributes.keys.include?(sym)
|
55
|
+
@attributes[sym]
|
56
|
+
when
|
57
|
+
@collection.respond_to?(sym)
|
58
|
+
@collection.send sym
|
59
|
+
else
|
60
|
+
super
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns +true+ for concrete, delegated and dynamic methods.
|
65
|
+
# @return [Boolean]
|
66
|
+
def respond_to?(sym)
|
67
|
+
case
|
68
|
+
when @attributes.keys.include?(sym)
|
69
|
+
true
|
70
|
+
when @collection.respond_to?(sym)
|
71
|
+
true
|
72
|
+
else
|
73
|
+
super
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module OandaAPI
|
2
|
+
# A few general purpose useful methods.
|
3
|
+
# Intentionally not implemented as monkey patches.
|
4
|
+
# Some wheel-reinventing to avoid adding extra dependencies.
|
5
|
+
module Utils
|
6
|
+
# Puts camelHumps where you expect them.
|
7
|
+
# @param [String] s
|
8
|
+
# @return [String]
|
9
|
+
def self.camelize(s)
|
10
|
+
s.to_s.gsub(/(?:_)([a-z\d]*)/i) { "#{$1.capitalize}" }.sub(/^(.)/) { $1.downcase }
|
11
|
+
end
|
12
|
+
|
13
|
+
# Naively plops an "s" at the end of a string.
|
14
|
+
# If the string is "" or nil, returns "".
|
15
|
+
# @param [String] s
|
16
|
+
# @return [String]
|
17
|
+
def self.pluralize(s)
|
18
|
+
return "" if s.to_s == ""
|
19
|
+
s.to_s =~ /s$/ ? s.to_s : "#{s}s"
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns a string with its trailing "s" vaporized.
|
23
|
+
# @param [String] s
|
24
|
+
# @return [String]
|
25
|
+
def self.singularize(s)
|
26
|
+
s.to_s.chomp("s")
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns a deep copy of a hash with its keys downcased, underscored
|
30
|
+
# and symbolized into ruby sweetness.
|
31
|
+
# @param [Hash] hash
|
32
|
+
# @return [Hash]
|
33
|
+
def self.rubyize_keys(hash)
|
34
|
+
transform_hash_keys(hash) { |key| underscore(key).to_sym }
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns a deep copy of a hash with its keys camelized,
|
38
|
+
# underscored and symbolized.
|
39
|
+
# @param [Hash] hash
|
40
|
+
# @return [Hash]
|
41
|
+
def self.stringify_keys(hash)
|
42
|
+
transform_hash_keys(hash) { |key| camelize key }
|
43
|
+
end
|
44
|
+
|
45
|
+
# Yields all keys of a hash, and safely applies whatever transform
|
46
|
+
# the block provides. Supports nested hashes.
|
47
|
+
#
|
48
|
+
# @param [Object] value can be a +Hash+, an +Array+ or scalar object type.
|
49
|
+
#
|
50
|
+
# @param [Block] block transforms the yielded key.
|
51
|
+
#
|
52
|
+
# @yield [Object] key the key to be prettied up.
|
53
|
+
#
|
54
|
+
# @return [Hash] a deep copy of the hash with it's keys transformed
|
55
|
+
# according to the design of the block.
|
56
|
+
def self.transform_hash_keys(value, &block)
|
57
|
+
case
|
58
|
+
when value.is_a?(Array)
|
59
|
+
value.map { |v| transform_hash_keys(v, &block) }
|
60
|
+
when value.is_a?(Hash)
|
61
|
+
Hash[value.map { |k, v| [yield(k), transform_hash_keys(v, &block)] }]
|
62
|
+
else
|
63
|
+
value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Yields all key/value pairs of a hash, and safely applies whatever
|
68
|
+
# transform the block provides to the values.
|
69
|
+
# Supports nested hashes and arrays.
|
70
|
+
#
|
71
|
+
# @param [Object] value can be a +Hash+, an +Array+ or scalar object type.
|
72
|
+
#
|
73
|
+
# @param [Object] key
|
74
|
+
#
|
75
|
+
# @param [Block] block transforms the yielded value.
|
76
|
+
#
|
77
|
+
# @return [Hash] a deep copy of the hash with it's values transformed
|
78
|
+
# according to the design of the block.
|
79
|
+
def self.transform_hash_values(value, key = nil, &block)
|
80
|
+
case
|
81
|
+
when value.is_a?(Array)
|
82
|
+
value.map { |v| transform_hash_values(v, key, &block) }
|
83
|
+
when value.is_a?(Hash)
|
84
|
+
Hash[value.map { |k, v| [k, transform_hash_values(v, k, &block)] }]
|
85
|
+
else
|
86
|
+
yield key, value
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Converts a string from camelCase to snake_case.
|
91
|
+
# @param [String] s
|
92
|
+
# @return [String]
|
93
|
+
def self.underscore(s)
|
94
|
+
s.to_s
|
95
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
96
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
97
|
+
.tr("-", "_")
|
98
|
+
.downcase
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|