datashift 0.16.0 → 0.40.0

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 (422) hide show
  1. checksums.yaml +4 -4
  2. data/{LICENSE.txt → LICENSE} +0 -0
  3. data/Rakefile +1 -20
  4. data/datashift.thor +125 -0
  5. data/lib/applications/apache_poi_extensions.rb +21 -52
  6. data/lib/applications/excel.rb +64 -57
  7. data/lib/applications/hssf_row_extensions.rb +66 -0
  8. data/lib/applications/jexcel_file.rb +99 -95
  9. data/lib/applications/jexcel_file_extensions.rb +76 -83
  10. data/lib/applications/jruby/word.rb +36 -36
  11. data/lib/applications/ruby_poi_translations.rb +34 -32
  12. data/lib/applications/spreadsheet_extensions.rb +21 -19
  13. data/lib/datashift.rb +49 -59
  14. data/lib/datashift/binder.rb +217 -0
  15. data/lib/datashift/column_packer.rb +21 -72
  16. data/lib/datashift/configuration.rb +317 -0
  17. data/lib/datashift/context_factory.rb +88 -0
  18. data/lib/datashift/core_ext/array.rb +15 -0
  19. data/lib/datashift/core_ext/csv_ext.rb +46 -0
  20. data/lib/datashift/core_ext/string.rb +49 -0
  21. data/lib/datashift/core_ext/to_b.rb +11 -0
  22. data/lib/datashift/delimiters.rb +55 -61
  23. data/lib/datashift/doc_context.rb +137 -0
  24. data/lib/datashift/excel_base.rb +93 -81
  25. data/lib/datashift/exceptions.rb +30 -28
  26. data/lib/datashift/file_definitions.rb +44 -39
  27. data/lib/datashift/guards.rb +5 -5
  28. data/lib/datashift/header.rb +25 -0
  29. data/lib/datashift/headers.rb +94 -0
  30. data/lib/datashift/inbound_data/column.rb +44 -0
  31. data/lib/datashift/inbound_data/lookup_support.rb +33 -0
  32. data/lib/datashift/inbound_data/method_binding.rb +139 -0
  33. data/lib/datashift/load_object.rb +37 -12
  34. data/lib/datashift/logging.rb +54 -27
  35. data/lib/datashift/mandatory.rb +39 -0
  36. data/lib/datashift/mapping/data_flow_schema.rb +198 -0
  37. data/lib/datashift/{model_mapper.rb → mapping/mapper_utils.rb} +30 -10
  38. data/lib/datashift/model_methods/catalogue.rb +183 -0
  39. data/lib/datashift/model_methods/collection.rb +140 -0
  40. data/lib/datashift/model_methods/model_method.rb +162 -0
  41. data/lib/datashift/model_methods/model_methods_manager.rb +76 -0
  42. data/lib/datashift/model_methods/operator.rb +62 -0
  43. data/lib/datashift/node_collection.rb +26 -0
  44. data/lib/datashift/node_context.rb +68 -0
  45. data/lib/datashift/populator.rb +308 -282
  46. data/lib/datashift/progress_monitor.rb +91 -0
  47. data/lib/datashift/querying.rb +110 -52
  48. data/lib/datashift/templates/import_export_config.erb +55 -0
  49. data/lib/datashift/transformation/factory.rb +219 -0
  50. data/lib/datashift/transformation/remove.rb +44 -0
  51. data/lib/datashift/version.rb +3 -0
  52. data/lib/exporters/configuration.rb +84 -0
  53. data/lib/exporters/csv_exporter.rb +54 -52
  54. data/lib/exporters/excel_exporter.rb +80 -61
  55. data/lib/exporters/exporter_base.rb +8 -8
  56. data/lib/generators/config_generator.rb +218 -0
  57. data/lib/generators/csv_generator.rb +13 -70
  58. data/lib/generators/excel_generator.rb +23 -111
  59. data/lib/generators/generator_base.rb +15 -70
  60. data/lib/loaders/configuration.rb +90 -0
  61. data/lib/loaders/csv_loader.rb +63 -101
  62. data/lib/loaders/excel_loader.rb +71 -156
  63. data/lib/loaders/failure_data.rb +40 -0
  64. data/lib/loaders/file_loader.rb +16 -0
  65. data/lib/loaders/loader_base.rb +82 -410
  66. data/lib/loaders/loader_factory.rb +42 -0
  67. data/lib/loaders/paperclip/attachment_loader.rb +157 -140
  68. data/lib/loaders/paperclip/datashift_paperclip.rb +18 -35
  69. data/lib/loaders/paperclip/image_loading.rb +40 -35
  70. data/lib/loaders/reporters/basic_stdout_reporter.rb +40 -0
  71. data/lib/loaders/reporters/reporter.rb +26 -0
  72. data/lib/tasks/config.thor +65 -0
  73. data/{tasks → lib/tasks}/config/seed_fu_product_template.erb +0 -0
  74. data/{tasks → lib/tasks}/config/tidy_config.txt +0 -0
  75. data/lib/tasks/export.thor +192 -0
  76. data/lib/tasks/generate.thor +190 -0
  77. data/lib/tasks/import.thor +142 -0
  78. data/lib/{thor → tasks}/paperclip.thor +69 -69
  79. data/{tasks → lib/tasks/to_convert_to_thor}/db_tasks.rake +20 -20
  80. data/lib/tasks/tools.thor +109 -0
  81. data/spec/MissingAttachmentRecords/DEMO_001_ror_bag.jpeg +0 -0
  82. data/spec/MissingAttachmentRecords/DEMO_002_Powerstation.jpeg +0 -0
  83. data/spec/MissingAttachmentRecords/DEMO_003_ror_mug.jpeg +0 -0
  84. data/spec/MissingAttachmentRecords/DEMO_004_ror_ringer.jpeg +0 -0
  85. data/spec/datashift/binder_spec.rb +266 -0
  86. data/spec/datashift/config_generator_spec.rb +186 -0
  87. data/spec/datashift/configuration.rb +66 -0
  88. data/spec/datashift/context_factory_spec.rb +63 -0
  89. data/spec/datashift/data_flow_schema_spec.rb +150 -0
  90. data/spec/datashift/datashift_spec.rb +52 -0
  91. data/spec/datashift/excel_base_spec.rb +57 -0
  92. data/spec/datashift/excel_spec.rb +188 -0
  93. data/spec/datashift/failure_data_spec.rb +27 -0
  94. data/spec/{file_definitions.rb → datashift/file_definitions.rb} +9 -10
  95. data/spec/datashift/headers_spec.rb +56 -0
  96. data/spec/datashift/inbound_data_spec.rb +47 -0
  97. data/spec/datashift/mapper_utils_spec.rb +38 -0
  98. data/spec/datashift/method_binding_spec.rb +60 -0
  99. data/spec/datashift/model_method_spec.rb +109 -0
  100. data/spec/datashift/model_methods_catalogue.rb +111 -0
  101. data/spec/datashift/model_methods_collection_spec.rb +138 -0
  102. data/spec/datashift/model_methods_manager_spec.rb +329 -0
  103. data/spec/datashift/populator_spec.rb +117 -0
  104. data/spec/datashift/thor_spec.rb +314 -0
  105. data/spec/datashift/transformation/factory_spec.rb +195 -0
  106. data/spec/datashift/transformation/transformer_remove_spec.rb +43 -0
  107. data/spec/dummy/Gemfile +53 -0
  108. data/spec/dummy/Gemfile.lock +197 -0
  109. data/spec/dummy/README.rdoc +28 -0
  110. data/spec/dummy/Rakefile +6 -0
  111. data/spec/dummy/app/assets/javascripts/application.js +16 -0
  112. data/spec/dummy/app/assets/javascripts/categories.js +2 -0
  113. data/spec/dummy/app/assets/javascripts/digitals.js +2 -0
  114. data/spec/dummy/app/assets/javascripts/empties.js +2 -0
  115. data/spec/dummy/app/assets/javascripts/loader_releases.js +2 -0
  116. data/spec/dummy/app/assets/javascripts/long_and_complex_table_linked_to_versions.js +2 -0
  117. data/spec/dummy/app/assets/javascripts/milestones.js +2 -0
  118. data/spec/dummy/app/assets/javascripts/owners.js +2 -0
  119. data/spec/dummy/app/assets/javascripts/projects.js +2 -0
  120. data/spec/dummy/app/assets/javascripts/users.js +2 -0
  121. data/spec/dummy/app/assets/javascripts/versions.js +2 -0
  122. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  123. data/spec/dummy/app/assets/stylesheets/categories.css +4 -0
  124. data/spec/dummy/app/assets/stylesheets/digitals.css +4 -0
  125. data/spec/dummy/app/assets/stylesheets/empties.css +4 -0
  126. data/spec/dummy/app/assets/stylesheets/loader_releases.css +4 -0
  127. data/spec/dummy/app/assets/stylesheets/long_and_complex_table_linked_to_versions.css +4 -0
  128. data/spec/dummy/app/assets/stylesheets/milestones.css +4 -0
  129. data/spec/dummy/app/assets/stylesheets/owners.css +4 -0
  130. data/spec/dummy/app/assets/stylesheets/projects.css +4 -0
  131. data/spec/dummy/app/assets/stylesheets/scaffold.css +56 -0
  132. data/spec/dummy/app/assets/stylesheets/users.css +4 -0
  133. data/spec/dummy/app/assets/stylesheets/versions.css +4 -0
  134. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  135. data/spec/dummy/app/controllers/categories_controller.rb +58 -0
  136. data/spec/dummy/app/controllers/digitals_controller.rb +58 -0
  137. data/spec/dummy/app/controllers/empties_controller.rb +58 -0
  138. data/spec/dummy/app/controllers/loader_releases_controller.rb +58 -0
  139. data/spec/dummy/app/controllers/long_and_complex_table_linked_to_versions_controller.rb +58 -0
  140. data/spec/dummy/app/controllers/milestones_controller.rb +58 -0
  141. data/spec/dummy/app/controllers/owners_controller.rb +58 -0
  142. data/spec/dummy/app/controllers/projects_controller.rb +58 -0
  143. data/spec/dummy/app/controllers/users_controller.rb +58 -0
  144. data/spec/dummy/app/controllers/versions_controller.rb +58 -0
  145. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  146. data/spec/dummy/app/helpers/categories_helper.rb +2 -0
  147. data/spec/dummy/app/helpers/digitals_helper.rb +2 -0
  148. data/spec/dummy/app/helpers/empties_helper.rb +2 -0
  149. data/spec/dummy/app/helpers/loader_releases_helper.rb +2 -0
  150. data/spec/dummy/app/helpers/long_and_complex_table_linked_to_versions_helper.rb +2 -0
  151. data/spec/dummy/app/helpers/milestones_helper.rb +2 -0
  152. data/spec/dummy/app/helpers/owners_helper.rb +2 -0
  153. data/spec/dummy/app/helpers/projects_helper.rb +2 -0
  154. data/spec/dummy/app/helpers/users_helper.rb +2 -0
  155. data/spec/dummy/app/helpers/versions_helper.rb +2 -0
  156. data/spec/dummy/app/models/category.rb +6 -0
  157. data/spec/dummy/app/models/digital.rb +22 -0
  158. data/spec/dummy/app/models/empty.rb +2 -0
  159. data/spec/dummy/app/models/loader_release.rb +10 -0
  160. data/spec/dummy/app/models/long_and_complex_table_linked_to_version.rb +6 -0
  161. data/spec/dummy/app/models/milestone.rb +15 -0
  162. data/spec/dummy/app/models/owner.rb +13 -0
  163. data/spec/dummy/app/models/project.rb +53 -0
  164. data/spec/dummy/app/models/user.rb +5 -0
  165. data/spec/dummy/app/models/version.rb +7 -0
  166. data/spec/dummy/app/views/categories/_form.html.erb +17 -0
  167. data/spec/dummy/app/views/categories/edit.html.erb +6 -0
  168. data/spec/dummy/app/views/categories/index.html.erb +25 -0
  169. data/spec/dummy/app/views/categories/new.html.erb +5 -0
  170. data/spec/dummy/app/views/categories/show.html.erb +4 -0
  171. data/spec/dummy/app/views/digitals/_form.html.erb +17 -0
  172. data/spec/dummy/app/views/digitals/edit.html.erb +6 -0
  173. data/spec/dummy/app/views/digitals/index.html.erb +25 -0
  174. data/spec/dummy/app/views/digitals/new.html.erb +5 -0
  175. data/spec/dummy/app/views/digitals/show.html.erb +4 -0
  176. data/spec/dummy/app/views/empties/_form.html.erb +17 -0
  177. data/spec/dummy/app/views/empties/edit.html.erb +6 -0
  178. data/spec/dummy/app/views/empties/index.html.erb +25 -0
  179. data/spec/dummy/app/views/empties/new.html.erb +5 -0
  180. data/spec/dummy/app/views/empties/show.html.erb +4 -0
  181. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  182. data/spec/dummy/app/views/loader_releases/_form.html.erb +17 -0
  183. data/spec/dummy/app/views/loader_releases/edit.html.erb +6 -0
  184. data/spec/dummy/app/views/loader_releases/index.html.erb +25 -0
  185. data/spec/dummy/app/views/loader_releases/new.html.erb +5 -0
  186. data/spec/dummy/app/views/loader_releases/show.html.erb +4 -0
  187. data/spec/dummy/app/views/long_and_complex_table_linked_to_versions/_form.html.erb +17 -0
  188. data/spec/dummy/app/views/long_and_complex_table_linked_to_versions/edit.html.erb +6 -0
  189. data/spec/dummy/app/views/long_and_complex_table_linked_to_versions/index.html.erb +25 -0
  190. data/spec/dummy/app/views/long_and_complex_table_linked_to_versions/new.html.erb +5 -0
  191. data/spec/dummy/app/views/long_and_complex_table_linked_to_versions/show.html.erb +4 -0
  192. data/spec/dummy/app/views/milestones/_form.html.erb +17 -0
  193. data/spec/dummy/app/views/milestones/edit.html.erb +6 -0
  194. data/spec/dummy/app/views/milestones/index.html.erb +25 -0
  195. data/spec/dummy/app/views/milestones/new.html.erb +5 -0
  196. data/spec/dummy/app/views/milestones/show.html.erb +4 -0
  197. data/spec/dummy/app/views/owners/_form.html.erb +17 -0
  198. data/spec/dummy/app/views/owners/edit.html.erb +6 -0
  199. data/spec/dummy/app/views/owners/index.html.erb +25 -0
  200. data/spec/dummy/app/views/owners/new.html.erb +5 -0
  201. data/spec/dummy/app/views/owners/show.html.erb +4 -0
  202. data/spec/dummy/app/views/projects/_form.html.erb +17 -0
  203. data/spec/dummy/app/views/projects/edit.html.erb +6 -0
  204. data/spec/dummy/app/views/projects/index.html.erb +25 -0
  205. data/spec/dummy/app/views/projects/new.html.erb +5 -0
  206. data/spec/dummy/app/views/projects/show.html.erb +4 -0
  207. data/spec/dummy/app/views/users/_form.html.erb +17 -0
  208. data/spec/dummy/app/views/users/edit.html.erb +6 -0
  209. data/spec/dummy/app/views/users/index.html.erb +25 -0
  210. data/spec/dummy/app/views/users/new.html.erb +5 -0
  211. data/spec/dummy/app/views/users/show.html.erb +4 -0
  212. data/spec/dummy/app/views/versions/_form.html.erb +17 -0
  213. data/spec/dummy/app/views/versions/edit.html.erb +6 -0
  214. data/spec/dummy/app/views/versions/index.html.erb +25 -0
  215. data/spec/dummy/app/views/versions/new.html.erb +5 -0
  216. data/spec/dummy/app/views/versions/show.html.erb +4 -0
  217. data/spec/dummy/bin/bundle +3 -0
  218. data/spec/dummy/bin/rails +9 -0
  219. data/spec/dummy/bin/rake +9 -0
  220. data/spec/dummy/bin/setup +29 -0
  221. data/spec/dummy/bin/spring +16 -0
  222. data/spec/dummy/config.ru +4 -0
  223. data/spec/dummy/config/application.rb +26 -0
  224. data/spec/dummy/config/boot.rb +3 -0
  225. data/spec/dummy/config/database.yml +25 -0
  226. data/spec/dummy/config/environment.rb +5 -0
  227. data/spec/dummy/config/environments/development.rb +41 -0
  228. data/spec/dummy/config/environments/production.rb +79 -0
  229. data/spec/dummy/config/environments/test.rb +42 -0
  230. data/spec/dummy/config/initializers/assets.rb +11 -0
  231. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  232. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  233. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  234. data/spec/dummy/config/initializers/inflections.rb +16 -0
  235. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  236. data/spec/dummy/config/initializers/session_store.rb +3 -0
  237. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  238. data/spec/dummy/config/locales/en.yml +23 -0
  239. data/spec/dummy/config/routes.rb +76 -0
  240. data/spec/dummy/config/secrets.yml +22 -0
  241. data/spec/dummy/db/development.sqlite3 +0 -0
  242. data/spec/dummy/db/migrate/20110803201325_create_test_bed.rb +98 -0
  243. data/spec/dummy/db/migrate/20121009161700_add_digitals.rb +24 -0
  244. data/spec/dummy/db/migrate/20161005123106_create_digitals.rb +8 -0
  245. data/spec/dummy/db/migrate/20161005123106_create_long_and_complex_table_linked_to_versions.rb +8 -0
  246. data/spec/dummy/db/migrate/20161005123107_create_loader_releases.rb +8 -0
  247. data/spec/dummy/db/migrate/20161005123108_create_owners.rb +8 -0
  248. data/spec/dummy/db/migrate/20161005123109_create_empties.rb +8 -0
  249. data/spec/dummy/db/migrate/20161005123110_create_projects.rb +8 -0
  250. data/spec/dummy/db/migrate/20161005123111_create_users.rb +8 -0
  251. data/spec/dummy/db/migrate/20161005123111_create_versions.rb +8 -0
  252. data/spec/dummy/db/migrate/20161005123112_create_milestones.rb +8 -0
  253. data/spec/dummy/db/migrate/20161005123113_create_categories.rb +8 -0
  254. data/spec/dummy/db/schema.rb +93 -0
  255. data/spec/dummy/db/seeds.rb +9 -0
  256. data/spec/dummy/db/test.sqlite3 +0 -0
  257. data/spec/dummy/log/test.log +69 -0
  258. data/spec/dummy/public/404.html +67 -0
  259. data/spec/dummy/public/422.html +67 -0
  260. data/spec/dummy/public/500.html +66 -0
  261. data/spec/dummy/public/favicon.ico +0 -0
  262. data/spec/dummy/public/robots.txt +5 -0
  263. data/spec/dummy/sandbox_example.thor +4 -0
  264. data/spec/dummy/test/controllers/categories_controller_test.rb +49 -0
  265. data/spec/dummy/test/controllers/digitals_controller_test.rb +49 -0
  266. data/spec/dummy/test/controllers/empties_controller_test.rb +49 -0
  267. data/spec/dummy/test/controllers/loader_releases_controller_test.rb +49 -0
  268. data/spec/dummy/test/controllers/long_and_complex_table_linked_to_versions_controller_test.rb +49 -0
  269. data/spec/dummy/test/controllers/milestones_controller_test.rb +49 -0
  270. data/spec/dummy/test/controllers/owners_controller_test.rb +49 -0
  271. data/spec/dummy/test/controllers/projects_controller_test.rb +49 -0
  272. data/spec/dummy/test/controllers/users_controller_test.rb +49 -0
  273. data/spec/dummy/test/controllers/versions_controller_test.rb +49 -0
  274. data/spec/dummy/test/factories/categories.rb +5 -0
  275. data/spec/dummy/test/factories/digitals.rb +5 -0
  276. data/spec/dummy/test/factories/empties.rb +5 -0
  277. data/spec/dummy/test/factories/loader_releases.rb +5 -0
  278. data/spec/dummy/test/factories/long_and_complex_table_linked_to_versions.rb +5 -0
  279. data/spec/dummy/test/factories/milestones.rb +5 -0
  280. data/spec/dummy/test/factories/owners.rb +5 -0
  281. data/spec/dummy/test/factories/projects.rb +5 -0
  282. data/spec/dummy/test/factories/users.rb +5 -0
  283. data/spec/dummy/test/factories/versions.rb +5 -0
  284. data/spec/dummy/test/models/category_test.rb +7 -0
  285. data/spec/dummy/test/models/digital_test.rb +7 -0
  286. data/spec/dummy/test/models/empty_test.rb +7 -0
  287. data/spec/dummy/test/models/loader_release_test.rb +7 -0
  288. data/spec/dummy/test/models/long_and_complex_table_linked_to_version_test.rb +7 -0
  289. data/spec/dummy/test/models/milestone_test.rb +7 -0
  290. data/spec/dummy/test/models/owner_test.rb +7 -0
  291. data/spec/dummy/test/models/project_test.rb +7 -0
  292. data/spec/dummy/test/models/user_test.rb +7 -0
  293. data/spec/dummy/test/models/version_test.rb +7 -0
  294. data/spec/dummy/test/test_helper.rb +10 -0
  295. data/spec/exporters/csv_exporter_spec.rb +240 -0
  296. data/spec/exporters/csv_generator_spec.rb +139 -0
  297. data/spec/exporters/excel_exporter_spec.rb +193 -0
  298. data/spec/exporters/excel_generator_spec.rb +181 -0
  299. data/spec/exporters/generator_base_spec.rb +45 -0
  300. data/spec/factories/categories.rb +7 -0
  301. data/spec/factories/factories.rb +18 -0
  302. data/spec/factories/milestone.rb +16 -0
  303. data/spec/factories/projects.rb +41 -0
  304. data/spec/fixtures/BadAssociationName.xls +0 -0
  305. data/spec/fixtures/DemoNegativeTesting.xls +0 -0
  306. data/spec/fixtures/ProjectConfiguration.yml +18 -0
  307. data/spec/fixtures/ProjectsMultiCategories.xls +0 -0
  308. data/spec/fixtures/ProjectsMultiCategoriesHeaderLookup.xls +0 -0
  309. data/spec/fixtures/ProjectsSingleCategories.xls +0 -0
  310. data/spec/fixtures/ProjectsSingleCategories.xlsx +0 -0
  311. data/spec/fixtures/SimpleProjects.xls +0 -0
  312. data/spec/fixtures/config/database.yml +28 -0
  313. data/spec/fixtures/csv/BadAssociationName.csv +6 -0
  314. data/spec/fixtures/csv/DemoNegativeTesting.csv +6 -0
  315. data/spec/fixtures/csv/ProjectsMultiCategories.csv +5 -0
  316. data/spec/fixtures/csv/ProjectsMultiCategoriesHeaderLookup.csv +5 -0
  317. data/spec/fixtures/csv/ProjectsSingleCategories.csv +5 -0
  318. data/spec/fixtures/csv/SimpleProjects.csv +4 -0
  319. data/spec/fixtures/db/migrate/20110803201325_create_test_bed.rb +98 -0
  320. data/spec/fixtures/db/migrate/20121009161700_add_digitals.rb +24 -0
  321. data/spec/fixtures/db/seeds.rb +9 -0
  322. data/spec/fixtures/images/DEMO_001_ror_bag.jpeg +0 -0
  323. data/spec/fixtures/images/DEMO_002_Powerstation.jpeg +0 -0
  324. data/spec/fixtures/images/DEMO_003_ror_mug.jpeg +0 -0
  325. data/spec/fixtures/images/DEMO_004_ror_ringer.jpeg +0 -0
  326. data/spec/fixtures/load_datashift.thor +3 -0
  327. data/spec/fixtures/models/category.rb +6 -0
  328. data/spec/fixtures/models/digital.rb +22 -0
  329. data/spec/fixtures/models/empty.rb +2 -0
  330. data/spec/fixtures/models/loader_release.rb +10 -0
  331. data/spec/fixtures/models/long_and_complex_table_linked_to_version.rb +6 -0
  332. data/spec/fixtures/models/milestone.rb +15 -0
  333. data/spec/fixtures/models/owner.rb +13 -0
  334. data/spec/fixtures/models/project.rb +53 -0
  335. data/spec/fixtures/models/user.rb +5 -0
  336. data/spec/fixtures/models/version.rb +7 -0
  337. data/spec/fixtures/results/exp_project_assoc_headers.xls +0 -0
  338. data/spec/fixtures/results/exp_project_collection_spec.csv +2 -0
  339. data/spec/fixtures/results/exp_project_export.xls +0 -0
  340. data/spec/fixtures/results/exp_project_first_export.xls +0 -0
  341. data/spec/fixtures/results/exp_project_plus_assoc.xls +0 -0
  342. data/spec/fixtures/results/exp_project_plus_assoc_export_spec.csv +9 -0
  343. data/spec/fixtures/results/gen_project_plus_assoc_template.xls +0 -0
  344. data/spec/fixtures/results/gen_project_plus_some_assoc_template.xls +0 -0
  345. data/spec/fixtures/results/gen_project_template.xls +0 -0
  346. data/spec/fixtures/results/project_and_assoc_in_hash_export.xls +0 -0
  347. data/spec/fixtures/results/project_and_assoc_in_json_export.csv +9 -0
  348. data/spec/fixtures/results/project_and_assoc_in_json_export.xls +0 -0
  349. data/spec/fixtures/results/project_export_spec_with_custom_delim_,.csv +2 -0
  350. data/spec/fixtures/results/project_export_spec_with_custom_delim_/302/243.csv +2 -0
  351. data/spec/fixtures/results/project_export_spec_with_custom_delim_/302/247.csv +2 -0
  352. data/spec/fixtures/results/project_plus_assoc_template.csv +1 -0
  353. data/spec/fixtures/results/project_plus_some_assoc_template.csv +1 -0
  354. data/spec/fixtures/results/project_remove_export_spec.csv +2 -0
  355. data/spec/fixtures/results/project_template.csv +1 -0
  356. data/spec/fixtures/results/project_with_methods_export_spec.csv +2 -0
  357. data/spec/fixtures/results/thor_spec_gen_project.csv +1 -0
  358. data/spec/fixtures/sandbox_example.thor +4 -0
  359. data/spec/fixtures/simple_export_spec.xls +0 -0
  360. data/spec/fixtures/simple_template_spec.xls +0 -0
  361. data/spec/fixtures/test_model_defs.rb +7 -0
  362. data/spec/loaders/csv_loader_spec.rb +206 -0
  363. data/spec/loaders/data_flow_excel_loader_spec.rb +290 -0
  364. data/spec/loaders/excel_loader_failures_spec.rb +67 -0
  365. data/spec/loaders/excel_loader_spec.rb +294 -0
  366. data/spec/loaders/loader_base_spec.rb +29 -0
  367. data/spec/loaders/paperclip_loader_spec.rb +106 -0
  368. data/spec/log/datashift.log +14930 -0
  369. data/spec/private/digitals/1/DEMO_003_ror_mug.jpeg +0 -0
  370. data/spec/private/digitals/2/DEMO_002_Powerstation.jpeg +0 -0
  371. data/spec/private/digitals/3/DEMO_004_ror_ringer.jpeg +0 -0
  372. data/spec/private/digitals/4/DEMO_001_ror_bag.jpeg +0 -0
  373. data/spec/spec_helper.rb +26 -230
  374. data/spec/support/clear_and_manage_contexts.rb +25 -0
  375. data/spec/support/database_cleaner.rb +32 -0
  376. data/spec/support/datashift_test_helpers.rb +153 -0
  377. data/spec/support/files_paths_helper.rb +13 -0
  378. data/spec/support/fixtures/results/mapping_template.yaml +15 -0
  379. data/spec/support/sandbox.rb +136 -0
  380. metadata +804 -85
  381. data/README.markdown +0 -274
  382. data/README.rdoc +0 -19
  383. data/VERSION +0 -1
  384. data/datashift.gemspec +0 -48
  385. data/lib/applications/jruby/old_pre_proxy_jexcel_file.rb +0 -437
  386. data/lib/datashift/data_transforms.rb +0 -83
  387. data/lib/datashift/mapping_file_definitions.rb +0 -88
  388. data/lib/datashift/mapping_service.rb +0 -91
  389. data/lib/datashift/method_detail.rb +0 -165
  390. data/lib/datashift/method_details_manager.rb +0 -95
  391. data/lib/datashift/method_dictionary.rb +0 -281
  392. data/lib/datashift/method_mapper.rb +0 -174
  393. data/lib/datashift/thor_base.rb +0 -38
  394. data/lib/generators/mapping_generator.rb +0 -112
  395. data/lib/helpers/core_ext/csv_file.rb +0 -33
  396. data/lib/helpers/core_ext/to_b.rb +0 -24
  397. data/lib/loaders/reporter.rb +0 -58
  398. data/lib/thor/export.thor +0 -175
  399. data/lib/thor/generate.thor +0 -191
  400. data/lib/thor/import.thor +0 -110
  401. data/lib/thor/mapping.thor +0 -65
  402. data/lib/thor/tools.thor +0 -84
  403. data/spec/Gemfile +0 -31
  404. data/spec/Gemfile.lock +0 -134
  405. data/spec/csv_exporter_spec.rb +0 -144
  406. data/spec/csv_generator_spec.rb +0 -159
  407. data/spec/csv_loader_spec.rb +0 -212
  408. data/spec/datashift_spec.rb +0 -55
  409. data/spec/excel_exporter_spec.rb +0 -199
  410. data/spec/excel_generator_spec.rb +0 -203
  411. data/spec/excel_loader_spec.rb +0 -237
  412. data/spec/excel_spec.rb +0 -203
  413. data/spec/loader_base_spec.rb +0 -166
  414. data/spec/mapping_spec.rb +0 -117
  415. data/spec/method_dictionary_spec.rb +0 -300
  416. data/spec/method_mapper_spec.rb +0 -100
  417. data/spec/model_mapper_spec.rb +0 -41
  418. data/spec/paperclip_loader_spec.rb +0 -92
  419. data/spec/populator_spec.rb +0 -128
  420. data/spec/thor_spec.rb +0 -90
  421. data/tasks/file_tasks.rake +0 -37
  422. data/tasks/word_to_seedfu.rake +0 -167
