spreadsheet_architect 2.1.2 → 3.0.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 (243) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +17 -2
  3. data/README.md +149 -129
  4. data/lib/spreadsheet_architect/class_methods/csv.rb +2 -2
  5. data/lib/spreadsheet_architect/class_methods/ods.rb +2 -2
  6. data/lib/spreadsheet_architect/class_methods/xlsx.rb +26 -30
  7. data/lib/spreadsheet_architect/exceptions.rb +32 -30
  8. data/lib/spreadsheet_architect/utils/xlsx.rb +46 -25
  9. data/lib/spreadsheet_architect/utils.rb +116 -121
  10. data/lib/spreadsheet_architect/version.rb +1 -1
  11. data/lib/spreadsheet_architect.rb +5 -1
  12. data/test/dummy_app/app/models/active_model_object.rb +1 -0
  13. data/test/dummy_app/app/models/custom_post.rb +1 -0
  14. data/test/dummy_app/app/models/legacy_plain_ruby_object.rb +14 -0
  15. data/test/dummy_app/app/models/plain_ruby_object.rb +2 -9
  16. data/test/dummy_app/app/models/post.rb +3 -0
  17. data/test/dummy_app/config/routes.rb +4 -5
  18. data/test/dummy_app/db/test.sqlite3 +0 -0
  19. data/test/dummy_app/log/test.log +26911 -21739
  20. data/test/dummy_app/tmp/2.0.1/integration/alt_xlsx.xlsx +0 -0
  21. data/test/dummy_app/tmp/2.0.1/integration/csv.csv +6 -0
  22. data/test/dummy_app/tmp/2.0.1/integration/ods.ods +0 -0
  23. data/test/dummy_app/tmp/2.0.1/integration/xlsx.xlsx +0 -0
  24. data/test/dummy_app/tmp/2.0.1/kitchen_sink.ods +0 -0
  25. data/test/dummy_app/tmp/2.0.1/kitchen_sink.xlsx +0 -0
  26. data/test/dummy_app/tmp/2.0.1/models/ActiveModelObject/data.csv +3 -0
  27. data/test/dummy_app/tmp/2.0.1/models/ActiveModelObject/data.ods +0 -0
  28. data/test/dummy_app/tmp/2.0.1/models/ActiveModelObject/data.xlsx +0 -0
  29. data/test/dummy_app/tmp/{empty_sa.csv → 2.0.1/models/ActiveModelObject/empty.csv} +0 -0
  30. data/test/dummy_app/tmp/{ods/empty_model.ods → 2.0.1/models/ActiveModelObject/empty.ods} +0 -0
  31. data/test/dummy_app/tmp/2.0.1/models/ActiveModelObject/empty.xlsx +0 -0
  32. data/test/dummy_app/tmp/2.0.1/models/ActiveModelObject/instances.csv +6 -0
  33. data/test/dummy_app/tmp/2.0.1/models/ActiveModelObject/instances.ods +0 -0
  34. data/test/dummy_app/tmp/2.0.1/models/ActiveModelObject/instances.xlsx +0 -0
  35. data/test/dummy_app/tmp/2.0.1/models/CustomPost/data.csv +3 -0
  36. data/test/dummy_app/tmp/2.0.1/models/CustomPost/data.ods +0 -0
  37. data/test/dummy_app/tmp/2.0.1/models/CustomPost/data.xlsx +0 -0
  38. data/test/dummy_app/tmp/2.0.1/models/CustomPost/empty.csv +1 -0
  39. data/test/dummy_app/tmp/2.0.1/models/CustomPost/empty.ods +0 -0
  40. data/test/dummy_app/tmp/2.0.1/models/CustomPost/empty.xlsx +0 -0
  41. data/test/dummy_app/tmp/2.0.1/models/CustomPost/instances.csv +6 -0
  42. data/test/dummy_app/tmp/2.0.1/models/CustomPost/instances.ods +0 -0
  43. data/test/dummy_app/tmp/2.0.1/models/CustomPost/instances.xlsx +0 -0
  44. data/test/dummy_app/tmp/2.0.1/models/LegacyPlainRubyObject/data.csv +3 -0
  45. data/test/dummy_app/tmp/2.0.1/models/LegacyPlainRubyObject/data.ods +0 -0
  46. data/test/dummy_app/tmp/2.0.1/models/LegacyPlainRubyObject/data.xlsx +0 -0
  47. data/test/dummy_app/tmp/2.0.1/models/LegacyPlainRubyObject/empty.csv +1 -0
  48. data/test/dummy_app/tmp/2.0.1/models/LegacyPlainRubyObject/empty.ods +0 -0
  49. data/test/dummy_app/tmp/2.0.1/models/LegacyPlainRubyObject/empty.xlsx +0 -0
  50. data/test/dummy_app/tmp/2.0.1/models/LegacyPlainRubyObject/instances.csv +6 -0
  51. data/test/dummy_app/tmp/2.0.1/models/LegacyPlainRubyObject/instances.ods +0 -0
  52. data/test/dummy_app/tmp/2.0.1/models/LegacyPlainRubyObject/instances.xlsx +0 -0
  53. data/test/dummy_app/tmp/2.0.1/models/PlainRubyObject/data.csv +3 -0
  54. data/test/dummy_app/tmp/2.0.1/models/PlainRubyObject/data.ods +0 -0
  55. data/test/dummy_app/tmp/2.0.1/models/PlainRubyObject/data.xlsx +0 -0
  56. data/test/dummy_app/tmp/2.0.1/models/PlainRubyObject/empty.csv +0 -0
  57. data/test/dummy_app/tmp/2.0.1/models/PlainRubyObject/empty.ods +0 -0
  58. data/test/dummy_app/tmp/2.0.1/models/PlainRubyObject/empty.xlsx +0 -0
  59. data/test/dummy_app/tmp/2.0.1/models/PlainRubyObject/instances.csv +6 -0
  60. data/test/dummy_app/tmp/2.0.1/models/PlainRubyObject/instances.ods +0 -0
  61. data/test/dummy_app/tmp/2.0.1/models/PlainRubyObject/instances.xlsx +0 -0
  62. data/test/dummy_app/tmp/2.0.1/models/Post/data.csv +3 -0
  63. data/test/dummy_app/tmp/2.0.1/models/Post/data.ods +0 -0
  64. data/test/dummy_app/tmp/2.0.1/models/Post/data.xlsx +0 -0
  65. data/test/dummy_app/tmp/2.0.1/models/Post/empty.csv +0 -0
  66. data/test/dummy_app/tmp/2.0.1/models/Post/empty.ods +0 -0
  67. data/test/dummy_app/tmp/2.0.1/models/Post/empty.xlsx +0 -0
  68. data/test/dummy_app/tmp/2.0.1/models/SpreadsheetArchitect/data.csv +3 -0
  69. data/test/dummy_app/tmp/2.0.1/models/SpreadsheetArchitect/data.ods +0 -0
  70. data/test/dummy_app/tmp/2.0.1/models/SpreadsheetArchitect/data.xlsx +0 -0
  71. data/test/dummy_app/tmp/2.0.1/models/SpreadsheetArchitect/empty.csv +0 -0
  72. data/test/dummy_app/tmp/2.0.1/models/SpreadsheetArchitect/empty.ods +0 -0
  73. data/test/dummy_app/tmp/2.0.1/models/SpreadsheetArchitect/empty.xlsx +0 -0
  74. data/test/dummy_app/tmp/2.0.1/multi_sheet.ods +0 -0
  75. data/test/dummy_app/tmp/2.0.1/multi_sheet.xlsx +0 -0
  76. data/test/dummy_app/tmp/3.0.0.pre/integration/alt_xlsx.xlsx +0 -0
  77. data/test/dummy_app/tmp/3.0.0.pre/integration/csv.csv +6 -0
  78. data/test/dummy_app/tmp/3.0.0.pre/integration/ods.ods +0 -0
  79. data/test/dummy_app/tmp/3.0.0.pre/integration/xlsx.xlsx +0 -0
  80. data/test/dummy_app/tmp/3.0.0.pre/kitchen_sink.ods +0 -0
  81. data/test/dummy_app/tmp/3.0.0.pre/kitchen_sink.xlsx +0 -0
  82. data/test/dummy_app/tmp/3.0.0.pre/models/ActiveModelObject/data.csv +3 -0
  83. data/test/dummy_app/tmp/3.0.0.pre/models/ActiveModelObject/data.ods +0 -0
  84. data/test/dummy_app/tmp/3.0.0.pre/models/ActiveModelObject/data.xlsx +0 -0
  85. data/test/dummy_app/tmp/3.0.0.pre/models/ActiveModelObject/empty.csv +0 -0
  86. data/test/dummy_app/tmp/3.0.0.pre/models/ActiveModelObject/empty.ods +0 -0
  87. data/test/dummy_app/tmp/3.0.0.pre/models/ActiveModelObject/empty.xlsx +0 -0
  88. data/test/dummy_app/tmp/3.0.0.pre/models/ActiveModelObject/instances.csv +6 -0
  89. data/test/dummy_app/tmp/3.0.0.pre/models/ActiveModelObject/instances.ods +0 -0
  90. data/test/dummy_app/tmp/3.0.0.pre/models/ActiveModelObject/instances.xlsx +0 -0
  91. data/test/dummy_app/tmp/3.0.0.pre/models/CustomPost/data.csv +3 -0
  92. data/test/dummy_app/tmp/3.0.0.pre/models/CustomPost/data.ods +0 -0
  93. data/test/dummy_app/tmp/3.0.0.pre/models/CustomPost/data.xlsx +0 -0
  94. data/test/dummy_app/tmp/3.0.0.pre/models/CustomPost/empty.csv +1 -0
  95. data/test/dummy_app/tmp/3.0.0.pre/models/CustomPost/empty.ods +0 -0
  96. data/test/dummy_app/tmp/3.0.0.pre/models/CustomPost/empty.xlsx +0 -0
  97. data/test/dummy_app/tmp/3.0.0.pre/models/CustomPost/instances.csv +6 -0
  98. data/test/dummy_app/tmp/3.0.0.pre/models/CustomPost/instances.ods +0 -0
  99. data/test/dummy_app/tmp/3.0.0.pre/models/CustomPost/instances.xlsx +0 -0
  100. data/test/dummy_app/tmp/3.0.0.pre/models/LegacyPlainRubyObject/data.csv +3 -0
  101. data/test/dummy_app/tmp/3.0.0.pre/models/LegacyPlainRubyObject/data.ods +0 -0
  102. data/test/dummy_app/tmp/3.0.0.pre/models/LegacyPlainRubyObject/data.xlsx +0 -0
  103. data/test/dummy_app/tmp/3.0.0.pre/models/LegacyPlainRubyObject/empty.csv +0 -0
  104. data/test/dummy_app/tmp/3.0.0.pre/models/LegacyPlainRubyObject/empty.ods +0 -0
  105. data/test/dummy_app/tmp/3.0.0.pre/models/LegacyPlainRubyObject/empty.xlsx +0 -0
  106. data/test/dummy_app/tmp/3.0.0.pre/models/LegacyPlainRubyObject/instances.csv +6 -0
  107. data/test/dummy_app/tmp/3.0.0.pre/models/LegacyPlainRubyObject/instances.ods +0 -0
  108. data/test/dummy_app/tmp/3.0.0.pre/models/LegacyPlainRubyObject/instances.xlsx +0 -0
  109. data/test/dummy_app/tmp/3.0.0.pre/models/PlainRubyObject/data.csv +3 -0
  110. data/test/dummy_app/tmp/3.0.0.pre/models/PlainRubyObject/data.ods +0 -0
  111. data/test/dummy_app/tmp/3.0.0.pre/models/PlainRubyObject/data.xlsx +0 -0
  112. data/test/dummy_app/tmp/3.0.0.pre/models/PlainRubyObject/empty.csv +1 -0
  113. data/test/dummy_app/tmp/3.0.0.pre/models/PlainRubyObject/empty.ods +0 -0
  114. data/test/dummy_app/tmp/3.0.0.pre/models/PlainRubyObject/empty.xlsx +0 -0
  115. data/test/dummy_app/tmp/3.0.0.pre/models/PlainRubyObject/instances.csv +6 -0
  116. data/test/dummy_app/tmp/3.0.0.pre/models/PlainRubyObject/instances.ods +0 -0
  117. data/test/dummy_app/tmp/3.0.0.pre/models/PlainRubyObject/instances.xlsx +0 -0
  118. data/test/dummy_app/tmp/3.0.0.pre/models/Post/data.csv +3 -0
  119. data/test/dummy_app/tmp/3.0.0.pre/models/Post/data.ods +0 -0
  120. data/test/dummy_app/tmp/3.0.0.pre/models/Post/data.xlsx +0 -0
  121. data/test/dummy_app/tmp/3.0.0.pre/models/Post/empty.csv +0 -0
  122. data/test/dummy_app/tmp/3.0.0.pre/models/Post/empty.ods +0 -0
  123. data/test/dummy_app/tmp/3.0.0.pre/models/Post/empty.xlsx +0 -0
  124. data/test/dummy_app/tmp/3.0.0.pre/models/SpreadsheetArchitect/data.csv +3 -0
  125. data/test/dummy_app/tmp/3.0.0.pre/models/SpreadsheetArchitect/data.ods +0 -0
  126. data/test/dummy_app/tmp/3.0.0.pre/models/SpreadsheetArchitect/data.xlsx +0 -0
  127. data/test/dummy_app/tmp/3.0.0.pre/models/SpreadsheetArchitect/empty.csv +0 -0
  128. data/test/dummy_app/tmp/3.0.0.pre/models/SpreadsheetArchitect/empty.ods +0 -0
  129. data/test/dummy_app/tmp/3.0.0.pre/models/SpreadsheetArchitect/empty.xlsx +0 -0
  130. data/test/dummy_app/tmp/3.0.0.pre/multi_sheet.ods +0 -0
  131. data/test/dummy_app/tmp/3.0.0.pre/multi_sheet.xlsx +0 -0
  132. data/test/dummy_app/tmp/axlsx-master/integration/alt_xlsx.xlsx +0 -0
  133. data/test/dummy_app/tmp/axlsx-master/integration/csv.csv +6 -0
  134. data/test/dummy_app/tmp/axlsx-master/integration/ods.ods +0 -0
  135. data/test/dummy_app/tmp/axlsx-master/integration/xlsx.xlsx +0 -0
  136. data/test/dummy_app/tmp/axlsx-master/kitchen_sink.ods +0 -0
  137. data/test/dummy_app/tmp/axlsx-master/kitchen_sink.xlsx +0 -0
  138. data/test/dummy_app/tmp/axlsx-master/models/ActiveModelObject/data.csv +3 -0
  139. data/test/dummy_app/tmp/axlsx-master/models/ActiveModelObject/data.ods +0 -0
  140. data/test/dummy_app/tmp/axlsx-master/models/ActiveModelObject/data.xlsx +0 -0
  141. data/test/dummy_app/tmp/axlsx-master/models/ActiveModelObject/empty.csv +0 -0
  142. data/test/dummy_app/tmp/axlsx-master/models/ActiveModelObject/empty.ods +0 -0
  143. data/test/dummy_app/tmp/axlsx-master/models/ActiveModelObject/empty.xlsx +0 -0
  144. data/test/dummy_app/tmp/axlsx-master/models/ActiveModelObject/instances.csv +6 -0
  145. data/test/dummy_app/tmp/axlsx-master/models/ActiveModelObject/instances.ods +0 -0
  146. data/test/dummy_app/tmp/axlsx-master/models/ActiveModelObject/instances.xlsx +0 -0
  147. data/test/dummy_app/tmp/axlsx-master/models/CustomPost/data.csv +3 -0
  148. data/test/dummy_app/tmp/axlsx-master/models/CustomPost/data.ods +0 -0
  149. data/test/dummy_app/tmp/axlsx-master/models/CustomPost/data.xlsx +0 -0
  150. data/test/dummy_app/tmp/axlsx-master/models/CustomPost/empty.csv +0 -0
  151. data/test/dummy_app/tmp/axlsx-master/models/CustomPost/empty.ods +0 -0
  152. data/test/dummy_app/tmp/axlsx-master/models/CustomPost/empty.xlsx +0 -0
  153. data/test/dummy_app/tmp/axlsx-master/models/CustomPost/instances.csv +6 -0
  154. data/test/dummy_app/tmp/axlsx-master/models/CustomPost/instances.ods +0 -0
  155. data/test/dummy_app/tmp/axlsx-master/models/CustomPost/instances.xlsx +0 -0
  156. data/test/dummy_app/tmp/axlsx-master/models/LegacyPlainRubyObject/data.csv +3 -0
  157. data/test/dummy_app/tmp/axlsx-master/models/LegacyPlainRubyObject/data.ods +0 -0
  158. data/test/dummy_app/tmp/axlsx-master/models/LegacyPlainRubyObject/data.xlsx +0 -0
  159. data/test/dummy_app/tmp/axlsx-master/models/LegacyPlainRubyObject/empty.csv +1 -0
  160. data/test/dummy_app/tmp/axlsx-master/models/LegacyPlainRubyObject/empty.ods +0 -0
  161. data/test/dummy_app/tmp/axlsx-master/models/LegacyPlainRubyObject/empty.xlsx +0 -0
  162. data/test/dummy_app/tmp/axlsx-master/models/LegacyPlainRubyObject/instances.csv +6 -0
  163. data/test/dummy_app/tmp/axlsx-master/models/LegacyPlainRubyObject/instances.ods +0 -0
  164. data/test/dummy_app/tmp/axlsx-master/models/LegacyPlainRubyObject/instances.xlsx +0 -0
  165. data/test/dummy_app/tmp/axlsx-master/models/PlainRubyObject/data.csv +3 -0
  166. data/test/dummy_app/tmp/axlsx-master/models/PlainRubyObject/data.ods +0 -0
  167. data/test/dummy_app/tmp/axlsx-master/models/PlainRubyObject/data.xlsx +0 -0
  168. data/test/dummy_app/tmp/axlsx-master/models/PlainRubyObject/empty.csv +1 -0
  169. data/test/dummy_app/tmp/axlsx-master/models/PlainRubyObject/empty.ods +0 -0
  170. data/test/dummy_app/tmp/axlsx-master/models/PlainRubyObject/empty.xlsx +0 -0
  171. data/test/dummy_app/tmp/axlsx-master/models/PlainRubyObject/instances.csv +6 -0
  172. data/test/dummy_app/tmp/axlsx-master/models/PlainRubyObject/instances.ods +0 -0
  173. data/test/dummy_app/tmp/axlsx-master/models/PlainRubyObject/instances.xlsx +0 -0
  174. data/test/dummy_app/tmp/axlsx-master/models/Post/data.csv +3 -0
  175. data/test/dummy_app/tmp/axlsx-master/models/Post/data.ods +0 -0
  176. data/test/dummy_app/tmp/axlsx-master/models/Post/data.xlsx +0 -0
  177. data/test/dummy_app/tmp/axlsx-master/models/Post/empty.csv +0 -0
  178. data/test/dummy_app/tmp/axlsx-master/models/Post/empty.ods +0 -0
  179. data/test/dummy_app/tmp/axlsx-master/models/Post/empty.xlsx +0 -0
  180. data/test/dummy_app/tmp/axlsx-master/models/SpreadsheetArchitect/data.csv +3 -0
  181. data/test/dummy_app/tmp/axlsx-master/models/SpreadsheetArchitect/data.ods +0 -0
  182. data/test/dummy_app/tmp/axlsx-master/models/SpreadsheetArchitect/data.xlsx +0 -0
  183. data/test/dummy_app/tmp/axlsx-master/models/SpreadsheetArchitect/empty.csv +0 -0
  184. data/test/dummy_app/tmp/axlsx-master/models/SpreadsheetArchitect/empty.ods +0 -0
  185. data/test/dummy_app/tmp/axlsx-master/models/SpreadsheetArchitect/empty.xlsx +0 -0
  186. data/test/dummy_app/tmp/axlsx-master/multi_sheet.ods +0 -0
  187. data/test/dummy_app/tmp/axlsx-master/multi_sheet.xlsx +0 -0
  188. data/test/integration/application_test.rb +8 -11
  189. data/test/models/all_models_test.rb +107 -0
  190. data/test/test_helper.rb +13 -4
  191. data/test/unit/exceptions_test.rb +168 -0
  192. data/test/unit/formats_test.rb +15 -0
  193. data/test/unit/general_test.rb +15 -0
  194. data/test/unit/kitchen_sink_test.rb +87 -0
  195. data/test/unit/multi_sheet_test.rb +29 -0
  196. data/test/unit/utils_test.rb +179 -0
  197. data/test/unit/xlsx_utils_test.rb +143 -0
  198. metadata +409 -116
  199. data/lib/generators/spreadsheet_architect/add_project_defaults_generator.rb +0 -20
  200. data/test/dummy_app/app/models/bad_plain_ruby_object.rb +0 -3
  201. data/test/dummy_app/app/views/reports/sample.html.erb +0 -1
  202. data/test/dummy_app/log/development.log +0 -28
  203. data/test/dummy_app/tmp/active_model_object/csv.csv +0 -21
  204. data/test/dummy_app/tmp/active_model_object/ods.ods +0 -0
  205. data/test/dummy_app/tmp/active_model_object/xlsx.xlsx +0 -0
  206. data/test/dummy_app/tmp/custom_posts/csv.csv +0 -6
  207. data/test/dummy_app/tmp/custom_posts/empty.xlsx +0 -0
  208. data/test/dummy_app/tmp/custom_posts/ods.ods +0 -0
  209. data/test/dummy_app/tmp/custom_posts/xlsx.xlsx +0 -0
  210. data/test/dummy_app/tmp/empty_model.csv +0 -1
  211. data/test/dummy_app/tmp/empty_model.xlsx +0 -0
  212. data/test/dummy_app/tmp/empty_sa.xlsx +0 -0
  213. data/test/dummy_app/tmp/extreme.xlsx +0 -0
  214. data/test/dummy_app/tmp/integration_tests/alt_xlsx.xlsx +0 -0
  215. data/test/dummy_app/tmp/integration_tests/csv.csv +0 -6
  216. data/test/dummy_app/tmp/integration_tests/ods.ods +0 -0
  217. data/test/dummy_app/tmp/integration_tests/xlsx.xlsx +0 -0
  218. data/test/dummy_app/tmp/model.csv +0 -6
  219. data/test/dummy_app/tmp/model.xlsx +0 -0
  220. data/test/dummy_app/tmp/ods/empty_sa.ods +0 -0
  221. data/test/dummy_app/tmp/ods/model.ods +0 -0
  222. data/test/dummy_app/tmp/ods/model_options.ods +0 -0
  223. data/test/dummy_app/tmp/ods/sa.ods +0 -0
  224. data/test/dummy_app/tmp/options.csv +0 -6
  225. data/test/dummy_app/tmp/plain_ruby_object/csv.csv +0 -4
  226. data/test/dummy_app/tmp/plain_ruby_object/ods.ods +0 -0
  227. data/test/dummy_app/tmp/plain_ruby_object/xlsx.xlsx +0 -0
  228. data/test/dummy_app/tmp/posts/csv.csv +0 -6
  229. data/test/dummy_app/tmp/posts/empty.xlsx +0 -0
  230. data/test/dummy_app/tmp/posts/ods.ods +0 -0
  231. data/test/dummy_app/tmp/posts/xlsx.xlsx +0 -0
  232. data/test/dummy_app/tmp/sa.csv +0 -4
  233. data/test/dummy_app/tmp/sa.xlsx +0 -0
  234. data/test/models/active_model_object_test.rb +0 -51
  235. data/test/models/bad_plain_ruby_object_test.rb +0 -28
  236. data/test/models/csv_test.rb +0 -59
  237. data/test/models/custom_post_test.rb +0 -51
  238. data/test/models/ods_test.rb +0 -65
  239. data/test/models/plain_ruby_object_test.rb +0 -51
  240. data/test/models/post_test.rb +0 -44
  241. data/test/models/spreadsheet_architect_utils_test.rb +0 -73
  242. data/test/models/xlsx_test.rb +0 -98
  243. data/test/spreadsheet_architect_test.rb +0 -11
