ib-ruby 0.5.21 → 0.6.1
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/HISTORY +8 -0
- data/README.md +46 -27
- data/TODO +13 -2
- data/VERSION +1 -1
- data/bin/generic_data.rb +26 -0
- data/bin/place_order +1 -1
- data/lib/ib-ruby/connection.rb +126 -65
- data/lib/ib-ruby/messages/incoming.rb +3 -3
- data/lib/ib-ruby/models/bar.rb +11 -11
- data/lib/ib-ruby/models/combo_leg.rb +23 -29
- data/lib/ib-ruby/models/contract/bag.rb +34 -2
- data/lib/ib-ruby/models/contract/option.rb +2 -2
- data/lib/ib-ruby/models/contract.rb +151 -197
- data/lib/ib-ruby/models/execution.rb +27 -45
- data/lib/ib-ruby/models/model.rb +10 -4
- data/lib/ib-ruby/models/model_properties.rb +63 -0
- data/lib/ib-ruby/models/order.rb +274 -320
- data/lib/ib-ruby/symbols/stocks.rb +11 -5
- data/spec/account_helper.rb +80 -0
- data/spec/ib-ruby/connection_spec.rb +195 -52
- data/spec/ib-ruby/messages/incoming_spec.rb +4 -4
- data/spec/ib-ruby/models/combo_leg_spec.rb +1 -0
- data/spec/ib-ruby/models/contract_spec.rb +1 -1
- data/spec/ib-ruby/models/execution_spec.rb +73 -0
- data/spec/integration/account_info_spec.rb +12 -59
- data/spec/integration/contract_info_spec.rb +23 -37
- data/spec/integration/depth_data_spec.rb +4 -4
- data/spec/integration/historic_data_spec.rb +15 -27
- data/spec/integration/market_data_spec.rb +74 -61
- data/spec/integration/option_data_spec.rb +5 -48
- data/spec/integration/orders/execution_spec.rb +26 -31
- data/spec/integration/orders/open_order +2 -0
- data/spec/integration/orders/placement_spec.rb +28 -28
- data/spec/integration/orders/valid_ids_spec.rb +11 -11
- data/spec/integration_helper.rb +46 -32
- data/spec/message_helper.rb +4 -38
- data/spec/spec_helper.rb +2 -3
- metadata +9 -2
@@ -27,179 +27,154 @@ module IB
|
|
27
27
|
end
|
28
28
|
|
29
29
|
# Fields are Strings unless noted otherwise
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
30
|
+
prop :con_id, # int: The unique contract identifier.
|
31
|
+
:symbol, # This is the symbol of the underlying asset.
|
32
|
+
:sec_type, # Security type. Valid values are: SECURITY_TYPES
|
33
|
+
:strike, # double: The strike price.
|
34
|
+
:exchange, # The order destination, such as Smart.
|
35
|
+
:currency, # Only needed if there is an ambiguity, e.g. when SMART exchange
|
36
|
+
# and IBM is being requested (IBM can trade in GBP or USD).
|
37
|
+
|
38
|
+
:local_symbol, # Local exchange symbol of the underlying asset
|
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
|
+
:legs_description, # received in OpenOrder for all combos
|
57
|
+
|
58
|
+
:multiplier => :i,
|
59
|
+
# Future/option contract multiplier (only needed when multiple possibilities exist)
|
60
|
+
|
61
|
+
:primary_exchange =>
|
62
|
+
# non-aggregate (ie not the SMART) exchange that the contract trades on.
|
63
|
+
proc { |val|
|
64
|
+
val.upcase! if val.is_a?(String)
|
65
|
+
raise(ArgumentError.new("Don't set primary_exchange to smart")) if val == 'SMART'
|
66
|
+
self[:primary_exchange] = val
|
67
|
+
},
|
68
|
+
|
69
|
+
:right => # Specifies a Put or Call. Valid input values are: P, PUT, C, CALL
|
70
|
+
proc { |val|
|
71
|
+
self[:right] =
|
72
|
+
case val.to_s.upcase
|
73
|
+
when '', '0', '?'
|
74
|
+
nil
|
75
|
+
when 'PUT', 'P'
|
76
|
+
'PUT'
|
77
|
+
when 'CALL', 'C'
|
78
|
+
'CALL'
|
79
|
+
else
|
80
|
+
raise ArgumentError.new("Invalid right '#{val}' (must be one of PUT, CALL, P, C)")
|
81
|
+
end
|
82
|
+
},
|
83
|
+
|
84
|
+
:expiry => # The expiration date. Use the format YYYYMM.
|
85
|
+
proc { |val|
|
86
|
+
self[:expiry] =
|
87
|
+
case val.to_s
|
88
|
+
when /\d{6,8}/
|
89
|
+
val.to_s
|
90
|
+
when nil, ''
|
91
|
+
nil
|
92
|
+
else
|
93
|
+
raise ArgumentError.new("Invalid expiry '#{val}' (must be in format YYYYMM or YYYYMMDD)")
|
94
|
+
end
|
95
|
+
},
|
96
|
+
|
97
|
+
:sec_type => # Security type. Valid values are: SECURITY_TYPES
|
98
|
+
proc { |val|
|
99
|
+
val = nil if !val.nil? && val.empty?
|
100
|
+
unless val.nil? || SECURITY_TYPES.values.include?(val)
|
101
|
+
raise(ArgumentError.new("Invalid security type '#{val}' (must be one of #{SECURITY_TYPES.values}"))
|
102
|
+
end
|
103
|
+
self[:sec_type] = val
|
104
|
+
}
|
68
105
|
|
69
106
|
# ContractDetails fields are bundled into Contract proper, as it should be
|
70
107
|
# All fields Strings, unless specified otherwise:
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
:next_option_date, # only if bond has embedded options.
|
113
|
-
:next_option_type, # only if bond has embedded options.
|
114
|
-
:next_option_partial, # bool: # only if bond has embedded options.
|
115
|
-
:notes # Additional notes, if populated for the bond in IB's database
|
108
|
+
prop :market_name, # The market name for this contract.
|
109
|
+
:trading_class, # The trading class name for this contract.
|
110
|
+
:min_tick, # double: The minimum price tick.
|
111
|
+
:price_magnifier, # int: Allows execution and strike prices to be
|
112
|
+
# reported consistently with market data, historical data and the
|
113
|
+
# order price: Z on LIFFE is reported in index points, not GBP.
|
114
|
+
|
115
|
+
:order_types, # The list of valid order types for this contract.
|
116
|
+
:valid_exchanges, # The list of exchanges this contract is traded on.
|
117
|
+
:under_con_id, # int: The underlying contract ID.
|
118
|
+
:long_name, # Descriptive name of the asset.
|
119
|
+
:contract_month, # The contract month of the underlying for a futures contract.
|
120
|
+
|
121
|
+
# The industry classification of the underlying/product:
|
122
|
+
:industry, # Wide industry. For example, Financial.
|
123
|
+
:category, # Industry category. For example, InvestmentSvc.
|
124
|
+
:subcategory, # Subcategory. For example, Brokerage.
|
125
|
+
:time_zone, # Time zone for the trading hours of the product. For example, EST.
|
126
|
+
:trading_hours, # The trading hours of the product. For example:
|
127
|
+
# 20090507:0700-1830,1830-2330;20090508:CLOSED.
|
128
|
+
:liquid_hours, # The liquid trading hours of the product. For example,
|
129
|
+
# 20090507:0930-1600;20090508:CLOSED.
|
130
|
+
|
131
|
+
# Bond values:
|
132
|
+
:cusip, # The nine-character bond CUSIP or the 12-character SEDOL.
|
133
|
+
:ratings, # Credit rating of the issuer. Higher rating is less risky investment.
|
134
|
+
# Bond ratings are from Moody's and S&P respectively.
|
135
|
+
:desc_append, # Additional descriptive information about the bond.
|
136
|
+
:bond_type, # The type of bond, such as "CORP."
|
137
|
+
:coupon_type, # The type of bond coupon.
|
138
|
+
:callable, # bool: Can be called by the issuer under certain conditions.
|
139
|
+
:puttable, # bool: Can be sold back to the issuer under certain conditions
|
140
|
+
:coupon, # double: The interest rate used to calculate the amount you
|
141
|
+
# will receive in interest payments over the year. default 0
|
142
|
+
:convertible, # bool: Can be converted to stock under certain conditions.
|
143
|
+
:maturity, # The date on which the issuer must repay bond face value
|
144
|
+
:issue_date, # The date the bond was issued.
|
145
|
+
:next_option_date, # only if bond has embedded options.
|
146
|
+
:next_option_type, # only if bond has embedded options.
|
147
|
+
:next_option_partial, # bool: # only if bond has embedded options.
|
148
|
+
:notes # Additional notes, if populated for the bond in IB's database
|
116
149
|
|
117
150
|
# Used for Delta-Neutral Combo contracts only!
|
118
151
|
# UnderComp fields are bundled into Contract proper, as it should be.
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
152
|
+
prop :under_comp, # if not nil, attributes below are sent to server
|
153
|
+
#:under_con_id is is already defined in ContractDetails section
|
154
|
+
:under_delta, # double: The underlying stock or future delta.
|
155
|
+
:under_price # double: The price of the underlying.
|
123
156
|
|
124
157
|
attr_accessor :description # NB: local to ib-ruby, not part of TWS.
|
125
158
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
@under_con_id = 0
|
142
|
-
@min_tick = 0
|
143
|
-
@callable = false
|
144
|
-
@puttable = false
|
145
|
-
@coupon = 0
|
146
|
-
@convertible = false
|
147
|
-
@next_option_partial = false
|
148
|
-
|
149
|
-
super opts
|
150
|
-
end
|
151
|
-
|
152
|
-
# This property is from ContractDetails
|
159
|
+
DEFAULT_PROPS = {:con_id => 0,
|
160
|
+
:strike => 0,
|
161
|
+
:exchange => 'SMART',
|
162
|
+
:include_expired => false,
|
163
|
+
|
164
|
+
# These properties are from ContractDetails
|
165
|
+
:under_con_id => 0,
|
166
|
+
:min_tick => 0,
|
167
|
+
:callable => false,
|
168
|
+
:puttable => false,
|
169
|
+
:coupon => 0,
|
170
|
+
:convertible => false,
|
171
|
+
:next_option_partial => false, }
|
172
|
+
|
173
|
+
# NB: ContractDetails reference - to self!
|
153
174
|
def summary
|
154
175
|
self
|
155
176
|
end
|
156
177
|
|
157
|
-
# some protective filters
|
158
|
-
def primary_exchange= x
|
159
|
-
x.upcase! if x.is_a?(String)
|
160
|
-
|
161
|
-
# per http://chuckcaplan.com/twsapi/index.php/Class%20Contract
|
162
|
-
raise(ArgumentError.new("Don't set primary_exchange to smart")) if x == 'SMART'
|
163
|
-
|
164
|
-
@primary_exchange = x
|
165
|
-
end
|
166
|
-
|
167
|
-
def right= x
|
168
|
-
@right =
|
169
|
-
case x.to_s.upcase
|
170
|
-
when '', '0', '?'
|
171
|
-
nil
|
172
|
-
when 'PUT', 'P'
|
173
|
-
'PUT'
|
174
|
-
when 'CALL', 'C'
|
175
|
-
'CALL'
|
176
|
-
else
|
177
|
-
raise ArgumentError.new("Invalid right '#{x}' (must be one of PUT, CALL, P, C)")
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
def expiry= x
|
182
|
-
@expiry =
|
183
|
-
case x.to_s
|
184
|
-
when /\d{6,8}/
|
185
|
-
x.to_s
|
186
|
-
when ''
|
187
|
-
nil
|
188
|
-
else
|
189
|
-
raise ArgumentError.new("Invalid expiry '#{x}' (must be in format YYYYMM or YYYYMMDD)")
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
def sec_type= x
|
194
|
-
x = nil if !x.nil? && x.empty?
|
195
|
-
raise(ArgumentError.new("Invalid security type '#{x}' (must be one of #{SECURITY_TYPES.values}")) unless x.nil? || SECURITY_TYPES.values.include?(x)
|
196
|
-
@sec_type = x
|
197
|
-
end
|
198
|
-
|
199
|
-
def multiplier= x
|
200
|
-
@multiplier = x.to_i
|
201
|
-
end
|
202
|
-
|
203
178
|
# This returns an Array of data from the given contract.
|
204
179
|
# Different messages serialize contracts differently. Go figure.
|
205
180
|
# Note that it does NOT include the combo legs.
|
@@ -225,22 +200,6 @@ module IB
|
|
225
200
|
serialize :option, *fields
|
226
201
|
end
|
227
202
|
|
228
|
-
# This produces a string uniquely identifying this contract, in the format used
|
229
|
-
# for command line arguments in the IB-Ruby examples. The format is:
|
230
|
-
#
|
231
|
-
# symbol:security_type:expiry:strike:right:multiplier:exchange:primary_exchange:currency:local_symbol
|
232
|
-
#
|
233
|
-
# Fields not needed for a particular security should be left blank
|
234
|
-
# (e.g. strike and right are only relevant for options.)
|
235
|
-
#
|
236
|
-
# For example, to query the British pound futures contract trading on Globex
|
237
|
-
# expiring in September, 2008, the string is:
|
238
|
-
#
|
239
|
-
# GBP:FUT:200809:::62500:GLOBEX::USD:
|
240
|
-
def serialize_ib_ruby version
|
241
|
-
serialize.join(":")
|
242
|
-
end
|
243
|
-
|
244
203
|
# Serialize under_comp parameters
|
245
204
|
def serialize_under_comp *args
|
246
205
|
# EClientSocket.java, line 471:
|
@@ -254,27 +213,25 @@ module IB
|
|
254
213
|
end
|
255
214
|
end
|
256
215
|
|
257
|
-
|
258
|
-
|
259
|
-
# Some messages send open_close too, some don't. WTF.
|
260
|
-
# "BAG" is not really a contract, but a combination (combo) of securities.
|
261
|
-
# AKA basket or bag of securities. Individual securities in combo are represented
|
262
|
-
# by ComboLeg objects.
|
216
|
+
# Redefined in BAG subclass
|
263
217
|
def serialize_legs *fields
|
264
|
-
|
265
|
-
return [0] if legs.empty? || legs.nil?
|
266
|
-
[legs.size, legs.map { |leg| leg.serialize *fields }]
|
267
|
-
end
|
268
|
-
|
269
|
-
# Check if two Contracts have same legs (maybe in different order)
|
270
|
-
def same_legs? other
|
271
|
-
legs == other.legs ||
|
272
|
-
legs_description.split(',').sort == other.legs_description.split(',').sort
|
218
|
+
[]
|
273
219
|
end
|
274
220
|
|
275
|
-
#
|
276
|
-
|
277
|
-
|
221
|
+
# This produces a string uniquely identifying this contract, in the format used
|
222
|
+
# for command line arguments in the IB-Ruby examples. The format is:
|
223
|
+
#
|
224
|
+
# symbol:security_type:expiry:strike:right:multiplier:exchange:primary_exchange:currency:local_symbol
|
225
|
+
#
|
226
|
+
# Fields not needed for a particular security should be left blank
|
227
|
+
# (e.g. strike and right are only relevant for options.)
|
228
|
+
#
|
229
|
+
# For example, to query the British pound futures contract trading on Globex
|
230
|
+
# expiring in September, 2008, the string is:
|
231
|
+
#
|
232
|
+
# GBP:FUT:200809:::62500:GLOBEX::USD:
|
233
|
+
def serialize_ib_ruby version
|
234
|
+
serialize.join(":")
|
278
235
|
end
|
279
236
|
|
280
237
|
# Contract comparison
|
@@ -296,9 +253,6 @@ module IB
|
|
296
253
|
# Different currency
|
297
254
|
return false if currency && other.currency && currency != other.currency
|
298
255
|
|
299
|
-
# Different legs
|
300
|
-
return false unless same_legs? other
|
301
|
-
|
302
256
|
# Same con_id for all Bags, but unknown for new Contracts...
|
303
257
|
# 0 or nil con_id matches any
|
304
258
|
return false if con_id != 0 && other.con_id != 0 &&
|
@@ -5,54 +5,36 @@ module IB
|
|
5
5
|
# This is IB Order execution report.
|
6
6
|
# Instantiate with a Hash of attributes, to be auto-set via initialize in Model.
|
7
7
|
class Execution < Model
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
# Legacy
|
27
|
-
alias account_number account_name
|
28
|
-
alias account_number= account_name=
|
8
|
+
prop :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
|
+
:perm_id, # int: TWS id used to identify orders, remains
|
12
|
+
:exec_id, # String: Unique order execution id.
|
13
|
+
# the same over TWS sessions.
|
14
|
+
:time, # String: The order execution time.
|
15
|
+
:exchange, # String: Exchange that executed the order.
|
16
|
+
:price, # double: The order execution price.
|
17
|
+
:average_price, # double: Average price. Used in regular trades, combo
|
18
|
+
# trades and legs of the combo.
|
19
|
+
:shares, # int: The number of shares filled.
|
20
|
+
:cumulative_quantity, # int: Cumulative quantity. Used in regular
|
21
|
+
# trades, combo trades and legs of the combo
|
22
|
+
:liquidation, # int: This position is liquidated last should the need arise.
|
23
|
+
[:account_name, :account_number], # String: The customer account number.
|
24
|
+
:side => # String: Was the transaction a buy or a sale: BOT|SLD
|
25
|
+
{:set => proc { |val| self[:side] = val.to_s.upcase[0..0] == 'B' ? :buy : :sell }}
|
29
26
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
else
|
37
|
-
value
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def initialize opts = {}
|
42
|
-
@order_id = 0
|
43
|
-
@client_id = 0
|
44
|
-
@shares = 0
|
45
|
-
@price = 0
|
46
|
-
@perm_id = 0
|
47
|
-
@liquidation = 0
|
48
|
-
|
49
|
-
super opts
|
50
|
-
end
|
27
|
+
DEFAULT_PROPS = {:order_id => 0,
|
28
|
+
:client_id => 0,
|
29
|
+
:shares => 0,
|
30
|
+
:price => 0,
|
31
|
+
:perm_id => 0,
|
32
|
+
:liquidation => 0, }
|
51
33
|
|
52
34
|
def to_s
|
53
|
-
"<Execution #{
|
54
|
-
"cumulative: #{
|
55
|
-
"ids: #{
|
35
|
+
"<Execution #{time}: #{side} #{shares} @ #{price} on #{exchange}, " +
|
36
|
+
"cumulative: #{cumulative_quantity} @ #{average_price}, " +
|
37
|
+
"ids: #{order_id} order, #{perm_id} perm, #{exec_id} exec>"
|
56
38
|
end
|
57
39
|
end # Execution
|
58
40
|
end # module Models
|
data/lib/ib-ruby/models/model.rb
CHANGED
@@ -1,27 +1,33 @@
|
|
1
|
+
require 'ib-ruby/models/model_properties'
|
2
|
+
|
1
3
|
module IB
|
2
4
|
module Models
|
3
5
|
|
4
6
|
# Base IB data Model class, in future it will be developed into ActiveModel
|
5
7
|
class Model
|
8
|
+
extend ModelProperties
|
9
|
+
|
6
10
|
attr_reader :created_at
|
7
11
|
|
12
|
+
DEFAULT_PROPS = {}
|
13
|
+
|
8
14
|
# If a opts hash is given, keys are taken as attribute names, values as data.
|
9
15
|
# The model instance fields are then set automatically from the opts Hash.
|
10
|
-
#
|
11
16
|
def initialize(opts={})
|
12
17
|
raise ArgumentError.new("Argument must be a Hash") unless opts.is_a?(Hash)
|
13
18
|
@created_at = Time.now
|
14
19
|
|
15
|
-
|
20
|
+
props = self.class::DEFAULT_PROPS.merge(opts)
|
21
|
+
props.keys.each { |key| self.send("#{key}=", props[key]) }
|
16
22
|
end
|
17
23
|
|
18
24
|
# ActiveModel-style attribute accessors
|
19
25
|
def [] key
|
20
|
-
|
26
|
+
instance_variable_get "@#{key}".to_sym
|
21
27
|
end
|
22
28
|
|
23
29
|
def []= key, val
|
24
|
-
|
30
|
+
instance_variable_set "@#{key}".to_sym, val
|
25
31
|
end
|
26
32
|
|
27
33
|
end # Model
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module IB
|
2
|
+
module Models
|
3
|
+
|
4
|
+
# Module adding prop macro
|
5
|
+
module ModelProperties
|
6
|
+
|
7
|
+
def prop *properties
|
8
|
+
prop_hash = properties.last.is_a?(Hash) ? properties.pop : {}
|
9
|
+
|
10
|
+
properties.each { |names| define_property names, '' }
|
11
|
+
prop_hash.each { |names, type| define_property names, type }
|
12
|
+
end
|
13
|
+
|
14
|
+
def define_property names, body
|
15
|
+
aliases = [names].flatten
|
16
|
+
name = aliases.shift
|
17
|
+
instance_eval do
|
18
|
+
|
19
|
+
define_property_methods name, body
|
20
|
+
|
21
|
+
aliases.each do |ali|
|
22
|
+
alias_method "#{ali}", name
|
23
|
+
alias_method "#{ali}=", "#{name}="
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def define_property_methods name, body={}
|
29
|
+
#p name, body
|
30
|
+
case body
|
31
|
+
when '' # default getter and setter
|
32
|
+
define_property_methods name
|
33
|
+
when Proc # setter
|
34
|
+
define_property_methods name, :set => body
|
35
|
+
when Array # [setter, getter, validators]
|
36
|
+
define_property_methods name,
|
37
|
+
:get => body[0],
|
38
|
+
:set => body[1],
|
39
|
+
:validate => body[2]
|
40
|
+
when Hash # recursion ends HERE!
|
41
|
+
define_method(name, &body[:get] || proc { self[name] })
|
42
|
+
|
43
|
+
define_method("#{name}=", &body[:set] || proc { |value| self[name] = value })
|
44
|
+
|
45
|
+
[body[:validate]].flatten.compact.each do |validator|
|
46
|
+
case validator
|
47
|
+
when Proc
|
48
|
+
validates_each name, &validator
|
49
|
+
when Hash
|
50
|
+
validates name, validator
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
else
|
55
|
+
define_property_methods name, :set =>
|
56
|
+
proc { |value| self[name] = value.send "to_#{body}" }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end # module ModelProperties
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|