qbfc 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. data/.gitignore +11 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +85 -0
  4. data/Rakefile +84 -0
  5. data/VERSION +1 -0
  6. data/lib/qbfc.rb +41 -0
  7. data/lib/qbfc/base.rb +82 -0
  8. data/lib/qbfc/element.rb +243 -0
  9. data/lib/qbfc/entities/generated.rb +8 -0
  10. data/lib/qbfc/entity.rb +11 -0
  11. data/lib/qbfc/info.rb +42 -0
  12. data/lib/qbfc/infos/generated.rb +9 -0
  13. data/lib/qbfc/item.rb +29 -0
  14. data/lib/qbfc/items/generated.rb +11 -0
  15. data/lib/qbfc/list.rb +84 -0
  16. data/lib/qbfc/lists/account.rb +24 -0
  17. data/lib/qbfc/lists/generated.rb +15 -0
  18. data/lib/qbfc/lists/qb_class.rb +25 -0
  19. data/lib/qbfc/modifiable.rb +31 -0
  20. data/lib/qbfc/ole_wrapper.rb +201 -0
  21. data/lib/qbfc/qb_collection.rb +26 -0
  22. data/lib/qbfc/qb_types.rb +18 -0
  23. data/lib/qbfc/qbfc_const.rb +14 -0
  24. data/lib/qbfc/report.rb +95 -0
  25. data/lib/qbfc/reports/aging.rb +13 -0
  26. data/lib/qbfc/reports/budget_summary.rb +13 -0
  27. data/lib/qbfc/reports/custom_detail.rb +9 -0
  28. data/lib/qbfc/reports/custom_summary.rb +9 -0
  29. data/lib/qbfc/reports/general_detail.rb +44 -0
  30. data/lib/qbfc/reports/general_summary.rb +33 -0
  31. data/lib/qbfc/reports/job.rb +14 -0
  32. data/lib/qbfc/reports/payroll_detail.rb +13 -0
  33. data/lib/qbfc/reports/payroll_summary.rb +13 -0
  34. data/lib/qbfc/reports/rows.rb +51 -0
  35. data/lib/qbfc/reports/time.rb +12 -0
  36. data/lib/qbfc/request.rb +295 -0
  37. data/lib/qbfc/session.rb +147 -0
  38. data/lib/qbfc/terms.rb +10 -0
  39. data/lib/qbfc/terms/generated.rb +10 -0
  40. data/lib/qbfc/transaction.rb +110 -0
  41. data/lib/qbfc/transactions/generated.rb +25 -0
  42. data/lib/qbfc/voidable.rb +11 -0
  43. data/qbfc.gemspec +166 -0
  44. data/spec/fixtures/test.lgb +0 -0
  45. data/spec/fixtures/test.qbw +0 -0
  46. data/spec/fixtures/test.qbw.TLG +0 -0
  47. data/spec/integration/add_spec.rb +31 -0
  48. data/spec/integration/base_spec.rb +18 -0
  49. data/spec/integration/belongs_to_spec.rb +64 -0
  50. data/spec/integration/company_spec.rb +30 -0
  51. data/spec/integration/conditions_spec.rb +59 -0
  52. data/spec/integration/customer_spec.rb +46 -0
  53. data/spec/integration/element_finders_spec.rb +20 -0
  54. data/spec/integration/quick_test.rb +31 -0
  55. data/spec/integration/request_options_spec.rb +68 -0
  56. data/spec/rcov.opts +1 -0
  57. data/spec/spec.opts +6 -0
  58. data/spec/spec_helper.rb +62 -0
  59. data/spec/unit/base_spec.rb +138 -0
  60. data/spec/unit/element_finder_spec.rb +185 -0
  61. data/spec/unit/element_spec.rb +108 -0
  62. data/spec/unit/entities/generated_spec.rb +18 -0
  63. data/spec/unit/entity_spec.rb +18 -0
  64. data/spec/unit/info/generated_spec.rb +12 -0
  65. data/spec/unit/info_spec.rb +48 -0
  66. data/spec/unit/item_spec.rb +33 -0
  67. data/spec/unit/items/generated_spec.rb +16 -0
  68. data/spec/unit/list_finders_spec.rb +129 -0
  69. data/spec/unit/list_spec.rb +86 -0
  70. data/spec/unit/lists/account_spec.rb +20 -0
  71. data/spec/unit/lists/generated_spec.rb +15 -0
  72. data/spec/unit/lists/qb_class_spec.rb +9 -0
  73. data/spec/unit/modifiable_spec.rb +84 -0
  74. data/spec/unit/ole_wrapper_spec.rb +337 -0
  75. data/spec/unit/qb_collection_spec.rb +13 -0
  76. data/spec/unit/qbfc_const_spec.rb +10 -0
  77. data/spec/unit/qbfc_spec.rb +10 -0
  78. data/spec/unit/report_spec.rb +12 -0
  79. data/spec/unit/request_query_survey.txt +48 -0
  80. data/spec/unit/request_spec.rb +486 -0
  81. data/spec/unit/session_spec.rb +144 -0
  82. data/spec/unit/terms/generated_spec.rb +14 -0
  83. data/spec/unit/terms_spec.rb +18 -0
  84. data/spec/unit/transaction_finders_spec.rb +125 -0
  85. data/spec/unit/transaction_spec.rb +94 -0
  86. data/spec/unit/transactions/generated_spec.rb +20 -0
  87. data/spec/unit/voidable_spec.rb +32 -0
  88. data/tasks/qbfc_tasks.rake +4 -0
  89. metadata +182 -0
