ib-ruby 0.4.3 → 0.4.20
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 +32 -0
- data/HISTORY +68 -0
- data/README.rdoc +9 -6
- data/VERSION +1 -1
- data/bin/account_info +29 -0
- data/bin/contract_details +37 -0
- data/bin/depth_of_market +43 -0
- data/bin/historic_data +62 -0
- data/bin/{RequestHistoricData → historic_data_cli} +46 -91
- data/bin/market_data +49 -0
- data/bin/option_data +45 -0
- data/bin/template +21 -0
- data/bin/time_and_sales +63 -0
- data/lib/ib-ruby/connection.rb +166 -0
- data/lib/ib-ruby/constants.rb +91 -0
- data/lib/ib-ruby/messages/incoming.rb +807 -0
- data/lib/ib-ruby/messages/outgoing.rb +573 -0
- data/lib/ib-ruby/messages.rb +8 -1445
- data/lib/ib-ruby/models/bar.rb +26 -0
- data/lib/ib-ruby/models/contract.rb +335 -0
- data/lib/ib-ruby/models/execution.rb +55 -0
- data/lib/ib-ruby/models/model.rb +20 -0
- data/lib/ib-ruby/models/order.rb +262 -0
- data/lib/ib-ruby/models.rb +11 -0
- data/lib/ib-ruby/socket.rb +50 -0
- data/lib/ib-ruby/symbols/forex.rb +32 -72
- data/lib/ib-ruby/symbols/futures.rb +47 -68
- data/lib/ib-ruby/symbols/options.rb +30 -0
- data/lib/ib-ruby/symbols/stocks.rb +23 -0
- data/lib/ib-ruby/symbols.rb +9 -0
- data/lib/ib-ruby.rb +7 -8
- data/lib/legacy/bin/account_info_old +36 -0
- data/lib/legacy/bin/historic_data_old +81 -0
- data/lib/legacy/bin/market_data_old +68 -0
- data/lib/legacy/datatypes.rb +485 -0
- data/lib/legacy/ib-ruby.rb +10 -0
- data/lib/legacy/ib.rb +226 -0
- data/lib/legacy/messages.rb +1458 -0
- data/lib/version.rb +2 -3
- data/spec/ib-ruby/models/contract_spec.rb +261 -0
- data/spec/ib-ruby/models/order_spec.rb +64 -0
- data/spec/ib-ruby_spec.rb +0 -131
- metadata +106 -76
- data/bin/AccountInfo +0 -67
- data/bin/HistoricToCSV +0 -111
- data/bin/RequestMarketData +0 -78
- data/bin/SimpleTimeAndSales +0 -98
- data/bin/ib-ruby +0 -8
- data/lib/ib-ruby/datatypes.rb +0 -400
- data/lib/ib-ruby/ib.rb +0 -242
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'ib-ruby/models/model'
|
2
|
+
|
3
|
+
module IB
|
4
|
+
module Models
|
5
|
+
# This is a single data point delivered by HistoricData messages.
|
6
|
+
# Instantiate with a Hash of attributes, to be auto-set via initialize in Model.
|
7
|
+
class Bar < Model
|
8
|
+
attr_accessor :date, # The date-time stamp of the start of the bar. The format is
|
9
|
+
# determined by the reqHistoricalData() formatDate parameter.
|
10
|
+
:open, # The bar opening price.
|
11
|
+
:high, # The high price during the time covered by the bar.
|
12
|
+
:low, # The low price during the time covered by the bar.
|
13
|
+
:close, # The bar closing price.
|
14
|
+
:volume, # The bar opening price.
|
15
|
+
:wap, # Weighted average price during the time covered by the bar.
|
16
|
+
:has_gaps, # Whether or not there are gaps in the data.
|
17
|
+
:trades # int: When TRADES data history is returned, represents number
|
18
|
+
# of trades that occurred during the time period the bar covers
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
"<Bar #{@date}: wap: #{@wap}, OHLC: #{@open}, #{@high}, #{@low}, #{@close}, " +
|
22
|
+
(@trades ? "trades: #{@trades}," : "") + " vol: #{@volume}, gaps? #{@has_gaps}>"
|
23
|
+
end
|
24
|
+
end # Bar
|
25
|
+
end # module Models
|
26
|
+
end # module IB
|
@@ -0,0 +1,335 @@
|
|
1
|
+
require 'ib-ruby/models/model'
|
2
|
+
|
3
|
+
# TODO: Implement equals() according to the criteria in IB's Java client.
|
4
|
+
|
5
|
+
module IB
|
6
|
+
module Models
|
7
|
+
class Contract < Model
|
8
|
+
|
9
|
+
# Valid security types (sec_type attribute)
|
10
|
+
SECURITY_TYPES = {:stock => "STK",
|
11
|
+
:option => "OPT",
|
12
|
+
:future => "FUT",
|
13
|
+
:index => "IND",
|
14
|
+
:futures_option => "FOP",
|
15
|
+
:forex => "CASH",
|
16
|
+
:bag => "BAG"}
|
17
|
+
|
18
|
+
BAG_SEC_TYPE = "BAG"
|
19
|
+
|
20
|
+
# Fields are Strings unless noted otherwise
|
21
|
+
attr_accessor :con_id, # int: The unique contract identifier.
|
22
|
+
:symbol, # This is the symbol of the underlying asset.
|
23
|
+
:sec_type, # Security type. Valid values are: SECURITY_TYPES
|
24
|
+
:expiry, # The expiration date. Use the format YYYYMM.
|
25
|
+
:strike, # double: The strike price.
|
26
|
+
:right, # Specifies a Put or Call. Valid values are: P, PUT, C, CALL
|
27
|
+
:multiplier, # Specifies a future or option contract multiplier
|
28
|
+
# String? (only necessary when multiple possibilities exist)
|
29
|
+
|
30
|
+
:exchange, # The order destination, such as Smart.
|
31
|
+
:currency, # Ambiguities MAY require that currency field be specified,
|
32
|
+
# for example, when SMART is the exchange and IBM is being
|
33
|
+
# requested (IBM can trade in GBP or USD).
|
34
|
+
|
35
|
+
:local_symbol, # Local exchange symbol of the underlying asset
|
36
|
+
:primary_exchange, # pick a non-aggregate (ie not the SMART) exchange
|
37
|
+
# that the contract trades on. DO NOT SET TO SMART.
|
38
|
+
|
39
|
+
:include_expired, # When true, contract details requests and historical
|
40
|
+
# data queries can be performed pertaining to expired contracts.
|
41
|
+
# Note: Historical data queries on expired contracts are
|
42
|
+
# limited to the last year of the contracts life, and are
|
43
|
+
# only supported for expired futures contracts.
|
44
|
+
# This field can NOT be set to true for orders.
|
45
|
+
|
46
|
+
:sec_id_type, # Security identifier, when querying contract details or
|
47
|
+
# when placing orders. Supported identifiers are:
|
48
|
+
# - ISIN (Example: Apple: US0378331005)
|
49
|
+
# - CUSIP (Example: Apple: 037833100)
|
50
|
+
# - SEDOL (6-AN + check digit. Example: BAE: 0263494)
|
51
|
+
# - RIC (exchange-independent RIC Root and exchange-
|
52
|
+
# identifying suffix. Ex: AAPL.O for Apple on NASDAQ.)
|
53
|
+
:sec_id, # Unique identifier of the given secIdType.
|
54
|
+
|
55
|
+
# COMBOS
|
56
|
+
:combo_legs_description, # received in open order for all combos
|
57
|
+
:combo_legs # Dynamic memory structure used to store the leg
|
58
|
+
# definitions for this contract.
|
59
|
+
|
60
|
+
# ContractDetails fields are bundled into Contract proper, as it should be
|
61
|
+
# All fields Strings, unless specified otherwise:
|
62
|
+
attr_accessor :summary, # NB: ContractDetails reference - to self!
|
63
|
+
:market_name, # The market name for this contract.
|
64
|
+
:trading_class, # The trading class name for this contract.
|
65
|
+
:min_tick, # double: The minimum price tick.
|
66
|
+
:price_magnifier, # int: Allows execution and strike prices to be
|
67
|
+
# reported consistently with market data, historical data and the
|
68
|
+
# order price: Z on LIFFE is reported in index points, not GBP.
|
69
|
+
|
70
|
+
:order_types, # The list of valid order types for this contract.
|
71
|
+
:valid_exchanges, # The list of exchanges this contract is traded on.
|
72
|
+
:under_con_id, # int: The underlying contract ID.
|
73
|
+
:long_name, # Descriptive name of the asset.
|
74
|
+
:contract_month, # Typically the contract month of the underlying for
|
75
|
+
# a futures contract.
|
76
|
+
|
77
|
+
# The industry classification of the underlying/product:
|
78
|
+
:industry, # Wide industry. For example, Financial.
|
79
|
+
:category, # Industry category. For example, InvestmentSvc.
|
80
|
+
:subcategory, # Subcategory. For example, Brokerage.
|
81
|
+
:time_zone, # The ID of the time zone for the trading hours of the
|
82
|
+
# product. For example, EST.
|
83
|
+
:trading_hours, # The trading hours of the product. For example:
|
84
|
+
# 20090507:0700-1830,1830-2330;20090508:CLOSED.
|
85
|
+
:liquid_hours, # The liquid trading hours of the product. For example,
|
86
|
+
# 20090507:0930-1600;20090508:CLOSED.
|
87
|
+
|
88
|
+
# Bond values:
|
89
|
+
:cusip, # The nine-character bond CUSIP or the 12-character SEDOL.
|
90
|
+
:ratings, # Credit rating of the issuer. Higher credit rating generally
|
91
|
+
# indicates a less risky investment. Bond ratings are from
|
92
|
+
# Moody's and S&P respectively.
|
93
|
+
:desc_append, # Additional descriptive information about the bond.
|
94
|
+
:bond_type, # The type of bond, such as "CORP."
|
95
|
+
:coupon_type, # The type of bond coupon.
|
96
|
+
:callable, # bool: Can be called by the issuer under certain conditions.
|
97
|
+
:puttable, # bool: Can be sold back to the issuer under certain conditions
|
98
|
+
:coupon, # double: The interest rate used to calculate the amount you
|
99
|
+
# will receive in interest payments over the year. default 0
|
100
|
+
:convertible, # bool: Can be converted to stock under certain conditions.
|
101
|
+
:maturity, # The date on which the issuer must repay bond face value
|
102
|
+
:issue_date, # The date the bond was issued.
|
103
|
+
:next_option_date, # only if bond has embedded options.
|
104
|
+
:next_option_type, # only if bond has embedded options.
|
105
|
+
:next_option_partial, # bool: # only if bond has embedded options.
|
106
|
+
:notes # Additional notes, if populated for the bond in IB's database
|
107
|
+
|
108
|
+
# Used for Delta-Neutral Combo contracts only!
|
109
|
+
# UnderComp fields are bundled into Contract proper, as it should be.
|
110
|
+
# Already defined
|
111
|
+
attr_accessor :under_comp, # if not nil, attributes below are sent to server
|
112
|
+
#:under_con_id is is already defined in ContractDetails section
|
113
|
+
:under_delta, # double: The underlying stock or future delta.
|
114
|
+
:under_price # double: The price of the underlying.
|
115
|
+
|
116
|
+
# NB :description field is entirely local to ib-ruby, and not part of TWS.
|
117
|
+
# You can use it to store whatever arbitrary data you want.
|
118
|
+
attr_accessor :description
|
119
|
+
|
120
|
+
def initialize opts = {}
|
121
|
+
# Assign defaults to properties first!
|
122
|
+
@con_id = 0
|
123
|
+
@strike = 0
|
124
|
+
@sec_type = ''
|
125
|
+
@include_expired = false
|
126
|
+
@combo_legs = Array.new
|
127
|
+
|
128
|
+
# These properties are from ContractDetails
|
129
|
+
@summary = self
|
130
|
+
@under_con_id = 0
|
131
|
+
@min_tick = 0
|
132
|
+
@callable = false
|
133
|
+
@puttable = false
|
134
|
+
@coupon = 0
|
135
|
+
@convertible = false
|
136
|
+
@next_option_partial = false
|
137
|
+
|
138
|
+
super opts
|
139
|
+
end
|
140
|
+
|
141
|
+
# some protective filters
|
142
|
+
def primary_exchange=(x)
|
143
|
+
x.upcase! if x.is_a?(String)
|
144
|
+
|
145
|
+
# per http://chuckcaplan.com/twsapi/index.php/Class%20Contract
|
146
|
+
raise(ArgumentError.new("Don't set primary_exchange to smart")) if x == "SMART"
|
147
|
+
|
148
|
+
@primary_exchange = x
|
149
|
+
end
|
150
|
+
|
151
|
+
def right=(x)
|
152
|
+
x.upcase! if x.is_a?(String)
|
153
|
+
x = nil if !x.nil? && x.empty?
|
154
|
+
raise(ArgumentError.new("Invalid right \"#{x}\" (must be one of PUT, CALL, P, C)")) unless x.nil? || ["PUT", "CALL", "P", "C", "0"].include?(x)
|
155
|
+
@right = x
|
156
|
+
end
|
157
|
+
|
158
|
+
def expiry=(x)
|
159
|
+
x = x.to_s
|
160
|
+
if (x.nil? || !(x =~ /\d{6,8}/)) and !x.empty? then
|
161
|
+
raise ArgumentError.new("Invalid expiry \"#{x}\" (must be in format YYYYMM or YYYYMMDD)")
|
162
|
+
end
|
163
|
+
@expiry = x
|
164
|
+
end
|
165
|
+
|
166
|
+
def sec_type=(x)
|
167
|
+
x = nil if !x.nil? && x.empty?
|
168
|
+
raise(ArgumentError.new("Invalid security type \"#{x}\" (see SECURITY_TYPES constant in Contract class for valid types)")) unless x.nil? || SECURITY_TYPES.values.include?(x)
|
169
|
+
@sec_type = x
|
170
|
+
end
|
171
|
+
|
172
|
+
def reset
|
173
|
+
@combo_legs = Array.new
|
174
|
+
@strike = 0
|
175
|
+
end
|
176
|
+
|
177
|
+
# This returns an Array of data from the given contract, in standard format.
|
178
|
+
# Different messages serialize contracts differently. Go figure.
|
179
|
+
# Note that it does not include the combo legs.
|
180
|
+
def serialize(type = :long)
|
181
|
+
[symbol,
|
182
|
+
sec_type,
|
183
|
+
expiry,
|
184
|
+
strike,
|
185
|
+
right,
|
186
|
+
multiplier,
|
187
|
+
exchange] +
|
188
|
+
(type == :long ? [primary_exchange] : []) +
|
189
|
+
[currency,
|
190
|
+
local_symbol]
|
191
|
+
end
|
192
|
+
|
193
|
+
# @Legacy
|
194
|
+
def serialize_long(version)
|
195
|
+
serialize(:long)
|
196
|
+
end
|
197
|
+
|
198
|
+
# @Legacy
|
199
|
+
def serialize_short(version)
|
200
|
+
serialize(:short)
|
201
|
+
end
|
202
|
+
|
203
|
+
# This produces a string uniquely identifying this contract, in the format used
|
204
|
+
# for command line arguments in the IB-Ruby examples. The format is:
|
205
|
+
#
|
206
|
+
# symbol:security_type:expiry:strike:right:multiplier:exchange:primary_exchange:currency:local_symbol
|
207
|
+
#
|
208
|
+
# Fields not needed for a particular security should be left blank
|
209
|
+
# (e.g. strike and right are only relevant for options.)
|
210
|
+
#
|
211
|
+
# For example, to query the British pound futures contract trading on Globex
|
212
|
+
# expiring in September, 2008, the string is:
|
213
|
+
#
|
214
|
+
# GBP:FUT:200809:::62500:GLOBEX::USD:
|
215
|
+
def serialize_ib_ruby(version)
|
216
|
+
serialize.join(":")
|
217
|
+
end
|
218
|
+
|
219
|
+
# This returns a Contract initialized from the serialize_ib_ruby format string.
|
220
|
+
def self.from_ib_ruby(string)
|
221
|
+
c = Contract.new
|
222
|
+
c.symbol, c.sec_type, c.expiry, c.strike, c.right, c.multiplier,
|
223
|
+
c.exchange, c.primary_exchange, c.currency, c.local_symbol = string.split(":")
|
224
|
+
c
|
225
|
+
end
|
226
|
+
|
227
|
+
# Serialize under_comp parameters
|
228
|
+
def serialize_under_comp(*args)
|
229
|
+
# EClientSocket.java, line 471:
|
230
|
+
if under_comp
|
231
|
+
[true,
|
232
|
+
under_con_id,
|
233
|
+
under_delta,
|
234
|
+
under_price]
|
235
|
+
else
|
236
|
+
[false]
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def serialize_algo(*args)
|
241
|
+
raise "Unimplemented"
|
242
|
+
#if (m_serverVersion >= MIN_SERVER_VER_ALGO_ORDERS) {
|
243
|
+
# send( order.m_algoStrategy);
|
244
|
+
# if( !IsEmpty(order.m_algoStrategy)) {
|
245
|
+
# java.util.Vector algoParams = order.m_algoParams;
|
246
|
+
# int algoParamsCount = algoParams == null ? 0 : algoParams.size();
|
247
|
+
# send( algoParamsCount);
|
248
|
+
# if( algoParamsCount > 0) {
|
249
|
+
# for( int i = 0; i < algoParamsCount; ++i) {
|
250
|
+
# TagValue tagValue = (TagValue)algoParams.get(i);
|
251
|
+
# send( tagValue.m_tag);
|
252
|
+
# send( tagValue.m_value);
|
253
|
+
# }
|
254
|
+
# }
|
255
|
+
# }
|
256
|
+
#}
|
257
|
+
end
|
258
|
+
|
259
|
+
# Some messages send open_close too, some don't. WTF.
|
260
|
+
def serialize_combo_legs(type = :short)
|
261
|
+
# No idea what "BAG" means. Copied from the Java code.
|
262
|
+
return [] unless sec_type.upcase == "BAG"
|
263
|
+
return [0] if combo_legs.empty? || combo_legs.nil?
|
264
|
+
[combo_legs.size,
|
265
|
+
combo_legs.map { |leg| leg.serialize(type) }]
|
266
|
+
end
|
267
|
+
|
268
|
+
def to_human
|
269
|
+
"<Contract: " + [symbol, expiry, sec_type, strike, right, exchange, currency].join("-") + ">"
|
270
|
+
end
|
271
|
+
|
272
|
+
def to_short
|
273
|
+
"#{symbol}#{expiry}#{strike}#{right}#{exchange}#{currency}"
|
274
|
+
end
|
275
|
+
|
276
|
+
def to_s
|
277
|
+
to_human
|
278
|
+
end
|
279
|
+
|
280
|
+
# ComboLeg is an internal class of Contract, as it should be
|
281
|
+
class ComboLeg < Model
|
282
|
+
# // open/close leg value is same as combo
|
283
|
+
# Specifies whether the order is an open or close order. Valid values are:
|
284
|
+
SAME = 0 # Same as the parent security. The only option for retail customers.
|
285
|
+
OPEN = 1 # Open. This value is only valid for institutional customers.
|
286
|
+
CLOSE = 2 # Close. This value is only valid for institutional customers.
|
287
|
+
UNKNOWN = 3
|
288
|
+
|
289
|
+
|
290
|
+
attr_accessor :con_id, # int: The unique contract identifier specifying the security.
|
291
|
+
:ratio, # int: Select the relative number of contracts for the leg you
|
292
|
+
# are constructing. To help determine the ratio for a
|
293
|
+
# specific combination order, refer to the Interactive
|
294
|
+
# Analytics section of the User's Guide.
|
295
|
+
|
296
|
+
:action, # String: BUY/SELL/SSHORT/SSHORTX
|
297
|
+
# The side (buy or sell) for the leg you are constructing.
|
298
|
+
:exchange, # String: exchange to which the complete combination
|
299
|
+
# order will be routed.
|
300
|
+
:open_close, # int: Specifies whether the order is an open or close order.
|
301
|
+
# Valid values: ComboLeg::SAME/OPEN/CLOSE/UNKNOWN
|
302
|
+
|
303
|
+
# For institutional customers only! For stock legs when doing short sale
|
304
|
+
:short_sale_slot, # int: 0 - retail, 1 = clearing broker, 2 = third party
|
305
|
+
:designated_location, # String: Only for shortSaleSlot == 2.
|
306
|
+
# Otherwise leave blank or orders will be rejected.
|
307
|
+
:exempt_code # int: ?
|
308
|
+
|
309
|
+
def initialize opts = {}
|
310
|
+
@con_id = 0
|
311
|
+
@ratio = 0
|
312
|
+
@open_close = 0
|
313
|
+
@short_sale_slot = 0
|
314
|
+
@designated_location = ''
|
315
|
+
@exempt_code = -1
|
316
|
+
|
317
|
+
super opts
|
318
|
+
end
|
319
|
+
|
320
|
+
# Some messages include open_close, some don't. wtf.
|
321
|
+
def serialize(type = :short)
|
322
|
+
[con_id,
|
323
|
+
ratio,
|
324
|
+
action,
|
325
|
+
exchange] +
|
326
|
+
type == :short ? [] : [open_close,
|
327
|
+
short_sale_slot,
|
328
|
+
designated_location,
|
329
|
+
exempt_code]
|
330
|
+
end
|
331
|
+
end # ComboLeg
|
332
|
+
|
333
|
+
end # class Contract
|
334
|
+
end # module Models
|
335
|
+
end # module IB
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'ib-ruby/models/model'
|
2
|
+
|
3
|
+
module IB
|
4
|
+
module Models
|
5
|
+
# This is IB Order execution report.
|
6
|
+
# Instantiate with a Hash of attributes, to be auto-set via initialize in Model.
|
7
|
+
class Execution < Model
|
8
|
+
attr_accessor :order_id, # int: order id. TWS orders have a fixed order id of 0.
|
9
|
+
:client_id, # int: id of the client that placed the order.
|
10
|
+
# TWS orders have a fixed client id of 0.
|
11
|
+
:exec_id, # String: Unique order execution id.
|
12
|
+
:time, # String: The order execution time.
|
13
|
+
:account_number, #String: The customer account number.
|
14
|
+
:exchange, # String: Exchange that executed the order.
|
15
|
+
:side, # String: Was the transaction a buy or a sale: BOT|SLD
|
16
|
+
:shares, # int: The number of shares filled.
|
17
|
+
:price, # double: The order execution price.
|
18
|
+
:perm_id, # int: TWS id used to identify orders, remains
|
19
|
+
# the same over TWS sessions.
|
20
|
+
:liquidation, # int: Identifies the position as one to be liquidated
|
21
|
+
# last should the need arise.
|
22
|
+
:cumulative_quantity, # int: Cumulative quantity. Used in regular
|
23
|
+
# trades, combo trades and legs of the combo
|
24
|
+
:average_price # double: Average price. Used in regular trades, combo
|
25
|
+
# trades and legs of the combo.
|
26
|
+
|
27
|
+
def side= value
|
28
|
+
@side = case value
|
29
|
+
when 'BOT'
|
30
|
+
:bought
|
31
|
+
when 'SLD'
|
32
|
+
:sold
|
33
|
+
else
|
34
|
+
value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize opts = {}
|
39
|
+
@order_id = 0
|
40
|
+
@client_id = 0
|
41
|
+
@shares = 0
|
42
|
+
@price = 0
|
43
|
+
@perm_id = 0
|
44
|
+
@liquidation = 0
|
45
|
+
|
46
|
+
super opts
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_s
|
50
|
+
"<Execution #{@time}: #{@side} #{@shares} shares @ #{@price} on #{@exchange}, " +
|
51
|
+
"IDs: #{@order_id} order, #{@exec_id} exec, #{@perm_id} perm>"
|
52
|
+
end
|
53
|
+
end # Execution
|
54
|
+
end # module Models
|
55
|
+
end # module IB
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module IB
|
2
|
+
module Models
|
3
|
+
|
4
|
+
# Base IB data Model class, in future it will be developed into ActiveModel
|
5
|
+
class Model
|
6
|
+
attr_reader :created_at
|
7
|
+
|
8
|
+
# If a opts hash is given, keys are taken as attribute names, values as data.
|
9
|
+
# The model instance fields are then set automatically from the opts Hash.
|
10
|
+
#
|
11
|
+
def initialize(opts={})
|
12
|
+
raise ArgumentError.new("Argument must be a Hash") unless opts.is_a?(Hash)
|
13
|
+
@created_at = Time.now
|
14
|
+
opts.keys.each do |key|
|
15
|
+
self.send((key.to_s + "=").to_sym, opts[key])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end # Model
|
19
|
+
end # module Models
|
20
|
+
end # module IB
|