@@ -0,0 +1,40 @@
1
+ # Copyright:: (c) Autotelik Media Ltd 2016
2
+ # Author :: Tom Statter
3
+ # Date :: May 2016
4
+ # License:: MIT
5
+ #
6
+ # Details:: Holds the current context and load object related to a failure
7
+ #
8
+ module DataShift
9
+
10
+ class FailureData
11
+
12
+ attr_accessor :load_object
13
+ attr_accessor :node_context
14
+
15
+ attr_accessor :error_messages
16
+
17
+ # The database object, and the inbound context that failed
18
+ def initialize(load_object, node_context, error_messages = [])
19
+ @load_object = load_object
20
+ @node_context = node_context
21
+
22
+ @error_messages = [*error_messages]
23
+
24
+ @error_messages += load_object.errors.full_messages if(load_object)
25
+ @error_messages.uniq!
26
+ end
27
+
28
+ def errors
29
+ error_messages
30
+ end
31
+
32
+ def destroy_failed_object
33
+ if load_object.respond_to?('destroy') && !load_object.new_record?
34
+ load_object.destroy
35
+ reset
36
+ end if load_object
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,16 @@
1
+ # Copyright:: (c) Autotelik Media Ltd 2015
2
+ # Author :: Tom Statter
3
+ # Date :: May 2015
4
+ # License:: MIT
5
+ #
6
+ # Details:: Module for File based loaders
7
+ #
8
+ module DataShift
9
+
10
+ module FileLoader
11
+
12
+ attr_reader :file_name
13
+
14
+ end
15
+
16
+ end
@@ -1,232 +1,143 @@
1
- # Copyright:: (c) Autotelik Media Ltd 2011
1
+ # Copyright:: (c) Autotelik Media Ltd 2015
2
2
  # Author :: Tom Statter