@@ -1,33 +1,30 @@
1
1
  module SpreadsheetArchitect
2
2
  module Exceptions
3
3
 
4
- class NoDataError < StandardError
5
- def initialize
6
- super("Missing :data option")
7
- end
8
- end
9
-
10
- class NoInstancesError < StandardError
11
- def initialize
12
- super("Missing :instances option")
4
+ class InvalidRangeError < StandardError
5
+ def initialize(type, range)
6
+ case type
7
+ when :columns, :rows
8
+ super("Invalid range `#{range}` passed. Some of the #{type} specified were greater than the total number of #{type}")
9
+ when :format
10
+ super("Invalid range `#{range}` passed. Format must be as follows: A1:D4")
11
+ when :type
12
+ super("Invalid range type `#{range}`. Valid types are String and Hash")
13
+ else
14
+ super("Invalid range `#{range}` passed.")
15
+ end
13
16
  end
14
17
  end
15
18
 
16
- class IncorrectTypeError < StandardError
17
- def initialize(option=nil)
18
- super("Incorrect data type for :#{option} option")
19
- end
20
- end
21
-
22
- class SpreadsheetColumnsNotDefinedError < StandardError
23
- def initialize(klass=nil)
24
- super("The spreadsheet_columns option is not defined on #{klass.name}")
19
+ class InvalidTypeError < StandardError
20
+ def initialize(which)
21
+ super("Invalid data type for the #{which}")
25
22
  end
