quickbooks 0.4.0 → 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (293) hide show
  1. data.tar.gz.sig +0 -0
  2. data/History.txt +23 -0
  3. data/License.txt +54 -0
  4. data/Manifest.txt +73 -0
  5. data/README.txt +79 -0
  6. data/Rakefile +17 -78
  7. data/lib/quickbooks.rb +153 -7
  8. data/lib/quickbooks/adaptability.rb +67 -0
  9. data/lib/quickbooks/adapters/http_adapter.rb +94 -0
  10. data/lib/quickbooks/adapters/https_adapter.rb +57 -0
  11. data/lib/quickbooks/adapters/ole_adapter.rb +141 -110
  12. data/lib/quickbooks/adapters/spew_adapter.rb +39 -0
  13. data/lib/quickbooks/adapters/tcp_adapter.rb +71 -0
  14. data/lib/quickbooks/adapters/test_adapter.rb +62 -0
  15. data/lib/quickbooks/element.rb +385 -0
  16. data/lib/quickbooks/element_collection.rb +105 -0
  17. data/lib/quickbooks/extlib.rb +8 -0
  18. data/lib/quickbooks/extlib/assertions.rb +8 -0
  19. data/lib/quickbooks/extlib/class.rb +98 -0
  20. data/lib/quickbooks/extlib/days_and_times.rb +10 -0
  21. data/lib/quickbooks/extlib/days_and_times/duration.rb +362 -0
  22. data/lib/quickbooks/extlib/days_and_times/numeric.rb +48 -0
  23. data/lib/quickbooks/extlib/days_and_times/object.rb +19 -0
  24. data/lib/quickbooks/extlib/days_and_times/time.rb +137 -0
  25. data/lib/quickbooks/extlib/hash.rb +327 -0
  26. data/lib/quickbooks/extlib/hook.rb +366 -0
  27. data/lib/quickbooks/extlib/inflection.rb +436 -0
  28. data/lib/quickbooks/extlib/logger.rb +202 -0
  29. data/lib/quickbooks/extlib/object.rb +162 -0
  30. data/lib/quickbooks/extlib/pathname.rb +15 -0
  31. data/lib/quickbooks/extlib/rubygems.rb +38 -0
  32. data/lib/quickbooks/extlib/string.rb +32 -0
  33. data/lib/quickbooks/extlib/time.rb +41 -0
  34. data/lib/quickbooks/model.rb +503 -121
  35. data/lib/quickbooks/option.rb +95 -0
  36. data/lib/quickbooks/property.rb +69 -0
  37. data/lib/quickbooks/ruby_ext.rb +251 -0
  38. data/lib/quickbooks/type.rb +75 -0
  39. data/lib/quickbooks/types.rb +58 -0
  40. data/lib/quickbooks/version.rb +10 -0
  41. data/lib/quickbooks/xsd.rb +243 -0
  42. data/lib/quickbooks/xsd/attribute.rb +57 -0
  43. data/lib/quickbooks/xsd/choice.rb +94 -0
  44. data/lib/quickbooks/xsd/complex_type.rb +77 -0
  45. data/lib/quickbooks/xsd/element.rb +214 -0
  46. data/lib/quickbooks/xsd/group.rb +94 -0
  47. data/lib/quickbooks/xsd/restriction.rb +51 -0
  48. data/lib/quickbooks/xsd/sequence.rb +85 -0
  49. data/lib/quickbooks/xsd/simple_type.rb +87 -0
  50. data/lib/quickbooks/xsd/union.rb +70 -0
  51. data/lib/quickbooks/xsd/validation.rb +89 -0
  52. data/lib/quickbooks/xsd/xml_parse.rb +80 -0
  53. data/quickbooks.gemspec +36 -0
  54. data/source/qbxml21_demo.xsd +2223 -0
  55. data/source/qbxmlops_demo.xsd +483 -0
  56. data/source/qbxmltypes.xsd +160 -0
  57. data/spec/association_spec.rb +5 -0
  58. data/spec/element_collection_spec.rb +8 -0
  59. data/spec/find_spec.rb +30 -0
  60. data/spec/lib/quickbooks/element_spec.rb +83 -0
  61. data/spec/lib/quickbooks/elements/sales_order_add_spec.rb +14 -0
  62. data/spec/lib/quickbooks/elements/vendor_add_spec.rb +78 -0
  63. data/spec/lib/quickbooks/model_spec.rb +89 -0
  64. data/spec/lib/quickbooks/models/customer_spec.rb +52 -0
  65. data/spec/lib/quickbooks/models/invoice_spec.rb +12 -0
  66. data/spec/lib/quickbooks/models/sales_order_line_spec.rb +0 -0
  67. data/spec/lib/quickbooks/models/sales_order_spec.rb +145 -0
  68. data/spec/lib/quickbooks/xsd/choice_spec.rb +18 -0
  69. data/spec/pending_spec.rb +11 -0
  70. data/spec/quickbooks_spec.rb +38 -0
  71. data/spec/repeatable_spec.rb +20 -0
  72. data/spec/requires_spec.rb +10 -0
  73. data/spec/spec_helper.rb +5 -0
  74. data/spec/validation_spec.rb +24 -0
  75. metadata +133 -285
  76. metadata.gz.sig +0 -0
  77. data/LICENSE +0 -16
  78. data/README +0 -49
  79. data/doc/classes/Array.html +0 -368
  80. data/doc/classes/Array.src/M000026.html +0 -18
  81. data/doc/classes/Array.src/M000027.html +0 -21
  82. data/doc/classes/Array.src/M000028.html +0 -18
  83. data/doc/classes/Array.src/M000029.html +0 -19
  84. data/doc/classes/Array.src/M000030.html +0 -18
  85. data/doc/classes/Array.src/M000031.html +0 -23
  86. data/doc/classes/Array.src/M000032.html +0 -18
  87. data/doc/classes/Array.src/M000033.html +0 -18
  88. data/doc/classes/Array.src/M000034.html +0 -18
  89. data/doc/classes/Array.src/M000035.html +0 -19
  90. data/doc/classes/Array.src/M000036.html +0 -18
  91. data/doc/classes/Array.src/M000037.html +0 -23
  92. data/doc/classes/Array.src/M000038.html +0 -18
  93. data/doc/classes/Array.src/M000039.html +0 -21
  94. data/doc/classes/Array.src/M000040.html +0 -18
  95. data/doc/classes/Array.src/M000041.html +0 -18
  96. data/doc/classes/Class.html +0 -171
  97. data/doc/classes/Class.src/M000042.html +0 -18
  98. data/doc/classes/Class.src/M000043.html +0 -18
  99. data/doc/classes/Hash.html +0 -516
  100. data/doc/classes/Hash.src/M000001.html +0 -18
  101. data/doc/classes/Hash.src/M000002.html +0 -21
  102. data/doc/classes/Hash.src/M000003.html +0 -18
  103. data/doc/classes/Hash.src/M000004.html +0 -21
  104. data/doc/classes/Hash.src/M000005.html +0 -18
  105. data/doc/classes/Hash.src/M000006.html +0 -20
  106. data/doc/classes/Hash.src/M000007.html +0 -18
  107. data/doc/classes/Hash.src/M000008.html +0 -22
  108. data/doc/classes/Hash.src/M000009.html +0 -18
  109. data/doc/classes/Hash.src/M000010.html +0 -21
  110. data/doc/classes/Hash.src/M000011.html +0 -24
  111. data/doc/classes/Hash.src/M000012.html +0 -18
  112. data/doc/classes/Hash.src/M000013.html +0 -21
  113. data/doc/classes/Hash.src/M000014.html +0 -18
  114. data/doc/classes/Hash.src/M000015.html +0 -21
  115. data/doc/classes/Hash.src/M000016.html +0 -18
  116. data/doc/classes/Hash.src/M000017.html +0 -21
  117. data/doc/classes/Hash.src/M000018.html +0 -27
  118. data/doc/classes/Hash.src/M000019.html +0 -20
  119. data/doc/classes/Hash.src/M000020.html +0 -18
  120. data/doc/classes/Hash.src/M000021.html +0 -18
  121. data/doc/classes/Hash.src/M000022.html +0 -18
  122. data/doc/classes/Hash.src/M000023.html +0 -18
  123. data/doc/classes/Hash.src/M000024.html +0 -18
  124. data/doc/classes/Hash.src/M000025.html +0 -28
  125. data/doc/classes/Object.html +0 -278
  126. data/doc/classes/Object.src/M000044.html +0 -24
  127. data/doc/classes/Object.src/M000045.html +0 -19
  128. data/doc/classes/Object.src/M000046.html +0 -24
  129. data/doc/classes/Object.src/M000047.html +0 -18
  130. data/doc/classes/Object.src/M000048.html +0 -19
  131. data/doc/classes/Object.src/M000049.html +0 -18
  132. data/doc/classes/Object.src/M000050.html +0 -18
  133. data/doc/classes/Object.src/M000051.html +0 -18
  134. data/doc/classes/Qbxml.html +0 -131
  135. data/doc/classes/Qbxml/Request.html +0 -224
  136. data/doc/classes/Qbxml/Request.src/M000132.html +0 -66
  137. data/doc/classes/Qbxml/Request.src/M000133.html +0 -19
  138. data/doc/classes/Qbxml/Request.src/M000134.html +0 -83
  139. data/doc/classes/Qbxml/RequestSet.html +0 -199
  140. data/doc/classes/Qbxml/RequestSet.src/M000135.html +0 -22
  141. data/doc/classes/Qbxml/RequestSet.src/M000136.html +0 -26
  142. data/doc/classes/Qbxml/RequestSet.src/M000137.html +0 -18
  143. data/doc/classes/Qbxml/RequestSet.src/M000138.html +0 -22
  144. data/doc/classes/Qbxml/Response.html +0 -302
  145. data/doc/classes/Qbxml/Response.src/M000123.html +0 -24
  146. data/doc/classes/Qbxml/Response.src/M000124.html +0 -19
  147. data/doc/classes/Qbxml/Response.src/M000125.html +0 -55
  148. data/doc/classes/Qbxml/Response.src/M000126.html +0 -18
  149. data/doc/classes/Qbxml/Response.src/M000127.html +0 -18
  150. data/doc/classes/Qbxml/Response.src/M000128.html +0 -18
  151. data/doc/classes/Qbxml/Response.src/M000129.html +0 -18
  152. data/doc/classes/Qbxml/Response.src/M000130.html +0 -18
  153. data/doc/classes/Qbxml/Response.src/M000131.html +0 -63
  154. data/doc/classes/Qbxml/ResponseSet.html +0 -244
  155. data/doc/classes/Qbxml/ResponseSet.src/M000116.html +0 -22
  156. data/doc/classes/Qbxml/ResponseSet.src/M000117.html +0 -26
  157. data/doc/classes/Qbxml/ResponseSet.src/M000118.html +0 -24
  158. data/doc/classes/Qbxml/ResponseSet.src/M000119.html +0 -20
  159. data/doc/classes/Qbxml/ResponseSet.src/M000120.html +0 -27
  160. data/doc/classes/Qbxml/ResponseSet.src/M000121.html +0 -18
  161. data/doc/classes/Qbxml/ResponseSet.src/M000122.html +0 -18
  162. data/doc/classes/Quickbooks.html +0 -196
  163. data/doc/classes/Quickbooks/Address.html +0 -139
  164. data/doc/classes/Quickbooks/Address.src/M000115.html +0 -19
  165. data/doc/classes/Quickbooks/Base.html +0 -567
  166. data/doc/classes/Quickbooks/Base.src/M000095.html +0 -18
  167. data/doc/classes/Quickbooks/Base.src/M000096.html +0 -20
  168. data/doc/classes/Quickbooks/Base.src/M000097.html +0 -19
  169. data/doc/classes/Quickbooks/Base.src/M000098.html +0 -18
  170. data/doc/classes/Quickbooks/Base.src/M000099.html +0 -19
  171. data/doc/classes/Quickbooks/Base.src/M000100.html +0 -30
  172. data/doc/classes/Quickbooks/Base.src/M000101.html +0 -22
  173. data/doc/classes/Quickbooks/Base.src/M000102.html +0 -30
  174. data/doc/classes/Quickbooks/Base.src/M000103.html +0 -19
  175. data/doc/classes/Quickbooks/Base.src/M000104.html +0 -19
  176. data/doc/classes/Quickbooks/Base.src/M000105.html +0 -18
  177. data/doc/classes/Quickbooks/Base.src/M000106.html +0 -18
  178. data/doc/classes/Quickbooks/Base.src/M000107.html +0 -19
  179. data/doc/classes/Quickbooks/Base.src/M000108.html +0 -18
  180. data/doc/classes/Quickbooks/Base.src/M000109.html +0 -45
  181. data/doc/classes/Quickbooks/Base.src/M000110.html +0 -18
  182. data/doc/classes/Quickbooks/Base.src/M000111.html +0 -18
  183. data/doc/classes/Quickbooks/Base.src/M000112.html +0 -20
  184. data/doc/classes/Quickbooks/BillAddress.html +0 -113
  185. data/doc/classes/Quickbooks/CreditCardInfo.html +0 -113
  186. data/doc/classes/Quickbooks/Customer.html +0 -113
  187. data/doc/classes/Quickbooks/CustomerTypeRef.html +0 -113
  188. data/doc/classes/Quickbooks/Deleted.html +0 -113
  189. data/doc/classes/Quickbooks/ItemSalesTaxRef.html +0 -113
  190. data/doc/classes/Quickbooks/JobTypeRef.html +0 -113
  191. data/doc/classes/Quickbooks/ListDeleted.html +0 -139
  192. data/doc/classes/Quickbooks/ListDeleted.src/M000114.html +0 -18
  193. data/doc/classes/Quickbooks/ListItem.html +0 -170
  194. data/doc/classes/Quickbooks/ListItem.src/M000093.html +0 -20
  195. data/doc/classes/Quickbooks/ListItem.src/M000094.html +0 -18
  196. data/doc/classes/Quickbooks/Model.html +0 -462
  197. data/doc/classes/Quickbooks/Model.src/M000073.html +0 -24
  198. data/doc/classes/Quickbooks/Model.src/M000074.html +0 -19
  199. data/doc/classes/Quickbooks/Model.src/M000075.html +0 -18
  200. data/doc/classes/Quickbooks/Model.src/M000076.html +0 -18
  201. data/doc/classes/Quickbooks/Model.src/M000077.html +0 -18
  202. data/doc/classes/Quickbooks/Model.src/M000078.html +0 -22
  203. data/doc/classes/Quickbooks/Model.src/M000079.html +0 -32
  204. data/doc/classes/Quickbooks/Model.src/M000080.html +0 -32
  205. data/doc/classes/Quickbooks/Model.src/M000081.html +0 -18
  206. data/doc/classes/Quickbooks/Model.src/M000082.html +0 -18
  207. data/doc/classes/Quickbooks/Model.src/M000083.html +0 -22
  208. data/doc/classes/Quickbooks/Model.src/M000084.html +0 -23
  209. data/doc/classes/Quickbooks/Model.src/M000085.html +0 -18
  210. data/doc/classes/Quickbooks/Model.src/M000086.html +0 -21
  211. data/doc/classes/Quickbooks/Model.src/M000087.html +0 -26
  212. data/doc/classes/Quickbooks/Model.src/M000088.html +0 -26
  213. data/doc/classes/Quickbooks/Model.src/M000089.html +0 -26
  214. data/doc/classes/Quickbooks/Model.src/M000090.html +0 -21
  215. data/doc/classes/Quickbooks/Model.src/M000091.html +0 -23
  216. data/doc/classes/Quickbooks/OleAdapter.html +0 -129
  217. data/doc/classes/Quickbooks/OleAdapter/Connection.html +0 -343
  218. data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000058.html +0 -18
  219. data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000059.html +0 -19
  220. data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000060.html +0 -24
  221. data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000061.html +0 -18
  222. data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000062.html +0 -18
  223. data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000063.html +0 -21
  224. data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000064.html +0 -24
  225. data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000065.html +0 -20
  226. data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000066.html +0 -24
  227. data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000067.html +0 -24
  228. data/doc/classes/Quickbooks/OleAdapter/Ole.html +0 -209
  229. data/doc/classes/Quickbooks/OleAdapter/Ole.src/M000068.html +0 -21
  230. data/doc/classes/Quickbooks/OleAdapter/Ole.src/M000069.html +0 -22
  231. data/doc/classes/Quickbooks/OleAdapter/Ole.src/M000070.html +0 -18
  232. data/doc/classes/Quickbooks/ParentRef.html +0 -113
  233. data/doc/classes/Quickbooks/PreferredPaymentMethodRef.html +0 -113
  234. data/doc/classes/Quickbooks/PriceLevelRef.html +0 -113
  235. data/doc/classes/Quickbooks/QbxmlDebugAdapter.html +0 -111
  236. data/doc/classes/Quickbooks/QbxmlDebugAdapter/Connection.html +0 -139
  237. data/doc/classes/Quickbooks/QbxmlDebugAdapter/Connection.src/M000057.html +0 -18
  238. data/doc/classes/Quickbooks/Ref.html +0 -146
  239. data/doc/classes/Quickbooks/Ref.src/M000113.html +0 -18
  240. data/doc/classes/Quickbooks/SalesRepRef.html +0 -113
  241. data/doc/classes/Quickbooks/SalesTaxCodeRef.html +0 -113
  242. data/doc/classes/Quickbooks/ShipAddress.html +0 -113
  243. data/doc/classes/Quickbooks/TermsRef.html +0 -113
  244. data/doc/classes/Quickbooks/Transaction.html +0 -154
  245. data/doc/classes/Quickbooks/Transaction.src/M000071.html +0 -19
  246. data/doc/classes/Quickbooks/Transaction.src/M000072.html +0 -18
  247. data/doc/classes/Quickbooks/TxnDeleted.html +0 -139
  248. data/doc/classes/Quickbooks/TxnDeleted.src/M000092.html +0 -18
  249. data/doc/classes/String.html +0 -203
  250. data/doc/classes/String.src/M000052.html +0 -18
  251. data/doc/classes/String.src/M000053.html +0 -18
  252. data/doc/classes/String.src/M000054.html +0 -18
  253. data/doc/classes/String.src/M000055.html +0 -18
  254. data/doc/classes/String.src/M000056.html +0 -18
  255. data/doc/created.rid +0 -1
  256. data/doc/files/LICENSE.html +0 -129
  257. data/doc/files/README.html +0 -225
  258. data/doc/files/lib/qbxml/request_rb.html +0 -110
  259. data/doc/files/lib/qbxml/response_rb.html +0 -109
  260. data/doc/files/lib/qbxml/support_rb.html +0 -101
  261. data/doc/files/lib/qbxml_rb.html +0 -122
  262. data/doc/files/lib/quickbooks/adapters/ole_adapter_rb.html +0 -108
  263. data/doc/files/lib/quickbooks/adapters/qbxml_debug_adapter_rb.html +0 -108
  264. data/doc/files/lib/quickbooks/base_rb.html +0 -327
  265. data/doc/files/lib/quickbooks/model_rb.html +0 -108
  266. data/doc/files/lib/quickbooks/models/common/address_rb.html +0 -101
  267. data/doc/files/lib/quickbooks/models/common/all_refs_rb.html +0 -101
  268. data/doc/files/lib/quickbooks/models/customer/credit_card_info_rb.html +0 -101
  269. data/doc/files/lib/quickbooks/models/customer_rb.html +0 -110
  270. data/doc/files/lib/quickbooks/models/deleted_rb.html +0 -101
  271. data/doc/files/lib/quickbooks/models/list_item_rb.html +0 -101
  272. data/doc/files/lib/quickbooks/models/transaction_rb.html +0 -101
  273. data/doc/files/lib/quickbooks/ruby_magic_rb.html +0 -120
  274. data/doc/files/lib/quickbooks_rb.html +0 -125
  275. data/doc/fr_class_index.html +0 -64
  276. data/doc/fr_file_index.html +0 -45
  277. data/doc/fr_method_index.html +0 -164
  278. data/doc/index.html +0 -24
  279. data/doc/rdoc-style.css +0 -208
  280. data/lib/qbxml.rb +0 -3
  281. data/lib/qbxml/request.rb +0 -207
  282. data/lib/qbxml/response.rb +0 -197
  283. data/lib/qbxml/support.rb +0 -130
  284. data/lib/quickbooks/adapters/qbxml_debug_adapter.rb +0 -10
  285. data/lib/quickbooks/base.rb +0 -277
  286. data/lib/quickbooks/models/common/address.rb +0 -14
  287. data/lib/quickbooks/models/common/all_refs.rb +0 -37
  288. data/lib/quickbooks/models/customer.rb +0 -70
  289. data/lib/quickbooks/models/customer/credit_card_info.rb +0 -5
  290. data/lib/quickbooks/models/deleted.rb +0 -22
  291. data/lib/quickbooks/models/list_item.rb +0 -41
  292. data/lib/quickbooks/models/transaction.rb +0 -49
  293. data/lib/quickbooks/ruby_magic.rb +0 -213
