xeroizer-float 2.15.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. data/.bundle/config +2 -0
  2. data/.gitattributes +22 -0
  3. data/Gemfile +19 -0
  4. data/Gemfile.lock +57 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.md +529 -0
  7. data/Rakefile +55 -0
  8. data/VERSION +1 -0
  9. data/lib/big_decimal_to_s.rb +9 -0
  10. data/lib/class_level_inheritable_attributes.rb +25 -0
  11. data/lib/xeroizer.rb +64 -0
  12. data/lib/xeroizer/application_http_proxy.rb +30 -0
  13. data/lib/xeroizer/ca-certificates.crt +2642 -0
  14. data/lib/xeroizer/configuration.rb +19 -0
  15. data/lib/xeroizer/exceptions.rb +129 -0
  16. data/lib/xeroizer/generic_application.rb +54 -0
  17. data/lib/xeroizer/http.rb +167 -0
  18. data/lib/xeroizer/http_encoding_helper.rb +49 -0
  19. data/lib/xeroizer/logging.rb +8 -0
  20. data/lib/xeroizer/models/account.rb +67 -0
  21. data/lib/xeroizer/models/address.rb +32 -0
  22. data/lib/xeroizer/models/bank_account.rb +12 -0
  23. data/lib/xeroizer/models/bank_transaction.rb +75 -0
  24. data/lib/xeroizer/models/branding_theme.rb +22 -0
  25. data/lib/xeroizer/models/contact.rb +49 -0
  26. data/lib/xeroizer/models/contact_group.rb +16 -0
  27. data/lib/xeroizer/models/credit_note.rb +130 -0
  28. data/lib/xeroizer/models/currency.rb +18 -0
  29. data/lib/xeroizer/models/employee.rb +28 -0
  30. data/lib/xeroizer/models/external_link.rb +16 -0
  31. data/lib/xeroizer/models/invoice.rb +192 -0
  32. data/lib/xeroizer/models/item.rb +27 -0
  33. data/lib/xeroizer/models/item_purchase_details.rb +19 -0
  34. data/lib/xeroizer/models/item_sales_details.rb +19 -0
  35. data/lib/xeroizer/models/journal.rb +25 -0
  36. data/lib/xeroizer/models/journal_line.rb +28 -0
  37. data/lib/xeroizer/models/journal_line_tracking_category.rb +20 -0
  38. data/lib/xeroizer/models/line_amount_type.rb +11 -0
  39. data/lib/xeroizer/models/line_item.rb +40 -0
  40. data/lib/xeroizer/models/line_item_sum.rb +21 -0
  41. data/lib/xeroizer/models/manual_journal.rb +42 -0
  42. data/lib/xeroizer/models/manual_journal_line.rb +27 -0
  43. data/lib/xeroizer/models/option.rb +18 -0
  44. data/lib/xeroizer/models/organisation.rb +34 -0
  45. data/lib/xeroizer/models/payment.rb +42 -0
  46. data/lib/xeroizer/models/phone.rb +25 -0
  47. data/lib/xeroizer/models/tax_rate.rb +25 -0
  48. data/lib/xeroizer/models/tracking_category.rb +23 -0
  49. data/lib/xeroizer/models/tracking_category_child.rb +20 -0
  50. data/lib/xeroizer/oauth.rb +130 -0
  51. data/lib/xeroizer/partner_application.rb +46 -0
  52. data/lib/xeroizer/private_application.rb +27 -0
  53. data/lib/xeroizer/public_application.rb +21 -0
  54. data/lib/xeroizer/record/application_helper.rb +32 -0
  55. data/lib/xeroizer/record/base.rb +155 -0
  56. data/lib/xeroizer/record/base_model.rb +144 -0
  57. data/lib/xeroizer/record/base_model_http_proxy.rb +117 -0
  58. data/lib/xeroizer/record/model_definition_helper.rb +99 -0
  59. data/lib/xeroizer/record/record_association_helper.rb +140 -0
  60. data/lib/xeroizer/record/validation_helper.rb +71 -0
  61. data/lib/xeroizer/record/validators/associated_validator.rb +33 -0
  62. data/lib/xeroizer/record/validators/block_validator.rb +22 -0
  63. data/lib/xeroizer/record/validators/inclusion_of_validator.rb +22 -0
  64. data/lib/xeroizer/record/validators/presence_of_validator.rb +19 -0
  65. data/lib/xeroizer/record/validators/validator.rb +33 -0
  66. data/lib/xeroizer/record/xml_helper.rb +133 -0
  67. data/lib/xeroizer/report/aged_receivables_by_contact.rb +43 -0
  68. data/lib/xeroizer/report/base.rb +43 -0
  69. data/lib/xeroizer/report/cell.rb +32 -0
  70. data/lib/xeroizer/report/cell_xml_helper.rb +74 -0
  71. data/lib/xeroizer/report/factory.rb +53 -0
  72. data/lib/xeroizer/report/row/header.rb +12 -0
  73. data/lib/xeroizer/report/row/row.rb +48 -0
  74. data/lib/xeroizer/report/row/section.rb +10 -0
  75. data/lib/xeroizer/report/row/summary.rb +9 -0
  76. data/lib/xeroizer/report/row/xml_helper.rb +77 -0
  77. data/lib/xeroizer/report/xml_helper.rb +58 -0
  78. data/lib/xeroizer/response.rb +55 -0
  79. data/test/acceptance/about_creating_bank_transactions_test.rb +162 -0
  80. data/test/acceptance/about_fetching_bank_transactions_test.rb +56 -0
  81. data/test/acceptance/acceptance_test.rb +53 -0
  82. data/test/acceptance/bank_transaction_reference_data.rb +31 -0
  83. data/test/stub_responses/accounts.xml +589 -0
  84. data/test/stub_responses/api_exception.xml +153 -0
  85. data/test/stub_responses/bogus_oauth_error +1 -0
  86. data/test/stub_responses/branding_themes.xml +20 -0
  87. data/test/stub_responses/contact.xml +40 -0
  88. data/test/stub_responses/contacts.xml +0 -0
  89. data/test/stub_responses/create_credit_note.xml +104 -0
  90. data/test/stub_responses/create_invoice.xml +64 -0
  91. data/test/stub_responses/credit_note.xml +75 -0
  92. data/test/stub_responses/credit_note_not_found_error.xml +1 -0
  93. data/test/stub_responses/credit_notes.xml +169 -0
  94. data/test/stub_responses/currencies.xml +12 -0
  95. data/test/stub_responses/employees.xml +29 -0
  96. data/test/stub_responses/invalid_api_key_error.xml +1 -0
  97. data/test/stub_responses/invalid_consumer_key +1 -0
  98. data/test/stub_responses/invalid_request_token +1 -0
  99. data/test/stub_responses/invoice.xml +84 -0
  100. data/test/stub_responses/invoice_not_found_error.xml +1 -0
  101. data/test/stub_responses/invoices.xml +1899 -0
  102. data/test/stub_responses/items.xml +112 -0
  103. data/test/stub_responses/manual_journal.xml +45 -0
  104. data/test/stub_responses/manual_journals.xml +40 -0
  105. data/test/stub_responses/organisation.xml +25 -0
  106. data/test/stub_responses/organisations.xml +25 -0
  107. data/test/stub_responses/payments.xml +1121 -0
  108. data/test/stub_responses/rate_limit_exceeded +1 -0
  109. data/test/stub_responses/records/contact-043892a1-aef1-4c18-88d8-b8ccb6d31466.xml +38 -0
  110. data/test/stub_responses/records/contact-09664078-efe2-4a88-89a5-67eac9b0047b.xml +40 -0
  111. data/test/stub_responses/records/contact-0a4cf37b-a1a8-4753-9ee2-f9207f63a8ff.xml +48 -0
  112. data/test/stub_responses/records/contact-0e74f929-11b9-4255-a035-1fdfe573e676.xml +40 -0
  113. data/test/stub_responses/records/contact-0f471ca5-15c9-405e-a1b9-7cc35194b673.xml +38 -0
  114. data/test/stub_responses/records/contact-13cd4c47-baa6-4f07-93f6-6442310df4bf.xml +47 -0
  115. data/test/stub_responses/records/contact-158a2667-82ee-43bf-8f33-a6cc9524092d.xml +38 -0
  116. data/test/stub_responses/records/contact-17465072-6fa3-40bf-bc42-97765d9e1bea.xml +38 -0
  117. data/test/stub_responses/records/contact-1975b0ed-b7ba-4c61-bae8-2aa6d78b0dee.xml +39 -0
  118. data/test/stub_responses/records/contact-1b2be6e9-8d58-4da9-aaf8-4fe5471b653c.xml +53 -0
  119. data/test/stub_responses/records/contact-1c40da58-fe1d-4e97-b729-b2abdae94d9e.xml +38 -0
  120. data/test/stub_responses/records/contact-258176a5-c622-4394-9c94-6f88c3ea12e5.xml +40 -0
  121. data/test/stub_responses/records/contact-299dd3a0-a417-4a37-8a04-2f55e91963e5.xml +40 -0
  122. data/test/stub_responses/records/contact-2be39278-5154-4ed1-8eb0-676f25acfc66.xml +40 -0
  123. data/test/stub_responses/records/contact-2e58cff6-488c-4a32-884b-baf848010229.xml +40 -0
  124. data/test/stub_responses/records/contact-2faccd41-935e-40aa-b74e-e2fc28ac34c3.xml +38 -0
  125. data/test/stub_responses/records/contact-31af01e7-2ca7-45b9-a500-b02db996568e.xml +38 -0
  126. data/test/stub_responses/records/contact-344f1113-a25b-4344-b82e-bedeacc17c8e.xml +40 -0
  127. data/test/stub_responses/records/contact-3e776c4b-ea9e-4bb1-96be-6b0c7a71a37f.xml +39 -0
  128. data/test/stub_responses/records/contact-3fc1fc6c-e5ff-4e40-b6f3-7eb535637d87.xml +38 -0
  129. data/test/stub_responses/records/contact-416ab20c-5357-4beb-a740-e8d175d71efb.xml +38 -0
  130. data/test/stub_responses/records/contact-41a42865-f15a-4fa1-b643-47877608f557.xml +54 -0
  131. data/test/stub_responses/records/contact-42771b60-19a7-4692-af81-dd9f9b9362d4.xml +43 -0
  132. data/test/stub_responses/records/contact-451ceb28-9610-44c9-8f35-3225482f2413.xml +40 -0
  133. data/test/stub_responses/records/contact-4ab343ad-1ebb-4afe-9d48-1814a93c2081.xml +38 -0
  134. data/test/stub_responses/records/contact-4bb77692-42d4-4565-85a0-8849eb85e039.xml +39 -0
  135. data/test/stub_responses/records/contact-4dec292f-3ab7-46a8-83e4-5fb5eac42c7f.xml +40 -0
  136. data/test/stub_responses/records/contact-4e2f192e-8397-4d4d-97ca-a4fc5ac531bf.xml +38 -0
  137. data/test/stub_responses/records/contact-5188c17c-7786-4436-ad6e-9da2997386d0.xml +40 -0
  138. data/test/stub_responses/records/contact-52442753-b1c4-40b7-9b79-c33997de5837.xml +40 -0
  139. data/test/stub_responses/records/contact-565acaa9-e7f3-4fbf-80c3-16b081ddae10.xml +38 -0
  140. data/test/stub_responses/records/contact-571a2414-81ff-4f8f-8498-d91d83793131.xml +44 -0
  141. data/test/stub_responses/records/contact-58697449-85ef-46ae-83fc-6a9446f037fb.xml +40 -0
  142. data/test/stub_responses/records/contact-58bf2ae3-5144-4628-8de2-e165ac2bcdc6.xml +40 -0
  143. data/test/stub_responses/records/contact-5d41dafd-eb7e-42c1-bd5a-ba3be1da0960.xml +38 -0
  144. data/test/stub_responses/records/contact-5f005a09-5ce4-4fb4-8096-e69c18be636e.xml +38 -0
  145. data/test/stub_responses/records/contact-60d578d9-3e10-4aef-b5dc-9d9fd60a3633.xml +47 -0
  146. data/test/stub_responses/records/contact-62392126-dba4-4a75-b907-5875ebf75259.xml +40 -0
  147. data/test/stub_responses/records/contact-642c7fb5-e8e5-48e1-a710-39a18c6c3217.xml +40 -0
  148. data/test/stub_responses/records/contact-64aebf9c-bb89-4b38-b99b-405bd1ece6fd.xml +40 -0
  149. data/test/stub_responses/records/contact-64eedbc9-1fa0-485a-837f-705f23188161.xml +38 -0
  150. data/test/stub_responses/records/contact-65e96c9f-1595-4653-9a8a-2a36d49223c2.xml +40 -0
  151. data/test/stub_responses/records/contact-67d26b93-ccb4-4890-9bf1-284b70ea755d.xml +38 -0
  152. data/test/stub_responses/records/contact-69d3e538-44b3-4e00-a5f6-7dddcb6e0656.xml +40 -0
  153. data/test/stub_responses/records/contact-6a8450bc-f81a-4bb0-a8f6-aa4afe9497c7.xml +40 -0
  154. data/test/stub_responses/records/contact-6c70e424-41d6-4b9b-af3e-b3a9f3589106.xml +40 -0
  155. data/test/stub_responses/records/contact-6de0b0cf-560c-4503-aab3-e1543c329deb.xml +54 -0
  156. data/test/stub_responses/records/contact-72dd6a02-396e-42a2-a4d6-cc3fa75dfece.xml +41 -0
  157. data/test/stub_responses/records/contact-755f1475-d255-43a8-bedc-5ea7fd26c71f.xml +51 -0
  158. data/test/stub_responses/records/contact-78a9d0a0-3d8c-4f84-af3e-f260bf4a9dc0.xml +38 -0
  159. data/test/stub_responses/records/contact-79aa39ca-22b0-42c2-9026-78757a29d665.xml +42 -0
  160. data/test/stub_responses/records/contact-804f4140-5978-48fe-ba20-b56e5b834b18.xml +40 -0
  161. data/test/stub_responses/records/contact-812d4f28-1681-4241-8e34-d15c5520ba35.xml +38 -0
  162. data/test/stub_responses/records/contact-860b99a9-0958-4c8d-a98f-bb1f092b16bb.xml +60 -0
  163. data/test/stub_responses/records/contact-87c8da45-97cc-46be-b170-398da0eacfb8.xml +40 -0
  164. data/test/stub_responses/records/contact-8a154a19-6c6c-404b-bbc9-6deae2d18251.xml +38 -0
  165. data/test/stub_responses/records/contact-8bb6931d-2865-44e9-9a23-ed1fb9c7a46c.xml +40 -0
  166. data/test/stub_responses/records/contact-936c9759-01da-4063-b472-424ab9f48212.xml +38 -0
  167. data/test/stub_responses/records/contact-9d12a994-9640-4b75-95cc-3de1e9d0ef09.xml +38 -0
  168. data/test/stub_responses/records/contact-9fe59245-1fbb-4157-93c3-dc97388f3746.xml +40 -0
  169. data/test/stub_responses/records/contact-a06a7225-6f8a-4522-8400-c534dd43a16e.xml +40 -0
  170. data/test/stub_responses/records/contact-a76a85fe-73a2-46fa-aba7-791f36103cdb.xml +40 -0
  171. data/test/stub_responses/records/contact-a93b5f40-0346-4d21-9181-431e129911c0.xml +40 -0
  172. data/test/stub_responses/records/contact-abf272dd-6b1d-4829-af88-c57bf55855e3.xml +38 -0
  173. data/test/stub_responses/records/contact-ad24c33b-256b-4157-ad56-cbcf0e8db7b1.xml +47 -0
  174. data/test/stub_responses/records/contact-b107129d-f4c9-438e-9573-64b778527f4a.xml +40 -0
  175. data/test/stub_responses/records/contact-b233288a-aa26-4b26-9fc7-779d797dd56f.xml +40 -0
  176. data/test/stub_responses/records/contact-b2b5333a-2546-4975-891f-d71a8a640d23.xml +38 -0
  177. data/test/stub_responses/records/contact-b4d149bf-1823-4bd2-96da-9032388c9686.xml +40 -0
  178. data/test/stub_responses/records/contact-b78d4fd1-4306-4d83-a0b9-61458d1c53a2.xml +40 -0
  179. data/test/stub_responses/records/contact-b7d108a8-d5f7-4f16-a7c9-26eaed98e8de.xml +40 -0
  180. data/test/stub_responses/records/contact-baeed0f3-7989-4874-99b3-59f23032cb73.xml +38 -0
  181. data/test/stub_responses/records/contact-bc51a3a1-b7f6-46ca-ac9e-19b87e6ca100.xml +40 -0
  182. data/test/stub_responses/records/contact-be9f3aab-52f5-4d9c-94b4-87f7d9e5ee8b.xml +38 -0
  183. data/test/stub_responses/records/contact-c135f994-01e4-427b-9e15-acfe8a477c16.xml +49 -0
  184. data/test/stub_responses/records/contact-c14edf75-15e4-4a9c-86e4-f52e2fe7cfa4.xml +40 -0
  185. data/test/stub_responses/records/contact-ca9b9abc-c2dc-4221-8101-31f464d314cc.xml +44 -0
  186. data/test/stub_responses/records/contact-cc4db604-9ed8-4eef-8a29-51b5b70496a0.xml +38 -0
  187. data/test/stub_responses/records/contact-cce9b044-be4a-43b3-9dc7-c027d8dd35b2.xml +38 -0
  188. data/test/stub_responses/records/contact-d0cd2c4f-18a0-4f7c-a32a-2db00f29d298.xml +43 -0
  189. data/test/stub_responses/records/contact-d6851dc2-9ed9-4515-bc0b-810b09c06a6a.xml +38 -0
  190. data/test/stub_responses/records/contact-d6a384fb-f46f-41a3-8ac7-b7bc9e0b5efa.xml +46 -0
  191. data/test/stub_responses/records/contact-d74e61cf-2ad0-4f0d-b9d1-6a808e3f70cf.xml +40 -0
  192. data/test/stub_responses/records/contact-d9ab0f61-3b56-4e2b-be39-f33c11bd99e3.xml +40 -0
  193. data/test/stub_responses/records/contact-dbb1f0b5-a71b-4458-8462-104acd0fec6b.xml +38 -0
  194. data/test/stub_responses/records/contact-dd981bd6-40dd-496d-a282-bf7d3391b8b9.xml +40 -0
  195. data/test/stub_responses/records/contact-e1826204-cc0a-42a5-a6d0-4b352d9d5953.xml +40 -0
  196. data/test/stub_responses/records/contact-e2d955db-f366-42dd-87f7-fbdb4da2306f.xml +40 -0
  197. data/test/stub_responses/records/contact-e32e2130-3d27-443a-8313-48fffa03cf53.xml +40 -0
  198. data/test/stub_responses/records/contact-e3a68332-d322-4816-8678-73a537c8cd33.xml +38 -0
  199. data/test/stub_responses/records/contact-e6ac76a3-ca32-4fa1-8ef9-6a4bf8b0ec2a.xml +40 -0
  200. data/test/stub_responses/records/contact-e6ca965d-7c48-480e-be39-e847307f474a.xml +38 -0
  201. data/test/stub_responses/records/contact-e77d1f20-2e8e-46ec-9a10-50335a216724.xml +40 -0
  202. data/test/stub_responses/records/contact-e8b98c13-a424-41d2-ba0e-7b7621411e7a.xml +38 -0
  203. data/test/stub_responses/records/contact-e8e9a2c2-3e7e-48ed-8528-c3d61b28f276.xml +39 -0
  204. data/test/stub_responses/records/contact-eb43fcc6-87ec-4a0a-b243-d718bee4e2cb.xml +38 -0
  205. data/test/stub_responses/records/contact-ef6f54c1-eb45-4956-b8cd-1be82ad665f2.xml +43 -0
  206. data/test/stub_responses/records/contact-efdb3600-f233-42e2-8f18-ce7e2a95e4b1.xml +38 -0
  207. data/test/stub_responses/records/contact-f7eca431-5c97-4d24-93fd-004bb8a6c644.xml +40 -0
  208. data/test/stub_responses/records/contact-fb078879-5d6d-474f-825f-61dc90689349.xml +38 -0
  209. data/test/stub_responses/records/contact-fc39b273-4aa2-4785-99ca-24672f6c0000.xml +38 -0
  210. data/test/stub_responses/records/contact-fc9ec3a6-a2fe-4300-a8cb-ca8a0b3662e0.xml +40 -0
  211. data/test/stub_responses/records/contact-fdf96102-7491-44b6-bf4d-7a77ff25f890.xml +40 -0
  212. data/test/stub_responses/records/contact-fe61ead1-8afc-4f0b-beda-066620227aad.xml +38 -0
  213. data/test/stub_responses/records/credit_note-371cd138-1e5c-4ec1-a8c6-a1c10e8bdab1.xml +73 -0
  214. data/test/stub_responses/records/credit_note-3bffc09b-79f2-490d-b91b-c59b700b43a4.xml +95 -0
  215. data/test/stub_responses/records/credit_note-43c678ee-f357-48e2-b192-b6e3634762f9.xml +94 -0
  216. data/test/stub_responses/records/credit_note-482c018b-d329-4e05-9b4f-7a4cfc695aa0.xml +77 -0
  217. data/test/stub_responses/records/credit_note-4f67130a-749a-4ee6-98b2-743adbc11245.xml +62 -0
  218. data/test/stub_responses/records/credit_note-50e98404-2fba-4031-af67-8ba4bb227c44.xml +77 -0
  219. data/test/stub_responses/records/credit_note-7df8949c-b71f-40c0-bbcf-39f2f450f286.xml +75 -0
  220. data/test/stub_responses/records/credit_note-b356e488-2678-4be4-ad4b-d294df2d48d6.xml +78 -0
  221. data/test/stub_responses/records/invoice-0032f627-3156-4d30-9b1c-4d3b994dc921.xml +84 -0
  222. data/test/stub_responses/records/invoice-00c9511b-24b9-4190-a90a-8abf2fe9f4a0.xml +76 -0
  223. data/test/stub_responses/records/invoice-024d7994-a26c-4c20-9894-13934840fc31.xml +74 -0
  224. data/test/stub_responses/records/invoice-0e64a623-c2a1-446a-93ed-eb897f118cbc.xml +97 -0
  225. data/test/stub_responses/records/invoice-15e88e57-2554-4496-a18e-eb3f5c622345.xml +75 -0
  226. data/test/stub_responses/records/invoice-166f0588-d0ba-458c-b28a-8edd4c8fc463.xml +75 -0
  227. data/test/stub_responses/records/invoice-1d1ba340-afa2-4f4c-8ff7-a147bda9a47b.xml +93 -0
  228. data/test/stub_responses/records/invoice-290ef4c4-baec-492b-b4dd-c102826470ae.xml +87 -0
  229. data/test/stub_responses/records/invoice-30a87092-31b5-4a2c-831e-327486533dd2.xml +79 -0
  230. data/test/stub_responses/records/invoice-30dbd181-72a8-43df-b392-4241bf43d5fc.xml +79 -0
  231. data/test/stub_responses/records/invoice-33e4123e-7cdd-4f05-9a0a-eb8adeb2b868.xml +94 -0
  232. data/test/stub_responses/records/invoice-387db692-26ac-47e6-b6cc-015343809bda.xml +75 -0
  233. data/test/stub_responses/records/invoice-3b28bf11-ed2f-4cf4-8e9e-fcae730cc292.xml +91 -0
  234. data/test/stub_responses/records/invoice-3fcb9847-b350-412e-ab90-7d9d774ad881.xml +91 -0
  235. data/test/stub_responses/records/invoice-440613e4-4785-4eff-9ba1-c432816cf8c7.xml +78 -0
  236. data/test/stub_responses/records/invoice-4602eda6-abe9-448e-b65f-ae6bea21f0eb.xml +98 -0
  237. data/test/stub_responses/records/invoice-46441f63-873f-4cdc-a278-b8fe516f3abb.xml +94 -0
  238. data/test/stub_responses/records/invoice-4ad1ec01-f4a3-41d7-bbb4-d2ab2fec8e65.xml +74 -0
  239. data/test/stub_responses/records/invoice-4b9afceb-f7c7-4e64-8aac-7b009971fd52.xml +86 -0
  240. data/test/stub_responses/records/invoice-4edbf6d5-4e92-43af-bedd-7effc0b86833.xml +70 -0
  241. data/test/stub_responses/records/invoice-4fad1af2-b871-4ac5-a15a-3c5e32d2e2c4.xml +93 -0
  242. data/test/stub_responses/records/invoice-52ee4d67-cae4-462c-adb2-182c39017f3d.xml +83 -0
  243. data/test/stub_responses/records/invoice-54585f46-c1a0-4432-bd4f-c1fae2fba59b.xml +67 -0
  244. data/test/stub_responses/records/invoice-5613938b-9e27-472e-92ae-3b038b669d10.xml +87 -0
  245. data/test/stub_responses/records/invoice-5aa9451d-95d1-4f95-a966-bbab2573f71c.xml +75 -0
  246. data/test/stub_responses/records/invoice-5aadcd34-01a9-4b8d-a2bb-d7cc1de9fa45.xml +89 -0
  247. data/test/stub_responses/records/invoice-5f6deadf-36a2-495a-9980-ceb11e8af9a9.xml +85 -0
  248. data/test/stub_responses/records/invoice-625ffe1b-f5d8-438e-a376-981de5f5a733.xml +77 -0
  249. data/test/stub_responses/records/invoice-64cd559e-8e03-46af-b461-8555285cee71.xml +86 -0
  250. data/test/stub_responses/records/invoice-666f8dbb-bc9a-476c-8ec4-4665d7f83190.xml +63 -0
  251. data/test/stub_responses/records/invoice-66fbe37f-49b1-43fd-97ed-85114022cd2f.xml +79 -0
  252. data/test/stub_responses/records/invoice-673dd7cc-beb7-4697-83d4-0c47cb400cc2.xml +95 -0
  253. data/test/stub_responses/records/invoice-69fc971e-9b37-41c5-9c87-174330f22343.xml +66 -0
  254. data/test/stub_responses/records/invoice-70e6db69-e5a4-42c7-a397-aa3212c2945f.xml +75 -0
  255. data/test/stub_responses/records/invoice-762aa45d-4632-45b5-8087-b4f47690665e.xml +54 -0
  256. data/test/stub_responses/records/invoice-766d1289-b440-4675-a656-1a0612ecac77.xml +76 -0
  257. data/test/stub_responses/records/invoice-76bcb361-f93b-4513-b312-5a4af306d276.xml +67 -0
  258. data/test/stub_responses/records/invoice-76e3f056-479f-417c-a72b-f3d767899b87.xml +91 -0
  259. data/test/stub_responses/records/invoice-77b338ef-ecc0-4b95-a0d7-2617b0054611.xml +114 -0
  260. data/test/stub_responses/records/invoice-7be9956d-5316-4f6b-a66a-d355b3f159b2.xml +84 -0
  261. data/test/stub_responses/records/invoice-7dae876a-b424-436b-a4e6-17b3fdeec80c.xml +91 -0
  262. data/test/stub_responses/records/invoice-7e862d93-8dab-4856-8b0c-d844e09d750f.xml +68 -0
  263. data/test/stub_responses/records/invoice-803f70b0-56d9-4157-9787-41df271777a0.xml +84 -0
  264. data/test/stub_responses/records/invoice-86102312-aa3f-438c-9938-6840f4d8dda6.xml +75 -0
  265. data/test/stub_responses/records/invoice-8694c9c5-7097-4449-a708-b8c1982921a4.xml +69 -0
  266. data/test/stub_responses/records/invoice-86d6e00f-ef56-49f7-9a54-796ccd5ca057.xml +85 -0
  267. data/test/stub_responses/records/invoice-88e77f0f-54a5-4efc-a979-7e22223cc4d7.xml +66 -0
  268. data/test/stub_responses/records/invoice-8b0ccb6a-d9b7-4da5-8360-ef7fb157b5aa.xml +66 -0
  269. data/test/stub_responses/records/invoice-935fc854-8037-4111-8d91-993010c331cc.xml +74 -0
  270. data/test/stub_responses/records/invoice-95ef3000-c764-4ba9-a66a-b6e2d161f839.xml +62 -0
  271. data/test/stub_responses/records/invoice-962ef33f-c9d2-4602-9b9f-93a02bea23b3.xml +93 -0
  272. data/test/stub_responses/records/invoice-9868b472-1983-48e9-8edf-7e81ddf2c03a.xml +85 -0
  273. data/test/stub_responses/records/invoice-9a448e9b-a9fa-4a8b-98f5-6dc892a37374.xml +62 -0
  274. data/test/stub_responses/records/invoice-a1d04a14-96a8-4067-a0ff-8136990a354f.xml +91 -0
  275. data/test/stub_responses/records/invoice-a3bc62ef-f11b-4a9c-a4f9-a342bda371b5.xml +66 -0
  276. data/test/stub_responses/records/invoice-a6894ca0-60ee-4d45-9dd4-b44fcba46ec5.xml +90 -0
  277. data/test/stub_responses/records/invoice-a77268ce-74b2-483d-a2b3-70dbdc9e49d2.xml +78 -0
  278. data/test/stub_responses/records/invoice-a9f765e6-b9bc-4505-a47b-fb3ecb327e7b.xml +88 -0
  279. data/test/stub_responses/records/invoice-aa0173af-8707-4e7f-8dde-4c7a357bd312.xml +109 -0
  280. data/test/stub_responses/records/invoice-ab63738a-370a-43a5-bfa3-620d684e66d0.xml +83 -0
  281. data/test/stub_responses/records/invoice-b0344791-5a8a-40dd-a208-d99a461a6c10.xml +84 -0
  282. data/test/stub_responses/records/invoice-b1e53910-473c-46a3-b3cb-38ece571220e.xml +76 -0
  283. data/test/stub_responses/records/invoice-b2c02d0b-41a8-4d4d-97d7-014c78b3547d.xml +93 -0
  284. data/test/stub_responses/records/invoice-b75b3928-ab72-4424-8b93-9cdbbde4cd72.xml +90 -0
  285. data/test/stub_responses/records/invoice-bcd8a71f-aa31-4d0f-8a01-13ea26363ddf.xml +94 -0
  286. data/test/stub_responses/records/invoice-bfbb7c45-de02-45e7-b065-d9863ecfb0d8.xml +66 -0
  287. data/test/stub_responses/records/invoice-c12aff7e-12bf-4185-8702-460929f19674.xml +77 -0
  288. data/test/stub_responses/records/invoice-c3380b96-976d-4b3e-8b26-8d01eb6a3742.xml +87 -0
  289. data/test/stub_responses/records/invoice-c963f2b0-cbe1-4abd-9ccc-7e512c942068.xml +67 -0
  290. data/test/stub_responses/records/invoice-cba46b29-3788-4158-b668-ab10160ccbfe.xml +78 -0
  291. data/test/stub_responses/records/invoice-d62646b9-d0a9-4fdb-9561-756a8b7eba45.xml +63 -0
  292. data/test/stub_responses/records/invoice-dba2f021-f149-4191-a126-5351d587ab0e.xml +75 -0
  293. data/test/stub_responses/records/invoice-de5d9c29-21b3-4342-958b-ed72c4bd7ab0.xml +108 -0
  294. data/test/stub_responses/records/invoice-e3d96555-2876-4364-a46a-7551a4f52611.xml +123 -0
  295. data/test/stub_responses/records/invoice-e4a0afbd-aea0-450b-ae23-0ce921e84a77.xml +102 -0
  296. data/test/stub_responses/records/invoice-e9cb9ecb-58ef-43a8-bd20-69a85338142d.xml +76 -0
  297. data/test/stub_responses/records/invoice-ec9a6f67-7128-4a63-8ba3-5e516f455f9b.xml +94 -0
  298. data/test/stub_responses/records/invoice-ed0f2587-84fc-4aef-bc4b-b1a262e24484.xml +81 -0
  299. data/test/stub_responses/records/invoice-f362ca53-8ade-4047-865a-bb64bee5863d.xml +74 -0
  300. data/test/stub_responses/records/invoice-f571c38b-5be1-41e1-ad5a-ff6184284beb.xml +114 -0
  301. data/test/stub_responses/records/invoice-f5832195-5cd3-4660-ad3f-b73d9c64f263.xml +85 -0
  302. data/test/stub_responses/records/invoice-f9c857eb-64cd-4235-a078-d04b52c77ea7.xml +76 -0
  303. data/test/stub_responses/records/manual_journal-4765d07b-aa03-4e56-9166-50661958c864.xml +38 -0
  304. data/test/stub_responses/records/manual_journal-53fc5558-5b76-4ecd-ae5c-c4af3ccde87c.xml +31 -0
  305. data/test/stub_responses/records/manual_journal-bb6cfcfc-4500-4475-bd3a-93ee512428e0.xml +31 -0
  306. data/test/stub_responses/records/manual_journal-f00a355b-7374-445c-886b-0437bea4095c.xml +45 -0
  307. data/test/stub_responses/refresh_responses.rb +29 -0
  308. data/test/stub_responses/reports/trial_balance.xml +1435 -0
  309. data/test/stub_responses/tax_rates.xml +118 -0
  310. data/test/stub_responses/token_expired +1 -0
  311. data/test/stub_responses/tracking_categories.xml +27 -0
  312. data/test/stub_responses/unknown_error.xml +1 -0
  313. data/test/test_helper.rb +67 -0
  314. data/test/unit/models/bank_transaction_model_parsing_test.rb +133 -0
  315. data/test/unit/models/bank_transaction_test.rb +47 -0
  316. data/test/unit/models/bank_transaction_validation_test.rb +87 -0
  317. data/test/unit/models/contact_test.rb +26 -0
  318. data/test/unit/models/credit_note_test.rb +37 -0
  319. data/test/unit/models/invoice_test.rb +113 -0
  320. data/test/unit/models/journal_line_test.rb +22 -0
  321. data/test/unit/models/line_item_sum_test.rb +24 -0
  322. data/test/unit/models/line_item_test.rb +70 -0
  323. data/test/unit/oauth_config_test.rb +20 -0
  324. data/test/unit/oauth_test.rb +97 -0
  325. data/test/unit/private_application_test.rb +20 -0
  326. data/test/unit/record/base_model_test.rb +50 -0
  327. data/test/unit/record/base_test.rb +94 -0
  328. data/test/unit/record/block_validator_test.rb +125 -0
  329. data/test/unit/record/model_definition_test.rb +159 -0
  330. data/test/unit/record/parse_where_hash_test.rb +63 -0
  331. data/test/unit/record/record_association_test.rb +93 -0
  332. data/test/unit/record/validators_test.rb +228 -0
  333. data/test/unit/record_definition_test.rb +27 -0
  334. data/test/unit/report_definition_test.rb +26 -0
  335. data/test/unit/report_test.rb +145 -0
  336. data/xeroizer.gemspec +405 -0
  337. metadata +561 -0