26
23
  end
27
24
 
28
25
  class InvalidColumnError < StandardError
29
- def initialize(range_hash)
30
- super("Invalid Column `#{range_hash}` given for column_types options")
26
+ def initialize(col)
27
+ super("Invalid Column `#{col}` given for column_types options")
31
28
  end
32
29
  end
33
30
 
@@ -37,16 +34,21 @@ module SpreadsheetArchitect
37
34
  end
38
35
  end
39
36
 
40
- class BadRangeError < StandardError
41
- def initialize(type, range)
42
- case type
43
- when :columns, :rows
44
- super("Bad range `#{range}` passed. Some of the #{type} specified were greater than the total number of #{type}")
45
- when :format
46
- super("Bad range `#{range}` passed. Format must be as follows: A1:D4")
47
- when :type
48
- super("Incorrect range type `#{range}`. Valid types are String and Hash")
49
- end
37
+ class NoDataError < StandardError
38
+ def initialize
39
+ super("Missing :data or :instances option")
40
+ end
41
+ end
42
+
43
+ class MultipleDataSourcesError < StandardError
44
+ def initialize
45
+ super("Both :data and :instances options cannot be combined, please choose one.")
46
+ end
47
+ end
48
+
49
+ class SpreadsheetColumnsNotDefinedError < StandardError
50
+ def initialize(klass)
51
+ super("The instance method `spreadsheet_columns` is not defined on #{klass}")
50
52
  end