@@ -0,0 +1,41 @@
1
+ require "date"
2
+
3
+ class Time
4
+
5
+ ##
6
+ # Convert to ISO 8601 representation
7
+ #
8
+ # Time.now.to_json #=> "\"2008-03-28T17:54:20-05:00\""
9
+ #
10
+ # @return [String]
11
+ # ISO 8601 compatible representation of the Time object.
12
+ #
13
+ # @api public
14
+ def to_json(*)
15
+ self.xmlschema.to_json
16
+ end
17
+
18
+ ##
19
+ # Return receiver (for DateTime/Time conversion protocol).
20
+ #
21
+ # Time.now.to_time #=> Wed Nov 19 20:08:28 -0800 2008
22
+ #
23
+ # @return [Time] Receiver
24
+ #
25
+ # @api public
26
+ def to_time
27
+ self
28
+ end
29
+
30
+ ##
31
+ # Convert to DateTime (for DateTime/Time conversion protocol).
32
+ #
33
+ # Time.now.to_datetime #=> #<DateTime: 106046956823/43200,-1/3,2299161>
34
+ #
35
+ # @return [DateTime] DateTime object representing the same moment as receiver
36
+ #
37
+ # @api public
38
+ def to_datetime
39
+ DateTime.parse self.to_s
40
+ end
41
+ end
@@ -1,178 +1,560 @@
1
- require 'quickbooks/ruby_magic'
2
-
1
+ require 'quickbooks/element'
3
2
  module Quickbooks
