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.
Files changed (38) hide show
  1. data/HISTORY +8 -0
  2. data/README.md +46 -27
  3. data/TODO +13 -2
  4. data/VERSION +1 -1
  5. data/bin/generic_data.rb +26 -0
  6. data/bin/place_order +1 -1
  7. data/lib/ib-ruby/connection.rb +126 -65
  8. data/lib/ib-ruby/messages/incoming.rb +3 -3
  9. data/lib/ib-ruby/models/bar.rb +11 -11
  10. data/lib/ib-ruby/models/combo_leg.rb +23 -29
  11. data/lib/ib-ruby/models/contract/bag.rb +34 -2
  12. data/lib/ib-ruby/models/contract/option.rb +2 -2
  13. data/lib/ib-ruby/models/contract.rb +151 -197
  14. data/lib/ib-ruby/models/execution.rb +27 -45
  15. data/lib/ib-ruby/models/model.rb +10 -4
  16. data/lib/ib-ruby/models/model_properties.rb +63 -0
  17. data/lib/ib-ruby/models/order.rb +274 -320
  18. data/lib/ib-ruby/symbols/stocks.rb +11 -5
  19. data/spec/account_helper.rb +80 -0
  20. data/spec/ib-ruby/connection_spec.rb +195 -52
  21. data/spec/ib-ruby/messages/incoming_spec.rb +4 -4
  22. data/spec/ib-ruby/models/combo_leg_spec.rb +1 -0
  23. data/spec/ib-ruby/models/contract_spec.rb +1 -1
  24. data/spec/ib-ruby/models/execution_spec.rb +73 -0
  25. data/spec/integration/account_info_spec.rb +12 -59
  26. data/spec/integration/contract_info_spec.rb +23 -37
  27. data/spec/integration/depth_data_spec.rb +4 -4
  28. data/spec/integration/historic_data_spec.rb +15 -27
  29. data/spec/integration/market_data_spec.rb +74 -61
  30. data/spec/integration/option_data_spec.rb +5 -48
  31. data/spec/integration/orders/execution_spec.rb +26 -31
  32. data/spec/integration/orders/open_order +2 -0
  33. data/spec/integration/orders/placement_spec.rb +28 -28
  34. data/spec/integration/orders/valid_ids_spec.rb +11 -11
  35. data/spec/integration_helper.rb +46 -32
  36. data/spec/message_helper.rb +4 -38
  37. data/spec/spec_helper.rb +2 -3
  38. metadata +9 -2
@@ -27,179 +27,154 @@ module IB
27
27
  end
28
28
 
29
29
  # Fields are Strings unless noted otherwise