51
53
  end
52
54
  end
@@ -28,16 +28,12 @@ module SpreadsheetArchitect
28
28
  styles = {} unless styles.is_a?(Hash)
29
29
  styles = self.symbolize_keys(styles)
30
30
 
31
- if styles[:fg_color].nil?
32
- if styles[:color].respond_to?(:sub) && !styles[:color].empty?
33
- styles[:fg_color] = styles.delete(:color).sub('#','')
34
- end
31
+ if styles[:fg_color].nil? && styles[:color] && styles[:color].respond_to?(:sub) && !styles[:color].empty?
32
+ styles[:fg_color] = styles.delete(:color).sub('#','')
35
33
  end
36
34
 
37
- if styles[:bg_color].nil?
38
- if styles[:background_color].respond_to?(:sub) && !styles[:background_color].empty?
39
- styles[:bg_color] = styles.delete(:background_color).sub('#','')
40
- end
35
+ if styles[:bg_color].nil? && styles[:background_color] && styles[:background_color].respond_to?(:sub) && !styles[:background_color].empty?
36
+ styles[:bg_color] = styles.delete(:background_color).sub('#','')
41
37
  end
42
38
 
43
39
  if styles[:alignment].nil? && styles[:align]
@@ -48,26 +44,26 @@ module SpreadsheetArchitect
48
44
  wrap_text: styles[:align][:wrap_text]