4
- # These were all created from the info in qbxmlops70.xml, found in the docs in the QBSDK package.
5
- CAMELIZE_EXCEPTIONS = {'list_id' => 'ListID', 'txn_id' => 'TxnID', 'owner_id' => 'OwnerID'}
6
-
3
+ # Model steps above related Element classes give you a way to manage your Quickbooks data like _objects_ rather than by managing the _communication_ of those objects.
7
4
  class Model
5
+ # A ModelProperty is just a little data store that keeps track of characteristics of each of the model's properties.
6
+ class ModelProperty
7
+ def initialize(klass_name, addable=false, writable=false, readable=false) #:nodoc:
8
+ @klass_name = klass_name
9
+ @addable = !!addable
10
+ @writable = !!writable
11
+ @readable = !!readable
12
+ end
13
+
14
+ # Get the class name of the property.
15
+ def klass_name
16
+ @klass_name
17
+ end
18
+
19
+ # Get the class constant for the property.
20
+ def klass
21
+ QB[@klass_name]
22
+ end
23
+
24
+ # Can I include this property when creating a new object?
25
+ def addable?
26
+ @addable
27
+ end
28
+ def addable! #:nodoc:
29
+ @addable = true
30
+ end
31
+
32
+ # Can I include this property when modifying an existing object?
33
+ def writable?
34
+ @writable
35
+ end
36
+ def writable! #:nodoc:
37
+ @writable = true
38
+ end
39
+
40
+ # Is this property returned when reading back from QuickBooks?
41
+ def readable?
42
+ @readable
43
+ end
44
+ def readable! #:nodoc:
45
+ @readable = true
46
+ end
47
+ end
48
+
8
49
  class << self