@@ -0,0 +1,144 @@
1
+ require 'xeroizer/record/base_model_http_proxy'
2
+
3
+ module Xeroizer
4
+ module Record
5
+
6
+ class BaseModel
7
+
8
+ include ClassLevelInheritableAttributes
9
+ class_inheritable_attributes :api_controller_name
10
+
11
+ class InvaidPermissionError < StandardError; end
12
+ ALLOWED_PERMISSIONS = [:read, :write, :update]
13
+ class_inheritable_attributes :permissions
14
+
15
+ class_inheritable_attributes :xml_root_name
16
+ class_inheritable_attributes :optional_xml_root_name
17
+ class_inheritable_attributes :xml_node_name
18
+
19
+ include BaseModelHttpProxy
20
+
21
+ attr_reader :application
22
+ attr_reader :model_name
23
+ attr :model_class
24
+ attr_reader :response
25
+
26
+ class << self
27
+
28
+ # Method to allow override of the default controller name used
29
+ # in the API URLs.
30
+ #
31
+ # Default: pluaralized model name (e.g. if the controller name is
32
+ # Invoice then the default is Invoices.
33
+ def set_api_controller_name(controller_name)
34
+ self.api_controller_name = controller_name
35
+ end
36
+
37
+ # Set the permissions allowed for this class type.
38
+ # There are no permissions set by default.
39
+ # Valid permissions are :read, :write, :update.
40
+ def set_permissions(*args)
41
+ self.permissions = {}
42
+ args.each do | permission |
43
+ raise InvaidPermissionError.new("Permission #{permission} is invalid.") unless ALLOWED_PERMISSIONS.include?(permission)
44
+ self.permissions[permission] = true
45
+ end
46
+ end
47
+
48
+ # Method to allow override of the default XML node name.
49
+ #
50
+ # Default: singularized model name in camel-case.
51
+ def set_xml_node_name(node_name)
52
+ self.xml_node_name = node_name
53
+ end
54
+
55
+ # Method to allow override of the default XML root name to use
56
+ # in has_many associations.
57
+ def set_xml_root_name(root_name)
58
+ self.xml_root_name = root_name
59
+ end
60
+
61
+ # Method to add an extra top-level node to use in has_many associations.
62
+ def set_optional_xml_root_name(optional_root_name)
63
+ self.optional_root_name = optional_root_name
64
+ end
65
+
66
+ end
67
+
68
+ public
69
+
70
+ def initialize(application, model_name)
71
+ @application = application
72
+ @model_name = model_name
73
+ end
74
+
75
+ # Retrieve the controller name.
76
+ #
77
+ # Default: pluaralized model name (e.g. if the controller name is
78
+ # Invoice then the default is Invoices.
79
+ def api_controller_name
80
+ self.class.api_controller_name || model_name.pluralize
81
+ end
82
+
83
+ def model_class
84
+ @model_class ||= Xeroizer::Record.const_get(model_name.to_sym)
85
+ end
86
+
87
+ # Build a record with attributes set to the value of attributes.
88
+ def build(attributes = {})
89
+ model_class.build(attributes, self)
90
+ end
91
+
92
+ # Create (build and save) a record with attributes set to the value of attributes.
93
+ def create(attributes = {})
94
+ build(attributes).tap { |resource| resource.save }
95
+ end
96
+
97
+ # Retreive full record list for this model.
98
+ def all(options = {})
99
+ raise MethodNotAllowed.new(self, :all) unless self.class.permissions[:read]
100
+ response_xml = http_get(parse_params(options))
101
+ response = parse_response(response_xml, options)
102
+ response.response_items || []
103
+ end
104
+
105
+ # Helper method to retrieve just the first element from
106
+ # the full record list.
107
+ def first(options = {})
108
+ raise MethodNotAllowed.new(self, :all) unless self.class.permissions[:read]
109
+ result = all(options)
110
+ result.first if result.is_a?(Array)
111
+ end
112
+
113
+ # Retrieve record matching the passed in ID.
114
+ def find(id, options = {})
115
+ raise MethodNotAllowed.new(self, :all) unless self.class.permissions[:read]
116
+ response_xml = @application.http_get(@application.client, "#{url}/#{CGI.escape(id)}", options)
117
+ response = parse_response(response_xml, options)
118
+ result = response.response_items.first if response.response_items.is_a?(Array)
119
+ result.complete_record_downloaded = true if result
120
+ result
121
+ end
122
+
123
+ def parse_response(response_xml, options = {})
124
+ Response.parse(response_xml, options) do | response, elements, response_model_name |
125
+ if model_name == response_model_name
126
+ @response = response
127
+ parse_records(response, elements)
128
+ end
129
+ end
130
+ end
131
+
132
+ protected
133
+
134
+ # Parse the records part of the XML response and builds model instances as necessary.
135
+ def parse_records(response, elements)
136
+ elements.each do | element |
137
+ response.response_items << model_class.build_from_node(element, self)
138
+ end
139
+ end
140
+
141
+ end
142
+
143
+ end
144
+ end
@@ -0,0 +1,117 @@
1
+ require 'xeroizer/application_http_proxy'
2
+
3
+ module Xeroizer
4
+ module Record
5
+ module BaseModelHttpProxy
6
+
7
+ def self.included(base)
8
+ base.send :include, Xeroizer::ApplicationHttpProxy
9
+ base.send :include, InstanceMethods
10
+ end
11
+
12
+ module InstanceMethods
13
+
14
+ protected
15
+
16
+ # Parse parameters for GET requests.
17
+ def parse_params(options)
18
+ params = {}
19
+ params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
20
+ params[:OrderBy] = options[:order] if options[:order]
21
+ if options[:where]
22
+ params[:where] = case options[:where]
23
+ when String then options[:where]
24
+ when Hash then parse_where_hash(options[:where])
25
+ end
26
+ end
27
+ params[:offset] = options[:offset] if options[:offset]
28
+ params
29
+ end
30
+
31
+ # Parse the :where part of the options for GET parameters and construct a valid
32
+ # .Net version of the criteria to pass to Xero.
33
+ #
34
+ # Attribute names can be modified as follows to change the expression used:
35
+ # {attribute_name}_is_greater_than or {attribute_name}> uses '>'
36
+ # {attribute_name}_is_greater_than_or_equal_to or {attribute_name}>= uses '>='
37
+ # {attribute_name}_is_less_than or {attribute_name}< uses '<'
38
+ # {attribute_name}_is_less_than_or_equal_to or {attribute_name}< uses '<='
39
+ # DEFAULT: '=='
40
+ def parse_where_hash(where)
41
+ conditions = []
42
+ where.each do | key, value |
43
+ (attribute_name, expression) = extract_expression_from_attribute_name(key)
44
+ (field_name, field) = model_class.fields.find { | k, v | v[:internal_name] == attribute_name }
45
+ if field
46
+ conditions << where_condition_part(field, expression, value)
47
+ else
48
+ raise InvalidAttributeInWhere.new(model_name, attribute_name)
49
+ end
50
+ end
51
+ conditions.map { | (attr, expression, value) | "#{attr}#{expression}#{value}"}.join('&&')
52
+ end
53
+
54
+ # Extract the attribute name and expression from the attribute.
55
+ #
56
+ # @return [Array] containing [actual_attribute_name, expression]
57
+ def extract_expression_from_attribute_name(key)
58
+ case key.to_s
59
+ when /(_is_not|\<\>)$/
60
+ [
61
+ key.to_s.gsub(/(_is_not|\<\>)$/, '').to_sym,
62
+ '<>'
63
+ ]
64
+
65
+ when /(_is_greater_than|\>)$/
66
+ [
67
+ key.to_s.gsub(/(_is_greater_than|\>)$/, '').to_sym,
68
+ '>'
69
+ ]
70
+
71
+ when /(_is_greater_than_or_equal_to|\>\=)$/
72
+ [
73
+ key.to_s.gsub(/(_is_greater_than_or_equal_to|\>\=)$/, '').to_sym,
74
+ '>='
75
+ ]
76
+
77
+ when /(_is_less_than|\<)$/
78
+ [
79
+ key.to_s.gsub(/(_is_less_than|\<)$/, '').to_sym,
80
+ '<'
81
+ ]
82
+
83
+ when /(_is_less_than_or_equal_to|\<\=)$/
84
+ [
85
+ key.to_s.gsub(/(_is_less_than_or_equal_to|\<\=)$/, '').to_sym,
86
+ '<='
87
+ ]
88
+
89
+ else
90
+ [key, '==']
91
+
92
+ end
93
+ end
94
+
95
+ # Creates a condition part array containing the:
96
+ # * Field's API name
97
+ # * Expression
98
+ # * .Net formatted value.
99
+ def where_condition_part(field, expression, value)
100
+ case field[:type]
101
+ when :guid then ["#{field[:api_name]}.ToString()", expression, "\"#{value}\""]
102
+ when :string then [field[:api_name], expression, "\"#{value}\""]
103
+ when :boolean then [field[:api_name], expression, value ? 'true' : 'false']
104
+ when :integer then [field[:api_name], expression, value.to_s]
105
+ when :decimal then [field[:api_name], expression, value.to_s]
106
+ when :date then [field[:api_name], expression, "DateTime.Parse(\"#{value.strftime("%Y-%m-%d")}\")"]
107
+ when :datetime then [field[:api_name], expression, "DateTime.Parse(\"#{value.utc.strftime("%Y-%m-%dT%H:%M:%S")}\")"]
108
+ when :belongs_to then
109
+ when :has_many then
110
+ end
111
+ end
112
+
113
+ end
114
+
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,99 @@
1
+ module Xeroizer
2
+ module Record
3
+ module ModelDefinitionHelper
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ base.send :include, InstanceMethods
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ # Possible primary keys. At least one of these must exist before attempting to update a record.
13
+ def set_possible_primary_keys(*args)
14
+ args = [args] unless args.is_a?(Array)
15
+ self.possible_primary_keys = args
16
+ end
17
+
18
+ # Set the actual Xero primary key for this record.
19
+ def set_primary_key(primary_key_name)
20
+ self.primary_key_name = primary_key_name
21
+ end
22
+
23
+ # Whether this record type's list results contain summary data only.
24
+ #
25
+ # Records like invoices, when returning a list, only show summary data for
26
+ # certain types of associations (like the contact record) and do not return
27
+ # any data for line items.
28
+ #
29
+ # Default: false
30
+ def list_contains_summary_only(status)
31
+ self.summary_only = status
32
+ end
33
+
34
+ def list_contains_summary_only?
35
+ !!summary_only
36
+ end
37
+
38
+ # Helper methods used to define the fields this model has.
39
+ def string(field_name, options = {}); define_simple_attribute(field_name, :string, options); end
40
+ def boolean(field_name, options = {}); define_simple_attribute(field_name, :boolean, options); end
41
+ def integer(field_name, options = {}); define_simple_attribute(field_name, :integer, options, 0); end
42
+ def decimal(field_name, options = {}); define_simple_attribute(field_name, :decimal, options, 0.0); end
43
+ def date(field_name, options = {}); define_simple_attribute(field_name, :date, options); end
44
+ def datetime(field_name, options = {}); define_simple_attribute(field_name, :datetime, options); end
45
+ def datetime_utc(field_name, options = {}); define_simple_attribute(field_name, :datetime_utc, options); end
46
+
47
+ def guid(field_name, options = {})
48
+ # Ensure all automated Id conversions are changed to ID.
49
+ options[:api_name] ||= field_name.to_s.camelize.gsub(/Id/, 'ID')
50
+ define_simple_attribute(field_name, :guid, options)
51
+ end
52
+
53
+ # Helper method to simplify field definition.
54
+ # Creates an accessor and reader for the field.
55
+ # Options:
56
+ # :internal_name => allows the specification of an internal field name differing from the API's field name.
57
+ # :api_name => allows the API name to be specified if it can't be properly converted from camelize.
58
+ # :model_name => allows class used for children to be different from it's ndoe name in the XML.
59
+ # :type => type of field
60
+ # :skip_writer => skip the writer method
61
+ def define_simple_attribute(field_name, field_type, options, value_if_nil = nil)
62
+ self.fields ||= {}
63
+
64
+ internal_field_name = options[:internal_name] || field_name
65
+ self.fields[field_name] = options.merge({
66
+ :internal_name => internal_field_name,
67
+ :api_name => options[:api_name] || field_name.to_s.camelize,
68
+ :type => field_type
69
+ })
70
+ define_method internal_field_name do
71
+ @attributes[field_name] || value_if_nil
72
+ end
73
+
74
+ unless options[:skip_writer]
75
+ define_method "#{internal_field_name}=".to_sym do | value |
76
+ @attributes[field_name] = value
77
+ end
78
+ end
79
+ end
80
+
81
+ end
82
+
83
+ module InstanceMethods
84
+
85
+ # Returns the value of the Xero primary key for this record if it exists.
86
+ def id
87
+ self[self.class.primary_key_name]
88
+ end
89
+
90
+ # Sets the value of the Xero primary key for this record if it exists.
91
+ def id=(new_id)
92
+ self[self.class.primary_key_name] = new_id
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,140 @@
1
+ module Xeroizer
2
+ module Record
3
+ module RecordAssociationHelper
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+
11
+ def belongs_to(field_name, options = {})
12
+ internal_field_name = options[:internal_name] || field_name
13
+ internal_singular_field_name = internal_field_name.to_s.singularize
14
+
15
+ define_association_attribute(field_name, internal_singular_field_name, :belongs_to, options)
16
+
17
+ # Create a #build_record_name method to build the record.
18
+ define_method "build_#{internal_singular_field_name}" do | *args |
19
+ attributes = args.size == 1 ? args.first : {}
20
+
21
+ # The name of the record model.
22
+ model_name = options[:model_name] ? options[:model_name].to_sym : field_name.to_s.singularize.camelize.to_sym
23
+
24
+ # The record's parent instance for this current application.
25
+ model_parent = new_model_class(model_name)
26
+
27
+ # Create a new record, binding it to it's parent instance.
28
+ record = Xeroizer::Record.const_get(model_name).build(attributes, model_parent)
29
+ self.attributes[field_name] = record
30
+ end
31
+ end
32
+
33
+ def has_many(field_name, options = {})
34
+ internal_field_name = options[:internal_name] || field_name
35
+ internal_singular_field_name = internal_field_name.to_s.singularize
36
+
37
+ define_association_attribute(field_name, internal_field_name, :has_many, options)
38
+
39
+ # Create an #add_record_name method to build the record and add to the attributes.
40
+ define_method "add_#{internal_singular_field_name}" do | *args |
41
+ # The name of the record model.
42
+ model_name = options[:model_name] ? options[:model_name].to_sym : field_name.to_s.singularize.camelize.to_sym
43
+
44
+ # The record's parent instance for this current application.
45
+ model_parent = new_model_class(model_name)
46
+
47
+ # The class of this record.
48
+ record_class = Xeroizer::Record.const_get(model_name)
49
+
50
+ # Parse the *args variable so that we can use this method like:
51
+ # add_record({fields}, {fields}, ...)
52
+ # add_record(record_one, record_two, ...)
53
+ # add_record([{fields}, {fields}], ...)
54
+ # add_record(key => val, key2 => val)
55
+ records = []
56
+ if args.size == 1 && args.first.is_a?(Array)
57
+ records = args.first
58
+ elsif args.size > 0
59
+ records = args
60
+ else
61
+ raise StandardError.new("Invalid arguments for #{self.class.name}#add_#{internal_singular_field_name}(#{args.inspect}).")
62
+ end
63
+
64
+ # Ensure that complete record is downloaded before adding new records
65
+ self.send(field_name)
66
+
67
+ # Add each record.
68
+ last_record = nil
69
+ records.each do | record |
70
+ record = record_class.build(record, model_parent) if record.is_a?(Hash)
71
+ raise StandardError.new("Record #{record.class.name} is not a #{record_class.name}.") unless record.is_a?(record_class)
72
+ self.attributes[field_name] ||= []
73
+ self.attributes[field_name] << record
74
+ last_record = record
75
+ end
76
+
77
+ last_record # last record
78
+ end
79
+
80
+ end
81
+
82
+ def define_association_attribute(field_name, internal_field_name, association_type, options)
83
+ define_simple_attribute(field_name, association_type, options.merge!(:skip_writer => true), ((association_type == :has_many) ? [] : nil))
84
+
85
+ internal_field_name = options[:internal_name] || field_name
86
+ internal_singular_field_name = internal_field_name.to_s.singularize
87
+ model_name = options[:model_name] ? options[:model_name].to_sym : field_name.to_s.singularize.camelize.to_sym
88
+
89
+ define_method "#{internal_field_name}=".to_sym do | value |
90
+ record_class = Xeroizer::Record.const_get(model_name)
91
+ case value
92
+ when Hash
93
+ self.attributes[field_name] = ((association_type == :has_many) ? [] : nil)
94
+ case association_type
95
+ when :has_many
96
+ self.attributes[field_name] = []
97
+ self.send("add_#{internal_singular_field_name}".to_sym, value)
98
+
99
+ when :belongs_to
100
+ self.attributes[field_name] = Xeroizer::Record.const_get(model_name).build(value, new_model_class(model_name))
101
+
102
+ end
103
+
104
+ when Array
105
+ self.attributes[field_name] = ((association_type == :has_many) ? [] : nil)
106
+ value.each do | single_value |
107
+ case single_value
108
+ when Hash then send("add_#{internal_singular_field_name}".to_sym, single_value)
109
+ when record_class then self.attributes[field_name] << single_value
110
+ else raise AssociationTypeMismatch.new(record_class, single_value.class)
111
+ end
112
+ end
113
+
114
+ when record_class
115
+ self.attributes[field_name] = ((association_type == :has_many) ? [value] : value)
116
+
117
+ when NilClass
118
+ self.attributes[field_name] = []
119
+
120
+ else
121
+ raise AssociationTypeMismatch.new(record_class, value.class)
122
+ end
123
+ end
124
+
125
+ # Override reader for this association if this association belongs
126
+ # to a summary-typed record. This will automatically attempt to download
127
+ # the complete version of the record before accessing the association.
128
+ if list_contains_summary_only?
129
+ define_method internal_field_name do
130
+ download_complete_record! unless new_record? || options[:list_complete] || complete_record_downloaded?
131
+ self.attributes[field_name] || ((association_type == :has_many) ? [] : nil)
132
+ end
133
+ end
134
+ end
135
+
136
+ end
137
+
138
+ end
139
+ end
140
+ end