49
45
  }
50
46
  else
51
- styles[:alignment] = {horizontal: styles.delete(:align)}
47
+ styles[:alignment] = {horizontal: (styles.delete(:align) || nil) }
52
48
  end
53
49
 
54
50
  styles.delete(:align)
55
51
  end
56
52
 
57
53
  if styles[:b].nil?
58
- styles[:b] = styles.delete(:bold)
54
+ styles[:b] = styles.delete(:bold) || nil
59
55
  end
60
56
 
61
57
  if styles[:sz].nil?
62
- styles[:sz] = styles.delete(:font_size)
58
+ styles[:sz] = styles.delete(:font_size) || nil
63
59
  end
64
60
 
65
61
  if styles[:i].nil?
66
- styles[:i] = styles.delete(:italic)
62
+ styles[:i] = styles.delete(:italic) || nil
67
63
  end
68
64
 
69
65
  if styles[:u].nil?
70
- styles[:u] = styles.delete(:underline)
66
+ styles[:u] = styles.delete(:underline) || nil
71
67
  end
72
68
 
73
69
  ### If `:u` is false instead of nil, it may be incorrectly rendered as true in Excel
@@ -78,16 +74,26 @@ module SpreadsheetArchitect
78
74
  styles.delete_if{|k,v| v.nil?}
79
75
  end
80
76
 
81
- def self.range_hash_to_str(hash, num_columns, num_rows, col_names=Array('A'..'ZZZ'))
77
+ def self.range_hash_to_str(hash, num_columns, num_rows)
82
78
  case hash[:columns]
83
79
  when Integer
84
- start_col = end_col = col_names[hash[:columns]]
80
+ start_col = end_col = COL_NAMES[hash[:columns]]
81
+ when String
82
+ start_col = hash[:columns].first
83
+ end_col = hash[:columns].last
85
84
  when Range
86
- start_col = col_names[hash[:columns].first]
87
- end_col = col_names[hash[:columns].last]
85
+ start_col = hash[:columns].first
86
+ unless start_col.is_a?(String)
87
+ start_col = COL_NAMES[start_col]
88
+ end
89
+
90
+ end_col = hash[:columns].last
91
+ unless end_col.is_a?(String)
92
+ end_col = COL_NAMES[end_col]
93
+ end
88
94
  when :all
89
95
  start_col = 'A'
90
- end_col = col_names[num_columns-1]
96
+ end_col = COL_NAMES[num_columns-1]
91
97
  else
92
98
  raise SpreadsheetArchitect::Exceptions::InvalidRangeStylesOptionError.new(:columns, hash)
93
99
  end
@@ -105,28 +111,40 @@ module SpreadsheetArchitect
105
111
  raise SpreadsheetArchitect::Exceptions::InvalidRangeStylesOptionError.new(:rows, hash)
106
112
  end
107
113
 