50
+ attr_reader :properties
9
51
 
10
- def inherited(klass)
11
- def klass.valid_filters
12
- (superclass.valid_filters + (@valid_filters ||= []))
52
+ def help
53
+ "QB::#{short_name}.query_xsd for available query params\nQB::#{short_name}.write_xsd for create properties\nQB::#{short_name}.read_write_xsd for modifiable properties\nTry one of the above options to study this particular model's properties."
54
+ end
55
+
56
+ # Set or retrieve the XSD data for the Model's read elements -- properties that are readable.
57
+ def read_xsd(xsd=nil)
58
+ raise ArgumentError, "must be an Quickbooks::XSD::Element" unless xsd.nil? || xsd.is_a?(Quickbooks::XSD::Element)
59
+ if xsd
60
+ @read_xsd = xsd
61
+ create_combined_elements!
13
62
  end
14
- def klass.filter_aliases
15
- superclass.filter_aliases.slashed.merge(@filter_aliases ||= {}.slashed)
63
+ @read_xsd ||= nil
64
+ end
65
+
66
+ # Set or retrieve the XSD data for the Model's read/write elements -- properties that can be used in modifying objects.
67
+ # This is also the controller for validation of the object when you're modifying an existing object.
68
+ def read_write_xsd(xsd=nil)
69
+ raise ArgumentError, "must be an Quickbooks::XSD::Element" unless xsd.nil? || xsd.is_a?(Quickbooks::XSD::Element)
70
+ if xsd
71
+ @read_write_xsd = xsd
72
+ create_combined_elements!
16
73
  end
17
- klass.instance_variable_set('@object_properties', {})
74
+ @read_write_xsd ||= nil
18
75
  end
19
-
20
- def valid_filters=(v)
21
- raise TypeError, "must be only strings" unless v.all? {|e| e.is_a?(String)}
22
- @valid_filters = v
76
+
77
+ # Set or retrieve the XSD data for the Model's write elements -- properties that can be used in creating a new object.
78
+ # This is also the controller for validation of the object when you're creating a new object.
79
+ def write_xsd(xsd=nil)
80
+ raise ArgumentError, "must be an Quickbooks::XSD::Element" if !xsd.nil? && !xsd.is_a?(Quickbooks::XSD::Element)
81
+ if xsd
82
+ @write_xsd = xsd
83
+ create_combined_elements!
84
+ end
85
+ @write_xsd ||= nil
23
86
  end
24
- def valid_filters
25
- (@valid_filters ||= [])
87
+
88
+ # Set or retrieve the XSD data for the Model's write elements -- properties that can be used in creating a new object.
89
+ # This is also the controller for validation of the object when you're creating a new object.
90
+ def query_xsd(xsd=nil)
91
+ raise ArgumentError, "must be an Quickbooks::XSD::Element" if !xsd.nil? && !xsd.is_a?(Quickbooks::XSD::Element)
92
+ @query_xsd = xsd if xsd
93
+ @query_xsd ||= nil
26
94
  end
