oanda_api 0.8.1 → 0.8.3
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.
- 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
|