3
- # Date :: Aug 2010
3
+ # Date :: Aug 2015
4
4
  # License:: MIT
5
5
  #
6
6
  # Details:: Base class for loaders, providing a process hook which populates a model,
7
7
  # based on a method map and supplied value from a file - i.e a single column/row's string value.
8
8
  # Note that although a single column, the string can be formatted to contain multiple values.
9
9
  #
10
- # Tightly coupled with MethodMapper classes (in lib/engine) which contains full details of
10
+ # Tightly coupled with Binder classes (in lib/engine) which contains full details of
11
11
  # a file's column and it's correlated AR associations.
12
12
  #
13
- module DataShift
13
+ require 'datashift/binder'
14
+ require 'datashift/querying'
14
15
 
15
- require 'datashift/method_mapper'
16
- require 'datashift/querying'
16
+ module DataShift
17
17
 
18
18
  class LoaderBase
19
19
 
20
20
  include DataShift::Logging
21
21
  include DataShift::Querying
22
22
 
23
- attr_reader :headers
24
-
25
- attr_accessor :method_mapper
26
-
27
- # The inbound row/line number
28
- attr_accessor :current_row_idx
29
-
30
- attr_accessor :load_object_class, :load_object
23
+ attr_accessor :file_name
24
+ attr_accessor :binder
31
25
 