27
- def filter_aliases=(v)
28
- (@filter_aliases = v.empty? ? {} : v).slashed
95
+
96
+ def query_attributes
97
+ if query_xsd
98
+ query_xsd.attributes
99
+ else
100
+ raise RuntimeError, "You must first initialize query_xsd."
101
+ end
29
102
  end
30
- def filter_aliases
31
- @filter_aliases ||= {}
103
+
104
+ def create_combined_elements!
105
+ # If this runs more than once it's okay; but it shouldn't ever because the attributes are written in order so write_xsd are done last.
106
+ if read_xsd
107
+ @properties = {}
108
+ @associations = {}
109
+ [read_xsd, read_write_xsd, write_xsd].compact.inject([]) {|a,e| a.concat(e.children)}.each do |prop_xsd|
110
+ @properties[prop_xsd.name.to_sym] = ModelProperty.new(Quickbooks.get_constant(prop_xsd.name).short_name, write_xsd && write_xsd.include?(prop_xsd.name), read_write_xsd && read_write_xsd.include?(prop_xsd.name), read_xsd && read_xsd.include?(prop_xsd.name))
111
+ @associations[prop_xsd.name[0..-4].to_sym] = @properties[prop_xsd.name.to_sym] if prop_xsd.name =~ /Ref$/
112
+ end
113
+ end
32
114
  end
33
- def camelized_valid_filters
34
- cvf = []
35
- valid_filters.each do |v|
36
- cvf << (Quickbooks::CAMELIZE_EXCEPTIONS.has_key?(v) ? Quickbooks::CAMELIZE_EXCEPTIONS[v] : (v.is_a?(Symbol) ? v.to_s.camelize.to_sym : v.split('/').map {|e| e.camelize}.join('/')))
115
+ private :create_combined_elements!
116
+
117
+ # Emulated Associations
118
+ attr_reader :associations # set up in .create_combined_elements!
119
+ # * * * *
120
+
121
+ # Request an array of whatever QuickBooks objects match the query. The args presented should be attributes with which to construct
122
+ # a *QueryRq for whatever model you're in.
123
+ def all(args={})
124
+ process_finder_args(args)
125
+ # This includes DataExt if user has asked for it.
126
+ if args.has_key?(:IncludeExtData)
127
+ data_scope = args.delete(:IncludeExtData)
128
+ args[:OwnerID] = [data_scope == true ? '0' : data_scope] if data_scope
129
+ end
130
+ # Create a #{short_name}Query out of the filters, and send it to quickbooks for a response; instantiate the response objects.
131
+ query = Quickbooks.get_constant("#{short_name}QueryRq").new(args)
132
+ if caller.join =~ /lib\/quickbooks\.rb:\d+:in `qbxml'/
133
+ Quickbooks.requestify(query).to_xml
134
+ else
135
+ catch :response do
136
+ response = Quickbooks.execute(query)[:QBXMLMsgsRs]["#{short_name}QueryRs"][0]
137
+ (response.nil? || response["#{short_name}Ret"].nil? || response["#{short_name}Ret"].empty?) ?
138
+ [] : response["#{short_name}Ret"].collect {|r| r.to_model}
139
+ end
37
140
  end
38
- cvf
39
141
  end
40
-
41
- # Register multiple read/writable properties at once. For example:
42
- # read_write :first_name, :last_name, :phone, :alt_phone
43
- # For reference attributes (like parent_ref), use ParentRef - a class constant for that object.
44
- # _read_write_ will set the property setter and accessor accordingly.
45
- def read_write(*args)
46
- if args.empty?
47
- @read_write || (@read_write = [])
142
+ # Request a single QuickBooks object that matches the query. The args presented should be attributes with which to construct
143
+ # a *QueryRq for whatever model you're in. This simply adds the property :MaxReturned => 1, if the request is still valid with it.
144
+ def first(args={})
145
+ process_finder_args(args)
146
+ # Create a #{short_name}Query out of the filters, and send it to quickbooks for a response; instantiate the response objects.
147
+ query = QB["#{short_name}QueryRq"].new(args)
148
+ started_valid = query.valid?
149
+ query[:MaxReturned] = 1
150
+ query.delete(:MaxReturned) if started_valid && !query.valid?
151
+ if caller.join =~ /lib\/quickbooks\.rb:\d+:in `qbxml'/
152
+ Quickbooks.requestify(query).to_xml
48
153
  else
49
- args.each do |prop|
50
- if prop.is_a?(Symbol)
51
- read_write << prop
52
- attr_accessor prop
53
- else
54
- @object_properties[prop.class_leaf_name.underscore.to_sym] = prop
55
- read_write << prop.class_leaf_name.underscore.to_sym
56
- class_eval "def #{prop.class_leaf_name.underscore}=(v); @#{prop.class_leaf_name.underscore} = #{prop.name}.new(v); end
57
- def #{prop.class_leaf_name.underscore}; @#{prop.class_leaf_name.underscore}; end"
58
- end
154
+ catch :response do
155
+ response = Quickbooks.execute(query)[:QBXMLMsgsRs]["#{short_name}QueryRs"][0]
156
+ response.nil? || response["#{short_name}Ret"].nil? || response["#{short_name}Ret"][0].nil? ?
157
+ nil : response["#{short_name}Ret"][0].to_model
59
158
  end
60
159
  end
61
160
  end
62
161
 