108
- return "#{start_col}#{start_row}:#{end_col}#{end_row}"
114
+ range_str = "#{start_col}#{start_row}:#{end_col}#{end_row}"
115
+
116
+ unless hash[:columns] == :all && hash[:rows] == :all
117
+ verify_range(range_str, num_rows)
118
+ end
119
+
120
+ return range_str
109
121
  end
110
122
 
111
- def self.verify_range(range, num_rows, col_names=Array('A'..'ZZZ'))
123
+ def self.verify_range(range, num_rows)
112
124
  if range.is_a?(String)
113
125
  if range.include?(':')
114
126
  front, back = range.split(':')
115
127
  start_col, start_row = front.scan(/\d+|\D+/)
116
128
  end_col, end_row = back.scan(/\d+|\D+/)
117
129
 
118
- unless col_names.include?(start_col) && col_names.include?(end_col)
119
- raise SpreadsheetArchitect::Exceptions::BadRangeError.new(:columns, range)
130
+ unless COL_NAMES.include?(start_col) && COL_NAMES.include?(end_col)
131
+ raise SpreadsheetArchitect::Exceptions::InvalidRangeError.new(:columns, range)
120
132
  end
121
133
 
122
134
  unless start_row.to_i <= num_rows && end_row.to_i <= num_rows
123
- raise SpreadsheetArchitect::Exceptions::BadRangeError.new(:rows, range)
135
+ raise SpreadsheetArchitect::Exceptions::InvalidRangeError.new(:rows, range)
124
136
  end
125
137
  else
126
- raise SpreadsheetArchitect::Exceptions::BadRangeError.new(:format, range)
138
+ raise SpreadsheetArchitect::Exceptions::InvalidRangeError.new(:format, range)
127
139
  end
128
140
  else
129
- raise SpreadsheetArchitect::Exceptions::BadRangeError.new(:type, range)
141
+ raise SpreadsheetArchitect::Exceptions::InvalidRangeError.new(:type, range)
142
+ end
143
+ end
144
+
145
+ def self.verify_column(col, num_columns)
146
+ unless (col.is_a?(String) && COL_NAMES.include?(col)) || (col.is_a?(Integer) && col >= 0 && col < num_columns)
147
+ raise SpreadsheetArchitect::Exceptions::InvalidColumnError.new(col)
130
148
  end
131
149
  end
132
150
 
@@ -144,6 +162,9 @@ module SpreadsheetArchitect
144
162
  new_hash
145
163
  end
146
164
 
165
+ ### Limit of 16384 columns as per Excel limitations
166
+ COL_NAMES = Array('A'..'XFD').freeze
167
+
147
168
  end
148
169
  end
149
170
  end
@@ -1,90 +1,83 @@
1
1
  module SpreadsheetArchitect
2
2
  module Utils
3
- def self.str_humanize(str, capitalize = true)
4
- str = str.sub(/\A_+/, '').gsub(/[_\.]/,' ').sub(' rescue nil','')
5
- if capitalize
6
- str = str.gsub(/(\A|\ )\w/){|x| x.upcase}
3
+ def self.get_cell_data(options, klass)
4
+ if options[:data] && options[:instances]
5
+ raise SpreadsheetArchitect::Exceptions::MultipleDataSourcesError
6
+ elsif options[:data]
7
+ data = options[:data]
7
8
  end
8
- return str
9
- end
10
-
11
- def self.get_cell_data(options={}, klass)
12
- self.check_options_types
13
9
 
14
- if klass.name == 'SpreadsheetArchitect'
15
- if !options[:data]
16
- raise SpreadsheetArchitect::Exceptions::NoDataError
17
- end
18
-
19
- if options[:headers] && options[:headers].is_a?(Array) && !options[:headers].empty?
20
- headers = options[:headers]
21
- else
22
- headers = false
23
- end
24
-
25
- data = options[:data]
10
+ if !options[:data] && options[:headers] == true
11
+ headers = []
12
+ needs_headers = true
13
+ elsif options[:headers].is_a?(Array)
14
+ headers = options[:headers]
26
15
  else
27
- has_custom_columns = options[:spreadsheet_columns] || klass.instance_methods.include?(:spreadsheet_columns)
16
+ headers = false
17
+ end
28
18
 
29
- if !options[:instances] && defined?(ActiveRecord) && klass.ancestors.include?(ActiveRecord::Base)
30
- options[:instances] = klass.where(nil).to_a # triggers the relation call, not sure how this works but it does
31
- end
19
+ if options[:column_types]
20
+ column_types = options[:column_types]
21
+ else
22
+ column_types = []
23
+ needs_column_types = true
24
+ end
32
25
 
26
+ if !data
33
27
  if !options[:instances]
34
- raise SpreadsheetArchitect::Exceptions::NoInstancesError
35
- end
36
-
37
- if options[:headers].nil?
38
- headers = klass::SPREADSHEET_OPTIONS[:headers] if defined?(klass::SPREADSHEET_OPTIONS)
39
- headers ||= SpreadsheetArchitect.default_options[:headers]
40
- elsif options[:headers].is_a?(Array)
41
- headers = options[:headers]
42
- end
43
-
44
- if headers == false || headers.is_a?(Array)
45
- needs_headers = false
46
- else
47
- headers = true
48
- needs_headers = true
28
+ if is_ar_model?(klass)
29
+ options[:instances] = klass.where(nil).to_a # triggers the relation call, not sure how this works but it does
30
+ else
31
+ raise SpreadsheetArchitect::Exceptions::NoDataError
32
+ end
49
33
  end
50
34
 
51
- if has_custom_columns
52
- headers = [] if needs_headers
53
- columns = []
54
- array = options[:spreadsheet_columns] || (options[:instances].empty? ? [] : options[:instances].first.spreadsheet_columns)
55
- array.each_with_index do |x,i|
56
- if x.is_a?(Array)
57
- headers.push(x[0].to_s) if needs_headers
58
- columns.push x[1]
59
- else
60
- headers.push(str_humanize(x.to_s)) if needs_headers
61
- columns.push x
62
- end
35
+ if !options[:spreadsheet_columns] && klass != SpreadsheetArchitect && !klass.instance_methods.include?(:spreadsheet_columns)
36
+ if is_ar_model?(klass)
37
+ the_column_names = klass.column_names
38
+ headers = the_column_names.map{|x| str_titleize(x)} if needs_headers
39
+ columns = the_column_names.map{|x| x.to_sym}
40
+ else
41
+ raise SpreadsheetArchitect::Exceptions::SpreadsheetColumnsNotDefinedError.new(klass)
63
42
  end