32
- attr_accessor :reporter
33
- attr_accessor :populator
26
+ attr_accessor :doc_context
34
27
 
35
- attr_accessor :config, :verbose
28
+ # Fwd calls onto the DocumentContext
29
+ extend Forwardable
36
30
 
31
+ def_delegators :doc_context,
32
+ :load_object,
33
+ :loaded_count, :failed_count,
34
+ :headers, :reporters, :reporters=
37
35
 
38
- def options() return @config; end
39
-
40
-
41
- # Setup loading
42
- #
43
- # Options to drive building the method dictionary for a class, enabling headers to be mapped to operators on that class.
44
- #
45
- # Options
46
- # :reload : Force load of the method dictionary for object_class even if already loaded
47
- # :instance_methods : Include setter/delegate style instance methods for assignment, as well as AR columns
48
- # :verbose : Verbose logging and to STDOUT
49
- #
50
- def initialize(object_class, object = nil, options = {})
51
- @load_object_class = object_class
52
-
53
- logger.info("Loading objects of type #{@load_object_class} (#{object}")
36
+ attr_reader :configuration
54
37
 
55
- @populator = if(options[:populator].is_a?(String))
56
- ::Object.const_get(options[:populator]).new
57
- elsif(options[:populator].is_a?(Class))
58
- options[:populator].new
59
- else
60
- DataShift::Populator.new
61
- end
38
+ def initialize
39
+ @file_name = ''
62
40
 