63
- # Read-only attributes: These are attributes, but not modifiable in Quickbooks
64
- def read_only(*args)
65
- if args.empty?
66
- @read_only || (@read_only = [])
162
+ # Iterates through all records matching *args, but only grabs 70 at a time from QuickBooks.
163
+ def each(args={})
164
+ process_finder_args(args)
165
+ query = Quickbooks.get_constant("#{short_name}QueryRq").new(args)
166
+ started_valid = query.valid?
167
+ query[:iterator] = 'Start'
168
+ query[:MaxReturned] = 70
169
+ query.delete(:iterator, :MaxReturned) if started_valid && !query.valid?
170
+ if caller.join =~ /lib\/quickbooks\.rb:\d+:in `qbxml'/
171
+ Quickbooks.requestify(query).to_xml
67
172
  else
68
- args.each do |prop|
69
- if prop.is_a?(Symbol)
70
- read_only << prop
71
- attr_accessor prop
72
- else
73
- @object_properties[prop.class_leaf_name.underscore.to_sym] = prop
74
- read_only << prop.class_leaf_name.underscore.to_sym
75
- class_eval "def #{prop.class_leaf_name.underscore}=(v); @#{prop.class_leaf_name.underscore} = #{prop.name}.new(v); end
76
- def #{prop.class_leaf_name.underscore}; @#{prop.class_leaf_name.underscore}; end"
173
+ catch :response do
174
+ result = Quickbooks.execute(query)
175
+ ary = result[:QBXMLMsgsRs]["#{short_name}QueryRs"][0]["#{short_name}Ret"].each {|e| yield e.to_model}
176
+ if iteratorID = result[:QBXMLMsgsRs]["#{short_name}QueryRs"][0][:iteratorID]
177
+ query[:iteratorID] = iteratorID
178
+ query[:iterator] = 'Continue'
179
+ until(result[:QBXMLMsgsRs]["#{short_name}QueryRs"][0][:iteratorRemainingCount].to_i == 0)
180
+ result = Quickbooks.execute(query)
181
+ ary.concat result[:QBXMLMsgsRs]["#{short_name}QueryRs"][0]["#{short_name}Ret"].each {|e| yield e.to_model}
182
+ end
77
183
  end
184
+ ary
78
185
  end
79
186
  end
80
187
  end
81
-
82
- def properties
83
- read_only + read_write
188
+
189
+ # Should be used only internally, but if you need this - use it to paint a new QuickBooks model object that thinks it already exists in QuickBooks.
190
+ def instantiate(attrs={})
191
+ obj = allocate
192
+ obj.instance_variable_set(:@new_record, nil)
193
+ obj.attributes = attrs
194
+ obj.instance_variable_set(:@new_record, false)
195
+ obj.send :clean!
196
+ obj
84
197
  end
85
- end
86
198
 
87
- self.valid_filters = ['max_returned']
199
+ # Replies whether it's a :List or a :Txn item.
200
+ def item_type
201
+ @properties.has_key?(:ListID) ? :List : :Txn
202
+ end
88
203
 
89
- # The default for all subclasses is simply to apply the attributes given, and mark the object as a new_record?
90
- def initialize(args={})
91
- self.attributes = args
204
+ private
205
+ def process_finder_args(args)
206
+ # This includes DataExt if user has asked for it.
207
+ if args.has_key?(:IncludeExtData)
208
+ data_scope = args.delete(:IncludeExtData)
209
+ args[:OwnerID] = [data_scope == true ? '0' : data_scope] if data_scope
210
+ end
211
+ end
92
212
  end
93
-
94
- # Returns a hash that represents all this object's attributes.
95
- def attributes(include_read_only=false)
96
- attrs = {}
97
- (include_read_only ? self.class.read_only + self.class.read_write : self.class.read_write).each do |column|
98
- attrs[column.to_s] = instance_variable_get('@' + column.to_s)
213
+
214
+ # Paints up a new QuickBooks model object with the attributes you give it. Marked as new, so when you hit save, it will actually create it.
215
+ def initialize(attrs={})
216
+ @new_record = :init
217
+ self.attributes = attrs
218
+ @new_record = true
219
+ end
220
+
221
+ def inspect #:nodoc:
222
+ "<#{self.class.short_name}:##{object_id}\n #{attributes.collect {|k,v| v.inspect}.join("\n").gsub(/\n/, "\n ")}>"
223
+ end
224
+
225
+ # true if new record, false if already exists.
226
+ #
227
+ # For deeper inspection, @new_record is set to:
228
+ # * :init if being initialized
229
+ # * true if new_record
230
+ # * nil if being instantiated
231
+ # * false if not new_record
232
+ def new_record?
233
+ !!@new_record
234
+ end
235
+
236
+ # Returns the ListID or TxnID value respective of its type.
237
+ def id
238
+ self[:"#{self.class.item_type}ID"]
239
+ end
240
+
241
+ # Display a list of the available attribute names
242
+ def properties(key=nil)
243
+ if key
244
+ self.class.properties[key]
245
+ else
246
+ self.class.properties.keys
99
247
  end
100
- attrs
101
248
  end
102
- # Updates all attributes included in _attrs_ to the values given. The object will now be dirty?.
249
+
250
+ # Just return the attributes hash.
251
+ def attributes
252
+ @attributes ||= {}
253
+ end
254
+
255
+ # Pass a hash of attributes, and it'll set each one one by one via []=.
103
256
  def attributes=(attrs)
104
- raise ArgumentError, "attributes can only be set to a hash of attributes" unless attrs.is_a?(Hash)
105
- attrs.each do |key,value|
106
- if self.respond_to?(key.to_s.underscore+'=')
107
- self.send(key.to_s.underscore+'=', value)
257
+ raise ArgumentError, "must be a hash" unless attrs.is_a?(Hash)
258
+ attrs.each do |k,v|
259
+ if self.class.properties.has_key?(k.to_sym) || self.class.associations.has_key?(k.to_sym)
260
+ self[k.to_sym] = v
261
+ else
262
+ raise "Model #{self.class.short_name} does not have property #{k}!"
108
263
  end
109
264
  end
110
265
  end
266
+
267
+ # Access attribute values by key. Also access associations this way.
268
+ def [](key)
269
+ return self.attributes = key if key.is_a?(Hash)
270
+ key = case key # make a string
271
+ when String
272
+ key.to_sym
273
+ when Symbol
274
+ key
275
+ when Module
276
+ key.short_name.to_sym
277
+ end
278
+ return get_associated(key) if self.class.associations.has_key?(key)
279
+ attr_klass = begin
280
+ QB[key.to_s]
281
+ rescue RuntimeError
282
+ raise "Invalid key '#{key.inspect}'"
283
+ end
284
+ repeatable = (new_record? ? self.class.write_xsd : (@new_record == false ? self.class.read_write_xsd : self.class.read_xsd)).repeatable?(key.to_s)
285
+ attributes[key] = ElementCollection.new(self, key) if repeatable && !attributes[key].is_a?(ElementCollection)
286
+ attributes[key]
287
+ end
288
+
289
+ # Set attribute values by key. Also set associated objects here.
290
+ def []=(key,value)
291
+ key = case key # make a symbol
292
+ when String
293
+ key.to_sym
294
+ when Symbol
295
+ key
296
+ when Module
297
+ key.short_name.to_sym
298
+ end
299
+ return associate(key, value) if self.class.associations.include?(key)
300
+ raise RuntimeError, "'#{key}' is not a valid property name for #{self.class.name}!" unless self.class.properties.has_key?(key)
301
+ raise RuntimeError, "'#{key}' cannot be assigned on creation" if new_record? && !self.class.properties[key].addable?
302
+ raise RuntimeError, "'#{key}' cannot be modified" if @new_record == false && !self.class.properties[key].writable?
303
+ # Instantiate the value into the attribute class, if necessary
304
+ attr_klass = QB[key.to_s]
305
+ # If it *should* be an ElementCollection, it shouldn't be set here at all!
306
+ if (new_record? ? self.class.write_xsd : (@new_record == false ? self.class.read_write_xsd : self.class.read_xsd)).repeatable?(key.to_s)
307
+ if @new_record.in?(nil, :init) || value.is_a?(Array)
308
+ attributes[key] = ElementCollection.new(self, key, value)
309
+ attributes[key].send(:dirty!)
310
+ else
311
+ # If it *should* be an array element, it shouldn't be set here as a single value. This is just for safeguard, so that syntax
312
+ # always shows what is going on. For an array element, set it with: object.some_attr = [value]
313
+ raise RuntimeError, "You can't set a single value into a multi-value element to using equals(=). Use \"model[:#{key}] << value\" to append, or wrap the value in an array -- \"model[:#{key}] = [ value ]\" -- if you want to completely replace the current value set."
314
+ end
315
+ else
316
+ value = @new_record.nil? ? attr_klass.instantiate(value) : attr_klass.new(value) unless value.is_a?(attr_klass)
317
+ value.send(:dirty!)
318
+ attributes[key] = value
319
+ end
320
+ remove_incorrect_associated(key.to_s.sub(/Ref$/,'').to_sym, value) if self.class.associations.include?(key.to_s.sub(/Ref$/,'').to_sym)
321
+ end
322
+
323
+ # Completely remove an attribute value from the object. This is different from setting the attribute to nil.
324
+ def delete(*keys)
325
+ keys.each do |key|
326
+ key = case key # make a string
327
+ when String
328
+ key.to_sym
329
+ when Symbol
330
+ key
331
+ when Module
332
+ key.short_name.to_sym
333
+ end
334
+ attributes.delete(key)
335
+ end
336
+ end
337
+
338
+ # Return only the attributes that have been changed or whose descendents have changed. If include_required is true, then whatever is necessary to make the set of attributes valid is returned as well.
339
+ def dirty_attributes(include_required=false)
340
+ Hash[*((include_required ?
341
+ attributes.select {|k,v| v.dirty? || (new_record? ? self.class.write_xsd : self.class.read_write_xsd).required?(k) || !(new_record? ? self.class.write_xsd : self.class.read_write_xsd).validate(self.class.short_name => attributes.except(v.class.short_name))} :
342
+ attributes.select {|k,v| v.dirty?}).flatten)]
343
+ end
111
344
 
112
- # Keeps track of the original values the object had when it was instantiated from a quickbooks response. dirty? and dirty_attributes compare the current values with these ones.
113
- def original_values
114
- @original_values || (@original_values = {})
345
+ # Return only the attributes that have not been modified.
346
+ def clean_attributes
347
+ attributes.except(dirty_attributes.keys)
115
348
  end
116
349
 
117
- # Returns true if any attributes have changed since the object was last loaded or updated from Quickbooks.
350
+ # Have any of my values, or my descendents' values, been changed?
118
351
  def dirty?
119
- # Concept: For each column that the current model includes, has the value been changed?
120
- self.class.read_write.any? do |column|
121
- self.instance_variable_get('@' + column.to_s) != original_values[column.to_s]
122
- end
352
+ # Test for any dirty elements
353
+ @dirty || attributes.any? {|k,v| v.dirty?}
123
354
  end
124
355
 
125
- # Returns a hash of the attributes and their (new) values that have been changed since the object was last loaded or updated from Quickbooks.
126
- # If you send in some attributes, it will compare to those given instead of original_attributes.
127
- def dirty_attributes(compare={})
128
- compare = original_values if compare.empty?
129
- pairs = {}
130
- self.class.read_write.each do |column|
131
- value = instance_variable_get('@' + column.to_s)
132
- if value != compare[column.to_s]
133
- pairs[column.to_s] = value
134
- end
135
- end
136
- pairs
356
+ # Do I have a relative *Ref class?
357
+ def can_ref?
358
+ Quickbooks.get_constant(self.class.short_name.gsub(/(Add|Mod|Ret)/,'') + 'Ref') && true rescue false
137
359
  end
138
360
 
139
- def to_dirty_hash
140
- hsh = SlashedHash.new.ordered!(self.class.read_write.stringify_values)
141
- self.dirty_attributes.each do |key,value|
142
- if value.is_a?(Quickbooks::Model)
143
- hsh[key] = value.to_dirty_hash
361
+ # Make a Ref object out of me.
362
+ def to_ref
363
+ begin
364
+ attrs = {}
365
+ idsym = :"#{self.class.item_type}ID"
366
+ if self[idsym]
367
+ attrs[idsym] = self[idsym]
368
+ elsif self[:FullName]
369
+ attrs[:FullName] = self[:FullName]
144
370
  else
145
- hsh[key] = value
371
+ return nil # Not able to ref
146
372
  end
373
+ Quickbooks.get_constant(self.class.short_name.gsub(/(Add|Mod|Ret)/,'') + 'Ref').new(attrs)
374
+ rescue NameError => e
375
+ nil
147
376
  end
148
- hsh
149
377
  end
150
378
 
151
- def to_hash(include_read_only=false)
152
- hsh = SlashedHash.new.ordered!((include_read_only ? self.class.read_only + self.class.read_write : self.class.read_write).stringify_values)
153
- self.attributes(include_read_only).each do |key,value|
154
- if value.is_a?(Quickbooks::Model)
155
- hsh[key] = value.to_hash(include_read_only)
156
- else
157
- hsh[key] = value
379
+ # Turn me into an element (ending in *Add or *Mod, depending on my new_record? situation).
380
+ def to_element
381
+ # List clean attributes
382
+ clean_attrs = clean_attributes.collect {|k,v| v.respond_to?(:to_element) ? v.to_element : v}.to_hash_via {|e| [(e.is_a?(ElementCollection) ? e.type.short_name : e.class.short_name), e]}
383
+ # List dirty attributes
384
+ dirty_attrs = dirty_attributes.collect {|k,v| v.respond_to?(:to_element) ? v.to_element : v}.to_hash_via {|e| [(e.is_a?(ElementCollection) ? e.type.short_name : e.class.short_name), e]}
385
+ # Set up the corresponding element with corresponding properties
386
+ element = element_klass.instantiate(clean_attrs)
387
+ element.attributes = dirty_attrs
388
+ # Transfer the order index (for attribute sorting) from elements that were in a collection.
389
+ element.instance_variable_set(:@collection_index, @collection_index) if instance_variables.include?('@collection_index')
390
+ element
391
+ end
392
+
393
+ # I don't think this is actually being used anywhere...
394
+ def to_update_element
395
+ to_element.to_update_element
396
+ end
397
+
398
+ def to_xml
399
+ to_element.to_xml
400
+ end
401
+ def to_dirty_xml(include_required=false)
402
+ to_element.to_dirty_xml(include_required)
403
+ end
404
+
405
+ # Am I completely valid?
406
+ def valid?
407
+ validate.perfect?
408
+ end
409
+
410
+ # Use this to validate the object and get the Valean result instead of just a true/false value.
411
+ def validate
412
+ r = new_record? ? self.class.write_xsd.validate(self) : self.class.read_write_xsd.validate(self)
413
+ errors.replace(r.errors)
414
+ r
415
+ end
416
+
417
+ # The errors returned by the latest validate call.
418
+ def errors
419
+ @errors ||= []
420
+ end
421
+ def add_error(msg) #:nodoc:
422
+ errors << [nil, msg]
423
+ end
424
+
425
+ # Save the object to QuickBooks, whether that means creating a new record or updating an existing one.
426
+ def save
427
+ save_associations
428
+ query = Quickbooks.get_constant("#{element_klass.short_name}Rq").new([ to_element.to_update_element ])
429
+ if caller.join =~ /lib\/quickbooks\.rb:\d+:in `qbxml'/
430
+ Quickbooks.requestify(query).to_xml
431
+ else
432
+ catch :response do
433
+ @last_response = Quickbooks.execute(query)
434
+ response = @last_response[:QBXMLMsgsRs]["#{element_klass.short_name}Rs"][0]
435
+ reload = if response.nil? || response["#{self.class.short_name}Ret"].nil? || response["#{self.class.short_name}Ret"].nil?
436
+ raise "Response doesn't make sense after saving #{inspect}: #{@last_response.inspect} (#{@last_response[:QBXMLMsgsRs]["#{element_klass.short_name}Rs"][0].nil?})"
437
+ else
438
+ response["#{self.class.short_name}Ret"].to_model
439
+ end
440
+ @attributes = reload.attributes
441
+ true
158
442
  end