64
- elsif !has_custom_columns && defined?(ActiveRecord) && klass.ancestors.include?(ActiveRecord::Base)
65
- ignored_columns = ["id","created_at","updated_at","deleted_at"]
66
- the_column_names = (klass.column_names - ignored_columns)
67
- headers = the_column_names.map{|x| str_humanize(x)} if needs_headers
68
- columns = the_column_names.map{|x| x.to_sym}
69
- else
70
- raise SpreadsheetArchitect::Exceptions::SpreadsheetColumnsNotDefinedError, klass
71
43
  end
72
44
 
73
45
  data = []
74
46
  options[:instances].each do |instance|
75
- if has_custom_columns && !options[:spreadsheet_columns]
47
+ if columns
48
+ data.push columns.map{|col| col.is_a?(Symbol) ? instance.instance_eval(col.to_s) : col}
49
+ else
76
50
  row_data = []
77
- instance.spreadsheet_columns.each do |x|
51
+
52
+ if !options[:spreadsheet_columns]
53
+ if klass == SpreadsheetArchitect && !instance.respond_to?(:spreadsheet_columns)
54
+ raise SpreadsheetArchitect::Exceptions::SpreadsheetColumnsNotDefinedError.new(instance.class)
55
+ else
56
+ instance_cols = instance.spreadsheet_columns
57
+ end
58
+ else
59
+ instance_cols = options[:spreadsheet_columns].call(instance)
60
+ end
61
+
62
+ instance_cols.each_with_index do |x,i|
78
63
  if x.is_a?(Array)
64
+ headers.push(x[0].to_s) if needs_headers
79
65
  row_data.push(x[1].is_a?(Symbol) ? instance.instance_eval(x[1].to_s) : x[1])
66
+ if needs_column_types
67
+ column_types[i] = x[2]
68
+ end
80
69
  else
70
+ headers.push(str_titleize(x.to_s)) if needs_headers
81
71
  row_data.push(x.is_a?(Symbol) ? instance.instance_eval(x.to_s) : x)
82
72
  end
83
73
  end
74
+
84
75
  data.push row_data
85
- else
86
- data.push columns.map{|col| col.is_a?(Symbol) ? instance.instance_eval(col.to_s) : col}
76
+
77
+ needs_headers = false
78
+ needs_column_types = false
87
79
  end
80
+
88
81
  end
89
82
  end
90
83
 
@@ -92,54 +85,50 @@ module SpreadsheetArchitect
92
85
  headers = [headers]
93
86
  end
94
87
 
95
- return options.merge(headers: headers, data: data, column_types: options[:column_types])
88
+ if column_types.compact.empty?
89
+ column_types = nil
90
+ end
91
+
92
+ return options.merge(headers: headers, data: data, column_types: column_types)
96
93
  end
97
94
 
98
- def self.get_options(options={}, klass)
99
- if options[:headers]
100
- if defined?(klass::SPREADSHEET_OPTIONS)
101
- header_style = deep_clone(SpreadsheetArchitect.default_options[:header_style]).merge(klass::SPREADSHEET_OPTIONS[:header_style] || {})
95
+ def self.get_options(options, klass)
96
+ verify_option_types(options)
97
+
98
+ if defined?(klass::SPREADSHEET_OPTIONS)
99
+ if klass::SPREADSHEET_OPTIONS.is_a?(Hash)
100
+ options = SpreadsheetArchitect.default_options.merge(
101
+ klass::SPREADSHEET_OPTIONS.merge(options)
102
+ )
102
103
  else
103
- header_style = deep_clone(SpreadsheetArchitect.default_options[:header_style])
104
- end
105
-
106
- if options[:header_style]
107
- header_style.merge!(options[:header_style])
108
- elsif options[:header_style] == false
109
- header_style = false
104
+ raise SpreadsheetArchitect::Exceptions::InvalidTypeError.new("#{klass}::SPREADSHEET_OPTIONS constant")
110
105
  end
111
106
  else
112
- header_style = false
107
+ options = SpreadsheetArchitect.default_options.merge(options)
113
108
  end
114
109
 
115
- if options[:row_style] == false
116
- row_style = false
117
- else
118
- if defined?(klass::SPREADSHEET_OPTIONS)
119
- row_style = deep_clone(SpreadsheetArchitect.default_options[:row_style]).merge(klass::SPREADSHEET_OPTIONS[:row_style] || {})
110
+ if !options[:headers]
111
+ options[:header_style] = false
112
+ end
113
+
114
+ if !options[:sheet_name]
115
+ if klass == SpreadsheetArchitect
116
+ options[:sheet_name] = 'Sheet1'
120
117
  else
121
- row_style = deep_clone(SpreadsheetArchitect.default_options[:row_style])
122
- end
118
+ options[:sheet_name] = klass.name
123
119
 
124
- if options[:row_style]
125
- row_style.merge!(options[:row_style])
120
+ if options[:sheet_name].respond_to?(:pluralize)
121
+ options[:sheet_name] = options[:sheet_name].pluralize
122
+ end
126
123
  end
127
124
  end
128
125
 
129
- if defined?(klass::SPREADSHEET_OPTIONS)
130
- sheet_name = options[:sheet_name] || klass::SPREADSHEET_OPTIONS[:sheet_name] || SpreadsheetArchitect.default_options[:sheet_name]
131
- else
132
- sheet_name = options[:sheet_name] || SpreadsheetArchitect.default_options[:sheet_name]
133
- end
134
-
135
- sheet_name ||= (klass.name == 'SpreadsheetArchitect' ? 'Sheet1' : klass.name)
136
-
137
- return options.merge(header_style: header_style, row_style: row_style, sheet_name: sheet_name)
126
+ return options
138
127
  end
139
128
 
140
129
  def self.convert_styles_to_ods(styles={})
141
130
  styles = {} unless styles.is_a?(Hash)
142
- styles = self.stringify_keys(styles)
131
+ styles = stringify_keys(styles)
143
132
 
144
133
  property_styles = {}
145
134
 
@@ -175,48 +164,54 @@ module SpreadsheetArchitect
175
164
 
176
165
  private
177
166
 