63
- # Gather names of all possible 'setter' methods on AR class (instance variables and associations)
64
- if( !MethodDictionary::for?(object_class) || options[:reload] )
65
- #puts "DEBUG Building Method Dictionary for class #{object_class}"
41
+ @doc_context = DocContext.new(Object)
42
+ @binder = Binder.new
66
43
 
67
- meth_dict_opts = options.extract!(:reload, :instance_methods)
68
- DataShift::MethodDictionary.find_operators( @load_object_class, meth_dict_opts)
69
-
70
- # Create dictionary of data on all possible 'setter' methods which can be used to
71
- # populate or integrate an object of type @load_object_class
72
- DataShift::MethodDictionary.build_method_details(@load_object_class)
73
- end
74
-
75
- @method_mapper = DataShift::MethodMapper.new
76
- @config = options.dup # clone can cause issues like 'can't modify frozen hash'
44
+ @configuration = DataShift::Loaders::Configuration.call
45
+ end
77
46
 
78
- @verbose = @config[:verbose]
47
+ def setup_load_class(load_class)
48
+ @doc_context = DocContext.new( MapperUtils.ensure_class(load_class) )
49
+ end
79
50
 
80
- @current_row_idx = 0
51
+ def run(file_name, object_class)
52
+ @file_name = file_name
81
53
 