159
443
  end
160
- hsh
161
444
  end
162
445
 
163
- def ==(other)
164
- return false unless other.is_a?(self.class)
165
- !self.class.read_write.any? do |column|
166
- self.instance_variable_get('@' + column.to_s) != other.instance_variable_get('@' + column.to_s)
446
+ # Update some extended data by key and value.
447
+ # By default, just include the key and value, and the owner id of '0' will be used.
448
+ # However, if you want to specify a different owner, just add that on as a third parameter.
449
+ def update_extended_data(key, value, owner=nil)
450
+ # First, see if this key already exists or not.
451
+ load_extended_data unless attributes[:DataExt]
452
+
453
+ attrs = {
454
+ # Use the same OwnerID that was supplied for getting the DataExt.
455
+ :OwnerID => owner || ((attributes[:DataExt] && attributes[:DataExt][0]) ? attributes[:DataExt][0][:OwnerID] : '0'),
456
+ :DataExtName => key,
457
+ :"#{self.class.short_name == 'Company' ? 'Other' : self.class.item_type}DataExtType" => self.class.short_name,
458
+ :DataExtValue => value
459
+ }
460
+ case self.class.item_type
461
+ when :List
462
+ attrs[:ListObjRef] = {:ListID => id}
463
+ when :Txn
464
+ attrs[:TxnID] = id
465
+ # We don't need to bother with LineID extended data.
466
+ # attrs[:TxnLineID]
467
+ end
468
+
469
+ de = if self[:DataExt] && self[:DataExt].select {|de| de[:DataExtName] == key}
470
+ # Modify it.
471
+ QB::DataExtModRq.new(:DataExtMod => attrs)
472
+ else
473
+ # Create it.
474
+ QB::DataExtAddRq.new(:DataExtAdd => attrs)
167
475
  end