178
- def self.deep_clone(x)
179
- Marshal.load(Marshal.dump(x))
167
+ def self.is_ar_model?(klass)
168
+ defined?(ActiveRecord) && klass.ancestors.include?(ActiveRecord::Base)
180
169
  end
181
170
 
182
- def self.check_type(options, option_name, type)
183
- unless options[option_name].nil?
184
- valid = false
171
+ def self.str_titleize(str)
172
+ str = str.sub(/\A_+/, '')
173
+ .gsub(/[_\.]/,' ')
174
+ .sub(' rescue nil','')
175
+ .gsub(/(\A|\ )\w/){|x| x.upcase}
185
176
 
177
+ return str
178
+ end
179
+
180
+ def self.check_option_type(options, option_name, type)
181
+ val = options[option_name]
182
+
183
+ if val
186
184
  if type.is_a?(Array)
187
- valid = type.any?{|t| options[option_name].is_a?(t)}
188
- elsif options[option_name].is_a?(type)
189
- valid = true
185
+ invalid = type.all?{|t| !val.is_a?(t) }
186
+ elsif !val.is_a?(type)
187
+ invalid = true
190
188
  end
191
189
 
192
- if valid
193
- raise SpreadsheetArchitect::Exceptions::IncorrectTypeError option_name
190
+ if invalid
191
+ raise SpreadsheetArchitect::Exceptions::InvalidTypeError.new(":#{option_name} option")
194
192
  end
195
193
  end
196
194
  end
197
195
 
198
- def self.check_options_types(options={})
199
- self.check_type(options, :spreadsheet_columns, Array)
200
- self.check_type(options, :instances, Array)
201
- self.check_type(options, :headers, [TrueClass, FalseClass, Array])
202
- self.check_type(options, :sheet_name, String)
203
- self.check_type(options, :header_style, Hash)
204
- self.check_type(options, :row_style, Hash)
205
- self.check_type(options, :column_styles, Array)
206
- self.check_type(options, :range_styles, Array)
207
- self.check_type(options, :merges, Array)
208
- self.check_type(options, :borders, Array)
209
- self.check_type(options, :column_widths, Array)
196
+ def self.verify_option_types(options)
197
+ check_option_type(options, :spreadsheet_columns, Proc)
198
+ check_option_type(options, :data, Array)
199
+ check_option_type(options, :instances, Array)
200
+ check_option_type(options, :headers, [TrueClass, Array])
201
+ check_option_type(options, :header_style, Hash)
202
+ check_option_type(options, :row_style, Hash)
203
+ check_option_type(options, :column_styles, Array)
204
+ check_option_type(options, :range_styles, Array)
205
+ check_option_type(options, :merges, Array)
206
+ check_option_type(options, :borders, Array)
207
+ check_option_type(options, :column_widths, Array)
210
208
  end
211
209
 
212
- # only converts the first 2 levels
213
210
  def self.stringify_keys(hash={})
214
211
  new_hash = {}
215
212
  hash.each do |k,v|
216
213
  if v.is_a?(Hash)
217
- v.each do |k2,v2|
218
- new_hash[k2.to_s] = v2
219
- end
214
+ new_hash[k.to_s] = self.stringify_keys(v)
220
215
  else
221
216
  new_hash[k.to_s] = v
222
217
  end
@@ -1,3 +1,3 @@
1
1
  module SpreadsheetArchitect
2
- VERSION = "2.1.2"
2
+ VERSION = "3.0.0"
3
3
  end
@@ -23,7 +23,11 @@ module SpreadsheetArchitect
23
23
  }
24
24
 
25
25
  def self.default_options=(val)
26
- @default_options = val
26
+ if val.is_a?(Hash)
27
+ @default_options = val
28
+ else
29
+ raise SpreadsheetArchitect::Exceptions::InvalidTypeError.new("SpreadsheetArchitect.default_options")
30
+ end
27
31
  end
28
32
 
29
33
  def self.default_options
@@ -11,4 +11,5 @@ class ActiveModelObject
11
11
  :created_at
12
12
  ]
13
13
  end
14
+
14
15
  end
@@ -24,4 +24,5 @@ class CustomPost < ActiveRecord::Base
24
24
  borders: [],
25
25
  column_types: []
26
26
  }
27
+
27
28
  end
@@ -0,0 +1,14 @@
1
+ class LegacyPlainRubyObject
2
+ include SpreadsheetArchitect ### old syntax, not in readme anymore, should easily remain compatible though
3
+
4
+ attr_accessor :name, :content, :created_at
5
+
6
+ def spreadsheet_columns
7
+ [
8
+ ['Name', :name],
9
+ :content,
10
+ ['Object ID', (object_id)]
11
+ ]
12
+ end
13
+
14
+ end
@@ -1,19 +1,12 @@
1
1
  class PlainRubyObject
2
- include SpreadsheetArchitect
2
+ attr_accessor :name, :content, :created_at
3
3
 
4
4
  def spreadsheet_columns
5
5
  [
6
- ['Title', :title],
6
+ ['Name', :name],
7
7
  :content,
8
8
  ['Object ID', (object_id)]
9
9
  ]
10
10
  end
11
11
 
12
- def title
13
- 'My Title'
14
- end
15
-
16
- def content
17
- 'The content...'
18
- end
19
12
  end
@@ -1,3 +1,6 @@
1
1
  class Post < ActiveRecord::Base
2
2
  include SpreadsheetArchitect
3
+
4
+ attr_accessor :name, :content, :created_at
5
+
3
6
  end
@@ -1,7 +1,6 @@
1
1
  Dummy::Application.routes.draw do
2
- get 'spreadsheet/csv', to: 'spreadsheets#csv'
3
- get 'spreadsheet/ods', to: 'spreadsheets#ods'
4
- get 'spreadsheet/xlsx', to: 'spreadsheets#xlsx'
5
- get 'spreadsheet/alt_xlsx', to: 'spreadsheets#alt_xlsx'
6
- get 'test', to: 'spreadsheets#test_xlsx'
2
+ get 'spreadsheets/csv', to: 'spreadsheets#csv'
3
+ get 'spreadsheets/ods', to: 'spreadsheets#ods'
4
+ get 'spreadsheets/xlsx', to: 'spreadsheets#xlsx'
5
+ get 'spreadsheets/alt_xlsx', to: 'spreadsheets#alt_xlsx'
7
6
  end
Binary file