82
- @headers = []
54
+ setup_load_class(object_class)
83
55
 
84
- @reporter = DataShift::Reporter.new
56
+ logger.info("Loading objects of type #{load_object_class}")
85
57
 
86
- reset(object)
58
+ # no implementation - derived classes must implement
59
+ perform_load
87
60
  end
88
61
 
89
-
90
- # Based on filename call appropriate loading function
91
- # Currently supports :
92
- # Excel/Open Office files saved as .xls
93
- # CSV files
94
- #
95
- # OPTIONS :
96
- #
97
- # [:dummy] : Perform a dummy run - attempt to load everything but then roll back
98
- #
99
- # strict : Raise an exception of any headers can't be mapped to an attribute/association
100
- # ignore : List of column headers to ignore when building operator map
101
- # mandatory : List of columns that must be present in headers
102
- #
103
- # force_inclusion : List of columns that do not map to any operator but should be includeed in processing.
104
- # This provides the opportunity for loaders to provide specific methods to handle these fields
105
- # when no direct operator is available on the model or it's associations
62
+ # Reset the loader, including database object to be populated, and load counts
106
63
  #
107
- def perform_load( file_name, options = {} )
108
-
109
- raise DataShift::BadFile, "Cannot load #{file_name} file not found." unless(File.exists?(file_name))
64
+ def reset(object = nil)
65
+ doc_context.reset(object)
66
+ end
110
67
 
111
- logger.info("Perform Load Options:\n#{options.inspect}")
68
+ def abort_on_failure?
69
+ !! configuration.abort_on_failure
70
+ end
112
71
 
113
- ext = File.extname(file_name)
72
+ def load_object_class
73
+ doc_context.klass
74
+ end
114
75
 
115
- # TODO - make more modular - these methods doing too much, for example move the object creation/reset
116
- # out of these perform... methods to make it easier to over ride that behaviour
117
- if(ext.casecmp('.xls') == 0)
118
- perform_excel_load(file_name, options)
119
- elsif(ext.casecmp('.csv') == 0)
120
- perform_csv_load(file_name, options)
121
- else
122
- raise DataShift::UnsupportedFileType, "#{ext} files not supported - Try .csv or OpenOffice/Excel .xls"
123
- end
76
+ def set_headers(headings)
77
+ logger.info("Setting parsed headers to [#{headings.inspect}]")
78
+ doc_context.headers = headings
124
79
  end
125
80
 
126
81
  def report
127
- @reporter.report
82
+ reporters.each(&:report)
128
83
  end
129
84
 
130
85
  # Core API
131
- #
132
- # Given a list of free text column names from a file,
133
- # map all headers to a MethodDetail instance containing details on operator, look ups etc.
134
- #
135
- # These are available through @method_mapper.method_details
136
- #
137
- # Options:
138
- # [:strict] : Raise an exception of any headers can't be mapped to an attribute/association
139
- # [:ignore] : List of column headers to ignore when building operator map
140
- # [:mandatory] : List of columns that must be present in headers
141
- #
142
- # [:force_inclusion] : List of columns that do not map to any operator but should be includeed in processing.
143
- #
144
- # This provides the opportunity for :
145
- #
146
- # 1) loaders to provide specific methods to handle these fields, when no direct operator
147
- # is available on the model or it's associations
148
86
  #
149
- # 2) Handle delegated methods i.e no direct association but method is on a model throuygh it's delegate
150
- #
87
+ # Returns an instance of DataShift::Binder
88
+ #
89
+ # Given a list of free text column names from inbound headers,
90
+ # map all headers to a domain model containing details on operator, look ups etc.
91
+ #
92
+ # See configuration options
93
+ #
94
+ # [:ignore] : List of column headers to ignore when building operator ma
151
95
  # [:include_all] : Include all headers in processing - takes precedence of :force_inclusion
152
96
  #
153
- def populate_method_mapper_from_headers( headers, options = {} )
154
- @headers = headers
97
+ def bind_headers( headers )
155
98
 
156
- mandatory = options[:mandatory] || []
99
+ logger.info("Binding #{headers.size} inbound headers to #{load_object_class.name}")
157
100
 
158
- strict = (options[:strict] == true)
101
+ @binder ||= DataShift::Binder.new
159
102
 
160
103
  begin
161
- @method_mapper.map_inbound_headers_to_methods( load_object_class, @headers, options )
104
+ binder.map_inbound_headers(load_object_class, headers)
162
105
  rescue => e
163
- puts e.inspect, e.backtrace
164
106
  logger.error("Failed to map header row to set of database operators : #{e.inspect}")