476
+ Quickbooks.execute(de)
477
+ load_extended_data
478
+ end
479
+ # Use this to load extended data if you need to import into this same object.
480
+ def load_extended_data(owner='0')
481
+ attributes[:DataExt] = self.class.first(:"#{self.class.item_type}ID" => [id], :OwnerID => [owner])[:DataExt]
482
+ end
483
+
484
+ # Save any unsaved or dirty associations.
485
+ def save_associations
486
+ # first save any of my associated items that aren't already existing
487
+ self.class.associations.each_key do |association_name|
488
+ instance_variable_set("@#{association_name}", nil) unless self.instance_variables.include?("@#{association_name}") # just to avoid needless warnings.
489
+ if instance_variable_get("@#{association_name}") && self[association_name].new_record?
490
+ self[association_name].save
491
+ self[association_name] = self[association_name] # re-assigns the associated Ref
492
+ end
493
+ end
494
+ # then save any of my children's associated items that aren't already existing
495
+ attributes.each { |k,attv| attv.save_associations if attv.respond_to?(:save_associations) }
168
496
  end
169
497
 
170
- def ===(other)
171
- # other could be a hash
172
- if other.is_a?(Hash)
173
- self == self.class.new(other)
498
+ # That's right. Just get rid of that object. QuickBooks knows how to handle things, and it won't let you destroy items if it'll mess things up.
499
+ def destroy!
500
+ query = Quickbooks.get_constant("#{self.class.item_type}DelRq").new("#{self.class.item_type}DelType" => self.class.short_name, "#{self.class.item_type}ID" => self["#{self.class.item_type}ID"])
501
+ if caller.join =~ /lib\/quickbooks\.rb:\d+:in `qbxml'/
502
+ Quickbooks.requestify(query).to_xml
503
+ else
504
+ catch :response do
505
+ Quickbooks.execute(query)
506
+ end
507
+ end
508
+ end
509
+
510
+ private
511
+
512
+ def get_associated(association_name)
513
+ (self.instance_variables.include?("@#{association_name}") && instance_variable_get("@#{association_name}")) || begin
514
+ ref_klass = self.class.associations[association_name].klass
515
+ self[ref_klass].nil? ? nil : instance_variable_set("@#{association_name}", self[ref_klass].unref)
516
+ end
517
+ end
518
+
519
+ def associate(association_name, value)
520
+ instance_variable_set("@#{association_name}", value)
521
+ ref = value.to_ref
522
+ ref_klass = self.class.associations[association_name].klass
523
+ !ref.nil? ?
524
+ self[ref_klass] = ref :
525
+ self.delete(ref_klass)
526
+ end
527
+
528
+ def remove_incorrect_associated(association_name, value)
529
+ instance_variable_set("@#{association_name}", nil) if !self.instance_variables.include?("@#{association_name}") || (associated = instance_variable_get("@#{association_name}") && !associated.nil? && !associated.to_ref.nil? && (associated.to_ref[:ListID] || associated.to_ref[:TxnID] || associated.to_ref[:FullName]) != (value[:ListID] || value[:TxnID] || value[:FullName]))
530
+ end
531
+
532
+ def element_klass
533
+ Quickbooks.get_constant(self.class.short_name + (new_record? ? 'Add' : 'Mod'))
534
+ end
535
+
536
+ def clean!
537
+ @dirty = false
538
+ attributes.each {|k,v| v.send(:clean!)}
539
+ end
540
+
541
+ def dirty!
542
+ @dirty = true
543
+ end
544
+
545
+ def method_missing(method_name, *args)
546
+ if method_name.to_s =~ /^[A-Z]/
547
+ if method_name.to_s =~ /=$/
548
+ # Set property
549
+ self[method_name.to_s.gsub(/\=$/,'')] = *args
550
+ elsif args.empty?
551
+ # Get property
552
+ self[method_name.to_s.gsub(/\=$/,'')]
553
+ else
554
+ raise NoMethodError, "undefined method `#{method_name}' for #{self.inspect}:#{self.class.name}", caller[0..-1]
555
+ end
174
556
  else
175
- self == other
557
+ raise NoMethodError, "undefined method `#{method_name}' for #{self.inspect}:#{self.class.name}", caller[0..-1]
176
558
  end
177
559
  end
178
560
  end