quickbooks 0.4.0 → 0.9.9

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 (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