165
- raise MappingDefinitionError, "Failed to map header row to set of database operators"
107
+ logger.error( e.backtrace )
108
+ raise MappingDefinitionError, 'Failed to map header row to set of database operators'
166
109
  end
167
110
 
168
- unless(@method_mapper.missing_methods.empty?)
169
- logger.warn("Following headings couldn't be mapped to #{load_object_class} \n#{@method_mapper.missing_methods.inspect}")
170
- raise MappingDefinitionError, "Missing mappings for columns : #{@method_mapper.missing_methods.join(",")}" if(strict)
171
- end
111
+ unless binder.missing_bindings.empty?
112
+ logger.warn("Following headings couldn't be mapped to #{load_object_class}:")
113
+ binder.missing_bindings.each { |m| logger.warn("Heading [#{m.inbound_name}] - Index (#{m.inbound_index})") }
172
114
 
173
- unless(mandatory.empty? || @method_mapper.contains_mandatory?(mandatory) )
174
- @method_mapper.missing_mandatory(mandatory).each { |er| puts "ERROR: Mandatory column missing - expected column '#{er}'" }
175
- raise MissingMandatoryError, "Mandatory columns missing - please fix and retry."
115
+ raise MappingDefinitionError, "Missing mappings for columns : #{binder.missing_bindings.join(',')}" if configuration.strict
176
116
  end
177
117
 
178
- @method_mapper
179
- end
180
-
118
+ mandatory = DataShift::Mandatory.new(configuration.mandatory)
181
119
 
182
- #TODO - Move code into Populator
183
- # Process columns with a default value specified
184
- def process_defaults()
185
-
186
- @populator.default_values.each do |dname, dv|
187
-
188
- method_detail = MethodDictionary.find_method_detail( load_object_class, dname )
189
-
190
- if(method_detail)
191
- logger.debug "Applying default value [#{dname}] on (#{method_detail.operator})"
192
- @populator.prepare_and_assign(method_detail, load_object, dv)
193
- else
194
- logger.warn "No operator found for default [#{dname}] trying basic assignment"
195
- begin
196
- @populator.insistent_assignment(load_object, dv, dname)
197
- rescue
198
- logger.error "Badly specified default - could not set #{dname}(#{dv})"
199
- end
120
+ unless mandatory.contains_all?(binder)
121
+ mandatory.missing_columns.each do |er|
122
+ logger.error "Mandatory column missing - expected column '#{er}'"
200
123
  end
201
- end
202
- end
203
-
204
- # Core API - Given a single free text column name from a file, search method mapper for
205
- # associated operator on base object class.
206
- #
207
- # If suitable association found, process row data and then assign to current load_object
208
- def find_and_process(column_name, data)
209
-
210
- puts "WARNING: MethodDictionary empty for class #{load_object_class}" unless(MethodDictionary.for?(load_object_class))
211
-
212
- method_detail = MethodDictionary.find_method_detail( load_object_class, column_name )
213
124
 
214
- if(method_detail)
215
- process(method_detail, data)
216
- else
217
- puts "No matching method found for column #{column_name}"
218
- @load_object.errors.add(:base, "No matching method found for column #{column_name}")
125
+ raise MissingMandatoryError, 'Mandatory columns missing - see logs - please fix and retry.'
219
126
  end
127
+
128
+ binder
220
129
  end
221
130
 
131
+ # We can bind inbound 'fields' to associated model columns, from any source, not just headers
132
+ alias bind_fields bind_headers
222
133
 
223
134
  # Any Config under key 'LoaderBase' is merged over existing options - taking precedence.
224
- #
135
+ #
225
136
  # Any Config under a key equal to the full name of the Loader class (e.g DataShift::SpreeEcom::ImageLoader)
226
137
  # is merged over existing options - taking precedence.
227
- #
138
+ #
228
139
  # Format :
229
- #
140
+ #
230
141
  # LoaderClass:
231
142
  # option: value
232
143
  #
@@ -234,260 +145,21 @@ module DataShift
234
145
 
235
146
  logger.info("Reading Datashift loader config from: #{yaml_file.inspect}")
236
147
 
237
- data = YAML::load( ERB.new( IO.read(yaml_file) ).result )
148
+ data = YAML.load( ERB.new( IO.read(yaml_file) ).result )
238
149
 
239
150
  logger.info("Read Datashift config: #{data.inspect}")
240
151
 
241
- if(data['LoaderBase'])
242
- @config.merge!(data['LoaderBase'])
243
- end
244
-
245
- if(data[self.class.name])
246
- @config.merge!(data[self.class.name])
247
- end
248
-
249
- @populator.configure_from(load_object_class, yaml_file)
250
- logger.info("Loader Options : #{@config.inspect}")
251
- end
252
-
253
-
254
- # Return the find_by (where) operator, if specified, otherwise use the heading operator.
255
- # i.e where operator embedded in row ,takes precedence over operator in column heading
256
- #
257
- # Treat rest of the node as the value to use in the where clause e.g
258
- # price:0.99
259
- #
260
- # Column headings will be used, if the row only contains data e.g
261
- # 0.99
262
- #
263
- # We leave it to caller to manage any other aspects or problems in 'rest'
264
- #
265
- def get_operator_and_data(inbound_data)
266
-
267
- where_operator, data = inbound_data.split(Delimiters::name_value_delim)
268
-
269
- md = @populator.current_method_detail
270
-
271
- # Find by operator embedded in row takes precedence over operator in column heading
272
- if((data.nil? || data.empty?) && md.find_by_operator)
273
- if((where_operator.nil? || where_operator.empty?)) #colum completely empty - check for defaults
274
- if(md.find_by_value)
275
- data = md.find_by_value
276
- else
277
- data = Populator::header_default_data(md.operator)
278
- end
279
- else
280
- data = where_operator
281
- end
282
-
283
- # row contains single entry only so take operator from header via method details
284
- where_operator = md.find_by_operator
285
- end
286
-
287
- logger.debug("LoaderBase - get_operator_and_data - [#{where_operator}] - [#{data}]")
288
-
289
- return where_operator, data
290
- end
291
-
292
- # Process a value string from a column.
293
- # Assigning value(s) to correct association on @load_object.
294
- # Method detail represents a column from a file and it's correlated AR associations.
295
- # Value string which may contain multiple values for a collection association.
296
- #
297
- def process(method_detail, value)
298
-
299
- current_method_detail = method_detail
300
-
301
- current_value, current_attribute_hash = @populator.prepare_data(method_detail, value)
302
-
303
- # TODO - Move ALL of this into Populator properly
304
- if(current_method_detail.operator_for(:has_many))
305
-
306
- if(current_method_detail.operator_class && current_value)
307
-
308
- # there are times when we need to save early, for example before assigning to
309
- # has_and_belongs_to associations which require the load_object has an id for the join table
152
+ @config.merge!(data['LoaderBase']) if data['LoaderBase']
310
153
 
311
- save_if_new
154
+ @config.merge!(data[self.class.name]) if data[self.class.name]
312
155
 