30
- attr_accessor :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
- :expiry, # The expiration date. Use the format YYYYMM.
34
- :strike, # double: The strike price.
35
- :right, # Specifies a Put or Call. Valid values are: P, PUT, C, CALL
36
- :multiplier, # Specifies a future or option contract multiplier
37
- # String? (only necessary when multiple possibilities exist)
38
-
39
- :exchange, # The order destination, such as Smart.
40
- :currency, # Ambiguities MAY require that currency field be specified,
41
- # for example, when SMART is the exchange and IBM is being
42
- # requested (IBM can trade in GBP or USD).
43
-
44
- :local_symbol, # Local exchange symbol of the underlying asset
45
- :primary_exchange, # pick a non-aggregate (ie not the SMART) exchange
46
- # that the contract trades on. DO NOT SET TO SMART.
47
-
48
- :include_expired, # When true, contract details requests and historical
49
- # data queries can be performed pertaining to expired contracts.
50
- # Note: Historical data queries on expired contracts are
51
- # limited to the last year of the contracts life, and are
52
- # only supported for expired futures contracts.
53
- # This field can NOT be set to true for orders.
54
-
55
- :sec_id_type, # Security identifier, when querying contract details or
56
- # when placing orders. Supported identifiers are:
57
- # - ISIN (Example: Apple: US0378331005)
58
- # - CUSIP (Example: Apple: 037833100)
59
- # - SEDOL (6-AN + check digit. Example: BAE: 0263494)
60
- # - RIC (exchange-independent RIC Root and exchange-
61
- # identifying suffix. Ex: AAPL.O for Apple on NASDAQ.)
62
- :sec_id, # Unique identifier of the given secIdType.
63
-
64
- # COMBOS
65
- :legs_description, # received in open order for all combos
66
- :legs # Dynamic memory structure used to store the leg
67
- # definitions for this contract.
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
- attr_accessor :summary, # NB: ContractDetails reference - to self!
72
- :market_name, # The market name for this contract.
73
- :trading_class, # The trading class name for this contract.
74
- :min_tick, # double: The minimum price tick.
75
- :price_magnifier, # int: Allows execution and strike prices to be
76
- # reported consistently with market data, historical data and the
77
- # order price: Z on LIFFE is reported in index points, not GBP.
78
-
79
- :order_types, # The list of valid order types for this contract.
80
- :valid_exchanges, # The list of exchanges this contract is traded on.
81
- :under_con_id, # int: The underlying contract ID.
82
- :long_name, # Descriptive name of the asset.
83
- :contract_month, # Typically the contract month of the underlying for
84
- # a futures contract.
85
-
86
- # The industry classification of the underlying/product:
87
- :industry, # Wide industry. For example, Financial.
88
- :category, # Industry category. For example, InvestmentSvc.
89
- :subcategory, # Subcategory. For example, Brokerage.
90
- :time_zone, # The ID of the time zone for the trading hours of the
91
- # product. For example, EST.
92
- :trading_hours, # The trading hours of the product. For example:
93
- # 20090507:0700-1830,1830-2330;20090508:CLOSED.
94
- :liquid_hours, # The liquid trading hours of the product. For example,
95
- # 20090507:0930-1600;20090508:CLOSED.
96
-
97
- # Bond values:
98
- :cusip, # The nine-character bond CUSIP or the 12-character SEDOL.
99
- :ratings, # Credit rating of the issuer. Higher credit rating generally
100
- # indicates a less risky investment. Bond ratings are from
101
- # Moody's and S&P respectively.
102
- :desc_append, # Additional descriptive information about the bond.
103
- :bond_type, # The type of bond, such as "CORP."
104
- :coupon_type, # The type of bond coupon.
105
- :callable, # bool: Can be called by the issuer under certain conditions.
106
- :puttable, # bool: Can be sold back to the issuer under certain conditions
107
- :coupon, # double: The interest rate used to calculate the amount you
108
- # will receive in interest payments over the year. default 0
109
- :convertible, # bool: Can be converted to stock under certain conditions.
110
- :maturity, # The date on which the issuer must repay bond face value
111
- :issue_date, # The date the bond was issued.
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
- attr_accessor :under_comp, # if not nil, attributes below are sent to server
120
- #:under_con_id is is already defined in ContractDetails section
121
- :under_delta, # double: The underlying stock or future delta.
122
- :under_price # double: The price of the underlying.
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
- alias combo_legs legs
127
- alias combo_legs= legs=
128
- alias combo_legs_description legs_description
129
- alias combo_legs_description= legs_description=
130
-
131
- def initialize opts = {}
132
- # Assign defaults to properties first!
133
- @con_id = 0
134
- @strike = 0
135
- @sec_type = ''
136
- @exchange = 'SMART'
137
- @include_expired = false
138
- @legs = Array.new
139
-
140
- # These properties are from ContractDetails
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
- ### Leg-related methods (better suited to BAG subclass?)
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
- return [] unless sec_type.upcase == "BAG"
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
- # IB-equivalent leg description. TODO: Rewrite with self[:legs_description]
276
- def legs_description
277
- @legs_description || legs.map { |leg| "#{leg.con_id}|#{leg.weight}" }.join(',')
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
- 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_name, #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
- # 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
- def side= value
31
- @side = case value
32
- when 'BOT'
33
- :BUY
34
- when 'SLD'
35
- :SELL
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 #{@time}: #{@side} #{@shares} @ #{@price} on #{@exchange}, " +
54
- "cumulative: #{@cumulative_quantity} @ #{@average_price}, " +
55
- "ids: #{@order_id} order, #{@perm_id} perm, #{@exec_id} exec>"
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
@@ -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
- opts.keys.each { |key| self.send("#{key}=", opts[key]) }
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
- self.send key
26
+ instance_variable_get "@#{key}".to_sym
21
27
  end
22
28
 
23
29
  def []= key, val
24
- self.send "#{key}=", val
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
+