@@ -0,0 +1,8 @@
1
+ module QBFC
2
+ # Generated Entity Types (Inherit from List)
3
+ ENTITY_TYPES = %w{Customer Employee OtherName Vendor}
4
+
5
+ # Generate Entity subclasses
6
+ # NB: All Entities are Modifiable
7
+ generate(ENTITY_TYPES, Entity, {Modifiable => ENTITY_TYPES})
8
+ end
@@ -0,0 +1,11 @@
1
+ module QBFC
2
+ # Entity objects are Customers, Employees, Vendors and OtherNames
3
+ class Entity < List
4
+ is_base_class
5
+ end
6
+ end
7
+
8
+ # Require subclass files
9
+ Dir.new(File.dirname(__FILE__) + '/entities').each do |file|
10
+ require('qbfc/entities/' + File.basename(file)) if File.extname(file) == ".rb"
11
+ end
@@ -0,0 +1,42 @@
1
+ module QBFC
2
+ # Info objects are those which have only one instance within Quickbooks,
3
+ # which are: Company, CompanyActivity, Host and Preferences.
4
+ #
5
+ # Access through QBFC for these objects is read-only.
6
+ #
7
+ # QBFC::Info can be accessed via session::company, for example, or
8
+ # through QBFC::Company::get(session).
9
+ class Info < Base
10
+ class << self
11
+
12
+ # Get the Info object for the given session.
13
+ # session.[qb_name] aliases this functionality.
14
+ # For example QBFC::Company.get(session) and
15
+ # session.company are equivalent.
16
+ #
17
+ # It accepts the follow options as a hash:
18
+ # - <tt>:owner_id</tt>: One or more OwnerIDs, used in accessing
19
+ # custom fields (aka private data extensions).
20
+ def get(sess, *args)
21
+ # Setup q, options and base_options arguments
22
+ q, options, base_options = parse_find_args(*args)
23
+ q ||= create_query(sess)
24
+ q.apply_options(options)
25
+
26
+ new(sess, q.response)
27
+ end
28
+
29
+ # This is a convenience alias for +get+.
30
+ # It exists solely so that I don't have to modify
31
+ # Session#method_missing.
32
+ def find(sess, what, *args) #:nodoc:
33
+ get(sess, *args)
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ # Require subclass files
40
+ Dir.new(File.dirname(__FILE__) + '/infos').each do |file|
41
+ require('qbfc/infos/' + File.basename(file)) if File.extname(file) == ".rb"
42
+ end
@@ -0,0 +1,9 @@
1
+ module QBFC
2
+
3
+ # Generated Info types
4
+ INFO_TYPES = %w{Company CompanyActivity Host Preferences}
5
+
6
+ # Generate List subclasses
7
+ generate(INFO_TYPES, Info)
8
+
9
+ end
@@ -0,0 +1,29 @@
1
+ module QBFC
2
+ class Item < List
3
+ is_base_class
4
+
5
+ class << self
6
+
7
+ # Adds a SpecialItem.
8
+ #
9
+ # +item_type+ should be a constent, for example:
10
+ #
11
+ # Account.add_special(sess, QBFC_CONST::SitFinanceCharge)
12
+ #
13
+ # See SDK docs for SpecialItemAdd for more details.
14
+ def add_special(sess, item_type)
15
+ rq = QBFC::Request.new(sess, "SpecialItemAdd")
16
+ rq.special_item_type = item_type
17
+
18
+ # Insofar as I never actually plan to use this method, just return
19
+ # response.
20
+ return rq.response
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ # Require subclass files
27
+ Dir.new(File.dirname(__FILE__) + '/items').each do |file|
28
+ require('qbfc/items/' + File.basename(file)) if File.extname(file) == ".rb"
29
+ end
@@ -0,0 +1,11 @@
1
+ module QBFC
2
+
3
+ # Generated Item Types (Inherit from List)
4
+ ITEM_TYPES = %w{ItemService ItemNonInventory ItemOtherCharge ItemInventory ItemInventoryAssembly ItemFixedAsset
5
+ ItemSubtotal ItemDiscount ItemPayment ItemSalesTax ItemSalesTaxGroup ItemGroup}
6
+
7
+ # Generate Item subclasses
8
+ # NB: All Items are Modifiable
9
+ generate(ITEM_TYPES, Item, {Modifiable => ITEM_TYPES})
10
+
11
+ end
@@ -0,0 +1,84 @@
1
+ module QBFC
2
+ # List objects are those with names, such as Accounts, Entities, and Items.
3
+ #
4
+ # Note on the name: it doesn't make sense since a List is actually a single object,
5
+ # but it fits with the SDK's naming scheme, and I couldn't think of a better one.
6
+ class List < Element
7
+ is_base_class
8
+ ID_NAME = "ListID"
9
+
10
+ class << self
11
+
12
+ # Find by name (actually, FullName) of List record.
13
+ # +options+ are the same as those for in +find+.
14
+ def find_by_name(sess, full_name, options = {})
15
+ q = create_query(sess)
16
+ q.query.FullNameList.Add(full_name)
17
+ find(sess, :first, q, options)
18
+ end
19
+
20
+ alias_method :find_by_full_name, :find_by_name
21
+
22
+ # Find by ListID of List record.
23
+ # +options+ are the same as those for in +find+.
24
+ def find_by_id(sess, id, options = {})
25
+ q = create_query(sess)
26
+ q.query.ListIDList.Add(id)
27
+ find(sess, :first, q, options)
28
+ end
29
+
30
+ # Find by either name or id. Tries id first, then name.
31
+ def find_by_name_or_id(*args)
32
+ find_by_id(*args) || find_by_name(*args)
33
+ end
34
+
35
+ alias_method :find_by_unique_id, :find_by_name_or_id
36
+
37
+ end
38
+
39
+ # Alias of ListID for this record. This is a unique within each type of List.
40
+ def id
41
+ @ole.list_id
42
+ end
43
+
44
+ # If an entity has a Name field but not a FullName field,
45
+ # use Name (which, by implication, is the FullName)
46
+ def full_name
47
+ respond_to_ole?("FullName") ?
48
+ @ole.full_name :
49
+ @ole.name
50
+ end
51
+
52
+ # Delete this List record.
53
+ def delete
54
+ req = QBFC::Request.new(@sess, "ListDel")
55
+ req.list_del_type = QBFC_CONST::const_get("Ldt#{qb_name}")
56
+ req.list_id = id
57
+ req.submit
58
+ return true
59
+ end
60
+
61
+ # Display the Transaction add (for new records) or edit dialog box
62
+ def display
63
+ if new_record?
64
+ req = QBFC::Request.new(@sess, "ListDisplayAdd")
65
+ req.list_display_add_type = QBFC_CONST::const_get("Ldat#{qb_name}")
66
+ else
67
+ req = QBFC::Request.new(@sess, "ListDisplayMod")
68
+ req.list_display_mod_type = QBFC_CONST::const_get("Ldmt#{qb_name}")
69
+ req.list_id = id
70
+ end
71
+ req.submit
72
+ return true
73
+ end
74
+ end
75
+ end
76
+
77
+ # Require subclass files
78
+ %w{ entity item terms }.each do |file|
79
+ require 'qbfc/' + file
80
+ end
81
+
82
+ Dir.new(File.dirname(__FILE__) + '/lists').each do |file|
83
+ require('qbfc/lists/' + File.basename(file)) if File.extname(file) == ".rb"
84
+ end
@@ -0,0 +1,24 @@
1
+ module QBFC
2
+ class Account < List
3
+ include Modifiable
4
+
5
+ class << self
6
+
7
+ # Adds a SpecialAccount, per SDK documentation:
8
+ # "An account normally created automatically as needed within the
9
+ # QuickBooks UI, or in the SDK via the SpecialAccountAdd request."
10
+ #
11
+ # +account_type+ should be a constent, for example:
12
+ #
13
+ # Account.add_special(sess, QBFC_CONST::SatAccountsPayable)
14
+ #
15
+ # See SDK docs for SpecialAccountAdd for more details.
16
+ def add_special(sess, account_type)
17
+ rq = QBFC::Request.new(sess, "SpecialAccountAdd")
18
+ rq.special_account_type = account_type
19
+
20
+ new(sess, rq.response)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ module QBFC
2
+
3
+ # Generated List types that will inherit directly from QBFC::List
4
+ LIST_TYPES = %w{ BillingRate CustomerMsg CustomerType JobType PaymentMethod
5
+ PayrollItemNonWage PayrollItemWage PriceLevel SalesRep SalesTaxCode
6
+ ShipMethod ToDo Vehicle VendorType}
7
+
8
+ # Generated List types that do accept Mod Requests
9
+ # (most direct subclasses of List do not)
10
+ LIST_MOD_TYPES = %w{ PriceLevel SalesRep Vehicle }
11
+
12
+ # Generate List subclasses
13
+ generate(LIST_TYPES, List, {Modifiable => LIST_MOD_TYPES})
14
+
15
+ end
@@ -0,0 +1,25 @@
1
+ module QBFC
2
+ # QBClass objects represent QuickBooks SDK's <tt>Class</tt> objects.
3
+ # As naming this Class <tt>Class</tt> would be impractical, it is
4
+ # instead called QBClass. It is otherwise similar to the other List
5
+ # classes.
6
+ #
7
+ # From QBFC6 SDK docs:
8
+ #
9
+ # Classes can be used to separate transactions into meaningful categories.
10
+ # (For example, transactions could be classified according to department,
11
+ # business location, or type of work.) In QuickBooks, class tracking is
12
+ # off by default.
13
+ class QBClass < List
14
+ class << self
15
+
16
+ # The QuickBooks SDK class is called 'Class'.
17
+ # Calling this class QBClass avoids making ruby
18
+ # very angry; the qb_name method ensures that calls
19
+ # to QBFC use the correct name.
20
+ def qb_name
21
+ "Class"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,31 @@
1
+ module QBFC
2
+ module Modifiable
3
+
4
+ # Extend initialize of including classes to set up a Mod Request
5
+ # if the record is existing.
6
+ def initialize(*args)
7
+ super
8
+
9
+ unless @new_record
10
+ setup_mod_request
11
+ end
12
+ end
13
+
14
+ # Setup a Mod Request for this object and attach it
15
+ # to the ole object.
16
+ def setup_mod_request
17
+ @setter = QBFC::Request.new(@sess, "#{self.qb_name}Mod")
18
+
19
+ if self.kind_of?(List)
20
+ @setter.list_id = id
21
+ else # Transaction
22
+ @setter.txn_id = id
23
+ end
24
+
25
+ @setter.edit_sequence = @ole.edit_sequence
26
+ @ole.setter = @setter.ole_object
27
+ end
28
+
29
+ private :setup_mod_request
30
+ end
31
+ end
@@ -0,0 +1,201 @@
1
+ module QBFC
2
+
3
+ # OLEWrapper is more or less the centerpiece of RubyQBFC. (Nearly) every
4
+ # WIN32OLE object accessed within the library is wrapped in this class, which
5
+ # is responsible for allowing Ruby-esque methods in place of the OLE methods.
6
+ #
7
+ # customer.full_name # => Customer.FullName.GetValue
8
+ # customer.full_name=(val) # => Customer.FullName.SetValue(val)
9
+ #
10
+ # It also creates referenced objects when accessed.
11
+ #
12
+ # check.payee # => Entity.find_by_list_id(check.PayeeEntityRef.ListID.GetValue)
13
+ # check.account # => Account.find_by_list_id(check.AccountRef.ListID.GetValue)
14
+ #
15
+ # When an OLE method called via OLEWrapper returns a WIN32OLE object, a new
16
+ # OLEWrapper object is created with the WIN32OLE object and returned.
17
+ #
18
+ # Now, the fun (and +really+ hackish) part. In many cases within the QBFC
19
+ # library, the wrapper is actually wrapping two WIN32OLE objects, the additional
20
+ # being a 'setter' object. This object is used when creating a ModRequest. In
21
+ # such cases, a method ending in '=' is always sent to both the primary and the
22
+ # setter objects. To facilitate this, traversing child ole_objects also
23
+ # traverses the child setter objects.
24
+ class OLEWrapper
25
+ attr_reader :ole_object
26
+ attr_accessor :setter
27
+
28
+ # Set up wrapped object, by passing a WIN32OLE object
29
+ # (or a String with the name of a WIN32OLE server)
30
+ # Optionally, pass a +setter+ WIN32OLE object.
31
+ def initialize(ole_object, setter = nil)
32
+ ole_object = WIN32OLE.new(ole_object) if ole_object.kind_of?(String)
33
+ @ole_object = ole_object
34
+ @setter = setter
35
+ end
36
+
37
+ # Return Array of ole_methods for request WIN32OLE object.
38
+ def ole_methods
39
+ @ole_object.ole_methods
40
+ end
41
+
42
+ # Does this OLE object respond to the given ole method?
43
+ def respond_to_ole?(symbol)
44
+ detect_ole_method?(@ole_object, symbol)
45
+ end
46
+
47
+ # Use [idx] syntax for objects that respond to <tt>GetAt(idx)</tt>
48
+ def [](idx)
49
+ if idx.kind_of? Integer
50
+ self.class.new(@ole_object.GetAt(idx))
51
+ else
52
+ @ole_object[idx]
53
+ end
54
+ end
55
+
56
+ # Called by #method_missing of other classes. Initiates the OLEWrapper#method_missing
57
+ # method which is responsible for the various method conversions.
58
+ # +sess+ argument is a QBFC::Session.
59
+ def qbfc_method_missing(sess, symbol, *params)
60
+ @sess = sess
61
+ method_missing(symbol, *params)
62
+ end
63
+
64
+ # If the method name is capitalized, send directly to ole_object; if
65
+ # a WIN32OLE is returned, wrap it.
66
+ # If the method name starts with a lower-case letter, send to +lower_method_missing+
67
+ # for conversion.
68
+ def method_missing(symbol, *params) #:nodoc:
69
+ if (('a'..'z') === symbol.to_s[0].chr)
70
+ lower_case_method_missing(symbol, *params)
71
+ else
72
+ resp = @ole_object.send(symbol, *params)
73
+ return( resp.kind_of?(WIN32OLE) ?
74
+ self.class.new(resp) :
75
+ resp )
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ # Decide which conversion method needs to handle this method and send.
82
+ def lower_case_method_missing(symbol, *params)
83
+ if '=' == symbol.to_s[-1].chr
84
+ set_value(symbol.to_s[0..-2], *params)
85
+ elsif symbol.to_s =~ /\A(\w+)_(full_name|id)\Z/ && ref_name($1)
86
+ get_ref_name_or_id(ref_name($1), $2)
87
+ elsif detect_ole_method?(@ole_object, ole_sym(symbol))
88
+ get_value(symbol, *params)
89
+ elsif detect_ole_method?(@ole_object, (s = symbol.to_s.singularize.camelize + "RetList"))
90
+ setup_array(s)
91
+ elsif detect_ole_method?(@ole_object, (s = "OR" + symbol.to_s.singularize.camelize + "RetList"))
92
+ setup_array(s, true)
93
+ elsif detect_ole_method?(@ole_object, (s = symbol.to_s.singularize.camelize + "List"))
94
+ setup_array(s)
95
+ elsif detect_ole_method?(@ole_object, (s = "OR" + symbol.to_s.singularize.camelize + "List"))
96
+ setup_array(s, true)
97
+ elsif ref_name(symbol)
98
+ create_ref(ref_name(symbol), *params)
99
+ elsif detect_ole_method?(@ole_object, symbol)
100
+ # Occassionally, QBFC uses a lower case method
101
+ @ole_object.send(symbol, *params)
102
+ else
103
+ raise NoMethodError, symbol.to_s
104
+ end
105
+ end
106
+
107
+ # Sets a value by calling OLEMethodName.SetValue(*params)
108
+ def set_value(ole_method_name, *params)
109
+ ole_method_name = ole_sym(ole_method_name)
110
+ obj = @ole_object.send(ole_method_name)
111
+
112
+ if detect_ole_method?(obj, "SetValue")
113
+ params[0] = params[0].strftime("%Y-%m-%d") if params[0].kind_of?(Date)
114
+ obj.SetValue(*params)
115
+ if @setter && detect_ole_method?(@setter, ole_method_name)
116
+ @setter.send(ole_method_name).SetValue(*params)
117
+ end
118
+ else
119
+ raise SetValueMissing, "SetValue is expected, but missing, for #{ole_method_name}"
120
+ end
121
+ end
122
+
123
+ # Gets a value by calling OLEMethodName.GetValue
124
+ def get_value(ole_method_name, *params)
125
+ ole_method_name = ole_sym(ole_method_name)
126
+ obj = @ole_object.send(ole_method_name, *params)
127
+ if detect_ole_method?(obj, "GetValue")
128
+ if ole_method_name =~ /date/i || ole_method_name.to_s =~ /time/i
129
+ Time.parse(obj.GetValue)
130
+ else
131
+ obj.GetValue
132
+ end
133
+ else
134
+ if obj.kind_of?(WIN32OLE)
135
+ if @setter && detect_ole_method?(@setter, ole_method_name)
136
+ self.class.new(obj, @setter.send(ole_method_name, *params))
137
+ else
138
+ self.class.new(obj)
139
+ end
140
+ else
141
+ obj
142
+ end
143
+ end
144
+ end
145
+
146
+ # Sets up an array to return if the return of OLEMethodName appears
147
+ # to be a list structure.
148
+ # <tt>is_OR_list</tt> indicates the list is an OR*RetList which
149
+ # is structured differently.
150
+ def setup_array(ole_method_name, is_OR_list = false)
151
+ list = @ole_object.send(ole_method_name)
152
+ ary = []
153
+ 0.upto(list.Count - 1) do |i|
154
+ if is_OR_list
155
+ ary << self.class.new(list.GetAt(i)).send(ole_method_name.match(/\AOR(.*)List\Z/)[1])
156
+ else
157
+ ary << self.class.new(list.GetAt(i))
158
+ end
159
+ end
160
+ return ary
161
+ end
162
+
163
+ def ref_name(symbol)
164
+ if detect_ole_method?(@ole_object, symbol.to_s.camelize + "Ref")
165
+ symbol.to_s.camelize + "Ref"
166
+ elsif detect_ole_method?(@ole_object, symbol.to_s.camelize + "EntityRef")
167
+ symbol.to_s.camelize + "EntityRef"
168
+ else
169
+ nil
170
+ end
171
+ end
172
+
173
+ # Creates a QBFC::Base inherited object if the return of
174
+ # OLEMethodName appears to be a reference to such an object.
175
+ def create_ref(ref_ole_name, *options)
176
+ ref_ole_object = @ole_object.send(ref_ole_name)
177
+ if ref_ole_object
178
+ ref_ole_name =~ /EntityRef/ ?
179
+ QBFC::Entity.find_by_id(@sess, ref_ole_object.ListID.GetValue(), *options) :
180
+ QBFC::const_get(ref_ole_name.gsub(/Ref/,"")).find_by_id(@sess, ref_ole_object.ListID.GetValue(), *options)
181
+ else
182
+ return nil
183
+ end
184
+ end
185
+
186
+ def get_ref_name_or_id(symbol, field)
187
+ field = (field == "id" ? "ListID" : "FullName")
188
+ @ole_object.send(symbol).send(field).GetValue()
189
+ end
190
+
191
+ # Check if the obj has an ole_method matching the symbol.
192
+ def detect_ole_method?(obj, symbol)
193
+ obj && obj.respond_to?(:ole_methods) && obj.ole_methods.detect{|m| m.to_s == symbol.to_s}
194
+ end
195
+
196
+ # Helper method to convert 'Ruby-ish' method name to WIN32OLE method name
197
+ def ole_sym(symbol)
198
+ symbol.to_s.camelize.gsub(/Id/, 'ID')
199
+ end
200
+ end
201
+ end