313
- # A single column can contain multiple associations delimited by special char
314
- # Size:large|Colour:red,green,blue => ['Size:large', 'Colour:red,green,blue']
315
- columns = current_value.to_s.split( Delimiters::multi_assoc_delim )
156
+ DataShift::Transformation.factory { |f| f.configure_from(load_object_class, yaml_file) }
316
157
 
317
- # Size:large|Colour:red,green,blue =>
318
- # find_by_size( 'large' )
319
- # find_all_by_colour( ['red','green','blue'] )
158
+ ContextFactory.configure(load_object_class, yaml_file)
320
159
 
321
- columns.each do |col_str|
322
-
323
- find_operator, col_values = get_operator_and_data( col_str )
324
-
325
- raise "Cannot perform DB find by #{find_operator}. Expected format key:value" unless(find_operator && col_values)
326
-
327
- find_by_values = col_values.split(Delimiters::multi_value_delim)
328
-
329
- find_by_values << current_method_detail.find_by_value if(current_method_detail.find_by_value)
330
-
331
- found_values = []
332
-
333
- #if(find_by_values.size() == 1)
334
- # logger.info("Find or create #{current_method_detail.operator_class} with #{find_operator} = #{find_by_values.inspect}")
335
- # item = current_method_detail.operator_class.where(find_operator => find_by_values.first).first_or_create
336
- #else
337
- # logger.info("Find #{current_method_detail.operator_class} with #{find_operator} = values #{find_by_values.inspect}")
338
- # current_method_detail.operator_class.where(find_operator => find_by_values).all
339
- #end
340
-
341
- operator_class = current_method_detail.operator_class
342
-
343
- logger.info("Find #{current_method_detail.operator_class} with #{find_operator} = #{find_by_values.inspect}")
344
-
345
- find_by_values.each do |v|
346
- begin
347
- found_values << operator_class.where(find_operator => v).first_or_create
348
- rescue => e
349
- logger.error(e.inspect)
350
- # TODO some way to define if this is a fatal error or not ?
351
- end
352
- end
353
-
354
- logger.info("Scan result #{found_values.inspect}")
355
-
356
- unless(find_by_values.size == found_values.size)
357
- found = found_values.collect {|f| f.send(find_operator) }
358
- @load_object.errors.add( current_method_detail.operator, "Association with key(s) #{(find_by_values - found).inspect} NOT found")
359
- logger.error "Association [#{current_method_detail.operator}] with key(s) #{(find_by_values - found).inspect} NOT found - Not added."
360
- next if(found_values.empty?)
361
- end
362
-
363
- logger.info("Assigning #{found_values.inspect} (#{found_values.class})")
364
-
365
- # Lookup Assoc's Model done, now add the found value(s) to load model's collection
366
- @populator.prepare_and_assign(current_method_detail, @load_object, found_values)
367
- end # END HAS_MANY
368
- end
369
- else
370
- # Nice n simple straight assignment to a column variable
371
- #puts "INFO: LOADER BASE processing #{method_detail.name}"
372
- @populator.assign(load_object)
373
- end
374
- end
375
-
376
-
377
- # Loading failed. Store a failed object and if requested roll back (destroy) the current load object
378
- # For use case where object saved early but subsequent required columns fail to process
379
- # so the load object is invalid
380
-
381
- def failure( object = @load_object, rollback = false)
382
- if(object)
383
- @reporter.add_failed_object(object)
384
-
385
- if(rollback && object.respond_to?('destroy') && !object.new_record?)
386
- klass = object.class
387
- object.destroy
388
- object = klass.new
389
- end
390
- end
391
- end
392
-
393
- def save_and_report
394
- unless(save)
395
- failure
396
- logger.error "Failed to save row (#{current_row_idx}) - [#{@current_row}]"
397
- logger.error load_object.errors.inspect if(load_object)
398
- else
399
- logger.info("Successfully SAVED Object with ID #{load_object.id} for Row #{@current_row}")
400
- @reporter.add_loaded_object(@load_object)
401
- @reporter.success_inbound_count += 1
402
- end
403
- end
404
-
405
- def save
406
- return unless( @load_object )
407
-
408
- puts "DEBUG: SAVING #{@load_object.class} : #{@load_object.inspect}" if(verbose)
409
- begin
410
- return @load_object.save
411
- rescue => e
412
- logger.error( "Save Error : #{e.inspect} on #{@load_object.class}")
413
- logger.error(e.backtrace)
414
- end
415
-
416
- false
417
- end
418
-
419
- # Reset the loader, including database object to be populated, and load counts
420
- #
421
- def reset(object = nil)
422
- @load_object = object || new_load_object
423
- @reporter.reset
424
- end
425
-
426
-
427
- def new_load_object
428
- @load_object = @load_object_class.new
429
- @load_object
430
- end
431
-
432
- def abort_on_failure?
433
- @config[:abort_on_failure].to_s == 'true'
434
- end
435
-
436
- def loaded_count
437
- reporter.loaded_objects.size
438
- end
439
-
440
- def failed_count
441
- reporter.failed_objects.size
442
- end
443
-
444
-
445
- # Check whether headers contains supplied list
446
- def headers_contain_mandatory?( mandatory_list )
447
- [ [*mandatory_list] - @headers].flatten.empty?
448
- end
449
-
450
-
451
- # Check whether headers contains supplied list
452
- def missing_mandatory_headers( mandatory_list )
453
- [ [*mandatory_list] - @headers].flatten
454
- end
455
-
456
- def find_or_new( klass, condition_hash = {} )
457
- @records[klass] = klass.find(:all, :conditions => condition_hash)
458
- if @records[klass].any?
459
- return @records[klass].first
460
- else
461
- return klass.new
462
- end
463
- end
464
-
465
- protected
466
-
467
- # Take current column data and split into each association
468
- # Supported Syntax :
469
- # assoc_find_name:value | assoc2_find_name:value | etc
470
- def get_each_assoc
471
- current_value = @populator.current_value.to_s.split( Delimiters::multi_assoc_delim )
472
- end
473
-
474
- private
475
-
476
- # This method usually called during processing to avoid errors with associations like
477
- # <ActiveRecord::RecordNotSaved: You cannot call create unless the parent is saved>
478
- # If the object is still invalid at this point probably indicates compulsory
479
- # columns on model have not been processed before associations on that model
480
- # TODO smart ordering of columns dynamically ourselves rather than relying on incoming data order
481
- def save_if_new
482
- return unless(load_object.new_record?)
483
-
484
- if(load_object.valid?)
485
- save
486
- else
487
- raise DataShift::SaveError.new("Cannot Save - Invalid #{load_object.class} Record - #{load_object.errors.full_messages}")
488
- end
160
+ logger.info("Loader Options : #{@config.inspect}")
489
161
  end
490
162
 
491
163
  end
492
164
 
493
- end
165
+ end