voruby 1.1.1 → 2.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 (257) hide show
  1. data/Rakefile.rb +107 -224
  2. data/lib/misc.rb +1 -0
  3. data/lib/misc/misc.rb +60 -0
  4. data/lib/misc/propertyfile.rb +31 -0
  5. data/lib/symphony.rb +1 -0
  6. data/lib/symphony/symphony.rb +247 -0
  7. data/lib/voruby.rb +186 -0
  8. data/lib/voruby/active_votable/active_votable.rb +468 -347
  9. data/lib/voruby/adql/1.0/adql.rb +2418 -0
  10. data/lib/voruby/adql/support.rb +2 -0
  11. data/lib/voruby/misc.rb +351 -0
  12. data/lib/voruby/misc/connection_monitor.rb +97 -0
  13. data/lib/voruby/misc/libxml_ext.rb +121 -0
  14. data/lib/voruby/misc/rexml_ext.rb +223 -0
  15. data/lib/voruby/resolver/resolver.rb +12 -0
  16. data/lib/voruby/resolver/sesame.rb +299 -0
  17. data/lib/voruby/sky_query/sky_query.rb +192 -0
  18. data/lib/voruby/stc/1.10/coords.rb +2272 -0
  19. data/lib/voruby/stc/1.10/region.rb +892 -0
  20. data/lib/voruby/stc/1.10/stc.rb +3271 -0
  21. data/lib/voruby/stc/1.30/stc.rb +8666 -0
  22. data/lib/voruby/stc/support.rb +2 -0
  23. data/lib/voruby/ucd/ucd.rb +173 -0
  24. data/lib/voruby/voevent/1.1/voevent.rb +1124 -0
  25. data/lib/voruby/voevent/support.rb +5 -0
  26. data/lib/voruby/votable/1.0/votable.rb +1807 -0
  27. data/lib/voruby/votable/1.1/votable.rb +2100 -0
  28. data/lib/voruby/votable/votable.rb +305 -0
  29. data/lib/voruby/wesix/wesix.rb +491 -0
  30. data/lib/voruby/xlink/1.2/xlink.rb +21 -0
  31. data/test/voruby/active_votable/complex.vot +60 -0
  32. data/test/voruby/active_votable/error.vot +6 -0
  33. data/test/voruby/active_votable/large.vot +130040 -0
  34. data/test/voruby/active_votable/simple1.vot +38 -0
  35. data/test/voruby/active_votable/simple2.vot +38 -0
  36. data/test/voruby/active_votable/test.rb +193 -0
  37. data/test/voruby/adql/1.0/adql-alias.sql +1 -0
  38. data/test/voruby/adql/1.0/adql-alias.xml +26 -0
  39. data/test/voruby/adql/1.0/adql-avg.sql +1 -0
  40. data/test/voruby/adql/1.0/adql-avg.xml +31 -0
  41. data/test/voruby/adql/1.0/adql-circle.sql +1 -0
  42. data/test/voruby/adql/1.0/adql-circle.xml +46 -0
  43. data/test/voruby/adql/1.0/adql-expr.sql +1 -0
  44. data/test/voruby/adql/1.0/adql-expr.xml +34 -0
  45. data/test/voruby/adql/1.0/adql-function.sql +1 -0
  46. data/test/voruby/adql/1.0/adql-function.xml +41 -0
  47. data/test/voruby/adql/1.0/adql-group.sql +1 -0
  48. data/test/voruby/adql/1.0/adql-group.xml +51 -0
  49. data/test/voruby/adql/1.0/adql-having.sql +1 -0
  50. data/test/voruby/adql/1.0/adql-having.xml +25 -0
  51. data/test/voruby/adql/1.0/adql-like.sql +1 -0
  52. data/test/voruby/adql/1.0/adql-like.xml +17 -0
  53. data/test/voruby/adql/1.0/adql-order.sql +1 -0
  54. data/test/voruby/adql/1.0/adql-order.xml +37 -0
  55. data/test/voruby/adql/1.0/adql-simple.sql +1 -0
  56. data/test/voruby/adql/1.0/adql-simple.xml +12 -0
  57. data/test/voruby/adql/1.0/adql-top.sql +1 -0
  58. data/test/voruby/adql/1.0/adql-top.xml +33 -0
  59. data/test/voruby/adql/1.0/test.rb +2220 -0
  60. data/test/voruby/misc/test.rb +32 -0
  61. data/test/voruby/resolver/sesame/test.rb +56 -0
  62. data/test/voruby/sky_query/test.rb +107 -0
  63. data/test/voruby/stc/1.10/coords_test.rb +3704 -0
  64. data/test/voruby/stc/1.10/region_test.rb +993 -0
  65. data/test/voruby/stc/1.10/stc-catalog-entry-location.xml +112 -0
  66. data/test/voruby/stc/1.10/stc-obs-data-location.xml +126 -0
  67. data/test/voruby/stc/1.10/stc-region-circle.xml +5 -0
  68. data/test/voruby/stc/1.10/stc-region-convex.xml +11 -0
  69. data/test/voruby/stc/1.10/stc-region-convexhull.xml +5 -0
  70. data/test/voruby/stc/1.10/stc-region-ellipse.xml +7 -0
  71. data/test/voruby/stc/1.10/stc-region-intersection.xml +25 -0
  72. data/test/voruby/stc/1.10/stc-region-negation.xml +7 -0
  73. data/test/voruby/stc/1.10/stc-region-polygon.xml +13 -0
  74. data/test/voruby/stc/1.10/stc-region-sector.xml +6 -0
  75. data/test/voruby/stc/1.10/stc-region-union.xml +25 -0
  76. data/test/voruby/stc/1.10/stc-resource-profile.xml +60 -0
  77. data/test/voruby/stc/1.10/stc-search-location.xml +54 -0
  78. data/test/voruby/stc/1.10/stc_test.rb +4626 -0
  79. data/test/voruby/stc/1.30/stc-catalog-entry-location.xml +210 -0
  80. data/test/voruby/stc/1.30/stc-obs-data-location-arecibo.xml +353 -0
  81. data/test/voruby/stc/1.30/stc-obs-data-location-fits.xml +250 -0
  82. data/test/voruby/stc/1.30/stc-obs-data-location-xlink.xml +63 -0
  83. data/test/voruby/stc/1.30/stc-obs-data-location.xml +216 -0
  84. data/test/voruby/stc/1.30/stc-resource-profile-unusual-ref-pos.xml +39 -0
  85. data/test/voruby/stc/1.30/stc-resource-profile.xml +129 -0
  86. data/test/voruby/stc/1.30/stc-search-location-arecibo.xml +86 -0
  87. data/test/voruby/stc/1.30/stc-search-location.xml +101 -0
  88. data/test/voruby/stc/1.30/test.rb +6274 -0
  89. data/test/voruby/ucd/test.rb +48 -0
  90. data/test/voruby/voevent/1.1/test.rb +812 -0
  91. data/test/{voevent/voevent_v1_1.xml → voruby/voevent/1.1/voevent.xml} +2 -2
  92. data/test/voruby/voregistry/0.3/test.rb +137 -0
  93. data/test/voruby/votable/1.0/test.rb +714 -0
  94. data/test/voruby/votable/1.0/votable.basic.xml +660 -0
  95. data/test/voruby/votable/1.0/votable.html +86 -0
  96. data/test/voruby/votable/1.0/votable.ns.xml +56 -0
  97. data/test/voruby/votable/1.1/test.rb +785 -0
  98. data/test/voruby/votable/1.1/votable.basic.xml +38 -0
  99. data/test/voruby/votable/1.1/votable.html +86 -0
  100. data/test/voruby/votable/1.1/votable.ns.xml +56 -0
  101. data/test/voruby/votable/test.rb +15 -0
  102. data/test/voruby/wesix/test.rb +268 -0
  103. data/test/voruby/wesix/testr.fits +28 -0
  104. metadata +234 -247
  105. data/REQUIREMENTS +0 -6
  106. data/lib/voruby/active_votable/loader.rb +0 -5
  107. data/lib/voruby/adql/adql.rb +0 -2787
  108. data/lib/voruby/adql/ext.rb +0 -14
  109. data/lib/voruby/adql/loader.rb +0 -6
  110. data/lib/voruby/adql/operations.rb +0 -54
  111. data/lib/voruby/adql/parser.rb +0 -160
  112. data/lib/voruby/adql/transforms.rb +0 -573
  113. data/lib/voruby/ext.rb +0 -17
  114. data/lib/voruby/loader.rb +0 -4
  115. data/lib/voruby/misc/propertyfile.rb +0 -36
  116. data/lib/voruby/plastic/applications.rb +0 -174
  117. data/lib/voruby/plastic/constants.rb +0 -30
  118. data/lib/voruby/plastic/loader.rb +0 -10
  119. data/lib/voruby/plastic/plastic.rb +0 -1
  120. data/lib/voruby/resources/conesearch/conesearch.rb +0 -9
  121. data/lib/voruby/resources/conesearch/conesearch_v0_2.rb +0 -55
  122. data/lib/voruby/resources/conesearch/conesearch_v0_3.rb +0 -50
  123. data/lib/voruby/resources/conesearch/conesearch_v1_0.rb +0 -72
  124. data/lib/voruby/resources/conesearch/loader.rb +0 -4
  125. data/lib/voruby/resources/loader.rb +0 -50
  126. data/lib/voruby/resources/nodes.rb +0 -190
  127. data/lib/voruby/resources/openskynode/loader.rb +0 -4
  128. data/lib/voruby/resources/openskynode/openskynode.rb +0 -9
  129. data/lib/voruby/resources/openskynode/openskynode_v0_1.rb +0 -54
  130. data/lib/voruby/resources/sia/loader.rb +0 -5
  131. data/lib/voruby/resources/sia/sia.rb +0 -9
  132. data/lib/voruby/resources/sia/sia_v0_6.rb +0 -90
  133. data/lib/voruby/resources/sia/sia_v0_7.rb +0 -89
  134. data/lib/voruby/resources/sia/sia_v1_0.rb +0 -122
  135. data/lib/voruby/resources/stsci.rb +0 -59
  136. data/lib/voruby/resources/vodataservice/coverage_v0_2.rb +0 -195
  137. data/lib/voruby/resources/vodataservice/coverage_v0_3.rb +0 -158
  138. data/lib/voruby/resources/vodataservice/loader.rb +0 -5
  139. data/lib/voruby/resources/vodataservice/vodataservice.rb +0 -9
  140. data/lib/voruby/resources/vodataservice/vodataservice_v0_4.rb +0 -189
  141. data/lib/voruby/resources/vodataservice/vodataservice_v0_5.rb +0 -163
  142. data/lib/voruby/resources/vodataservice/vodataservice_v1_0.rb +0 -221
  143. data/lib/voruby/resources/voregistry/loader.rb +0 -4
  144. data/lib/voruby/resources/voregistry/voregistry.rb +0 -9
  145. data/lib/voruby/resources/voregistry/voregistry_v0_2.rb +0 -40
  146. data/lib/voruby/resources/voregistry/voregistry_v0_3.rb +0 -30
  147. data/lib/voruby/resources/voregistry/voregistry_v1_0.rb +0 -86
  148. data/lib/voruby/resources/voresource/loader.rb +0 -17
  149. data/lib/voruby/resources/voresource/voresource.rb +0 -9
  150. data/lib/voruby/resources/voresource/voresource_v0_10.rb +0 -327
  151. data/lib/voruby/resources/voresource/voresource_v0_9.rb +0 -405
  152. data/lib/voruby/resources/voresource/voresource_v1_0.rb +0 -230
  153. data/lib/voruby/services/ext.rb +0 -11
  154. data/lib/voruby/services/gestalt/footprint.rb +0 -95
  155. data/lib/voruby/services/gestalt/wcs_fixer.rb +0 -105
  156. data/lib/voruby/services/gestalt/wesix.rb +0 -155
  157. data/lib/voruby/services/loader.rb +0 -7
  158. data/lib/voruby/services/registry/registry.rb +0 -53
  159. data/lib/voruby/services/resolver/resolver.rb +0 -35
  160. data/lib/voruby/services/schema/schema.rb +0 -644
  161. data/lib/voruby/sesame/loader.rb +0 -6
  162. data/lib/voruby/sesame/sesame_v1_0.rb +0 -64
  163. data/lib/voruby/simple/loader.rb +0 -6
  164. data/lib/voruby/simple/parameters.rb +0 -196
  165. data/lib/voruby/simple/sap.rb +0 -446
  166. data/lib/voruby/spacetime/loader.rb +0 -3
  167. data/lib/voruby/spacetime/spacetime.rb +0 -607
  168. data/lib/voruby/stc/coords_v1_20.rb +0 -900
  169. data/lib/voruby/stc/loader.rb +0 -55
  170. data/lib/voruby/stc/region_v1_20.rb +0 -274
  171. data/lib/voruby/stc/stc_v1_20.rb +0 -1196
  172. data/lib/voruby/util.rb +0 -27
  173. data/lib/voruby/voevent/loader.rb +0 -7
  174. data/lib/voruby/voevent/voevent_v1_0.rb +0 -213
  175. data/lib/voruby/voevent/voevent_v1_1.rb +0 -196
  176. data/lib/voruby/votables/chandra.rb +0 -373
  177. data/lib/voruby/votables/data.rb +0 -179
  178. data/lib/voruby/votables/galex.rb +0 -377
  179. data/lib/voruby/votables/int.rb +0 -354
  180. data/lib/voruby/votables/libxml_parser.rb +0 -411
  181. data/lib/voruby/votables/libxml_votable.rb +0 -67
  182. data/lib/voruby/votables/loader.rb +0 -10
  183. data/lib/voruby/votables/meta.rb +0 -763
  184. data/lib/voruby/votables/misc.rb +0 -51
  185. data/lib/voruby/votables/nsa.rb +0 -410
  186. data/lib/voruby/votables/rexml_parser.rb +0 -408
  187. data/lib/voruby/votables/rexml_votable.rb +0 -67
  188. data/lib/voruby/votables/sdss.rb +0 -356
  189. data/lib/voruby/votables/transforms.rb +0 -388
  190. data/lib/voruby/votables/tree.rb +0 -45
  191. data/lib/voruby/votables/types.rb +0 -391
  192. data/lib/voruby/votables/votable.rb +0 -687
  193. data/test/active_votable/database.yml +0 -6
  194. data/test/active_votable/test.vot +0 -168492
  195. data/test/active_votable/unittest.rb +0 -41
  196. data/test/adql/test1.adql +0 -49
  197. data/test/adql/test2.adql +0 -51
  198. data/test/adql/test3.adql +0 -81
  199. data/test/adql/test4.adql +0 -53
  200. data/test/adql/test5.adql +0 -55
  201. data/test/adql/test6.adql +0 -18
  202. data/test/adql/test7.adql +0 -48
  203. data/test/adql/unittest.rb +0 -1672
  204. data/test/plastic/test.rb +0 -44
  205. data/test/plastic/test.vot +0 -5385
  206. data/test/plastic/unittest.rb +0 -66
  207. data/test/resources/conesearch/conesearch_v0_3.xml +0 -31
  208. data/test/resources/conesearch/conesearch_v1_0.xml +0 -86
  209. data/test/resources/conesearch/unittest_v0_3.rb +0 -22
  210. data/test/resources/conesearch/unittest_v1_0.rb +0 -24
  211. data/test/resources/openskynode/open_sky_node_v0_1.xml +0 -32
  212. data/test/resources/openskynode/unittest_v0_1.rb +0 -31
  213. data/test/resources/sia/simple_image_access_v0_7.xml +0 -36
  214. data/test/resources/sia/simple_image_access_v1_0.xml +0 -122
  215. data/test/resources/sia/unittest_v0_7.rb +0 -24
  216. data/test/resources/sia/unittest_v1_0.rb +0 -29
  217. data/test/resources/stsci.xml +0 -336
  218. data/test/resources/unittest_stsci.rb +0 -25
  219. data/test/resources/vodataservice/catalog_service_resource_v1_0.xml +0 -128
  220. data/test/resources/vodataservice/data_collection_resource_v0_5.xml +0 -54
  221. data/test/resources/vodataservice/data_collection_resource_v1_0.xml +0 -117
  222. data/test/resources/vodataservice/data_service_resource_v1_0.xml +0 -115
  223. data/test/resources/vodataservice/sky_service_resource_v0_10.xml +0 -45
  224. data/test/resources/vodataservice/table_service_resource_v1_0.xml +0 -122
  225. data/test/resources/vodataservice/tabular_sky_service_resource_v0_10.xml +0 -60
  226. data/test/resources/vodataservice/unittest_v0_5.rb +0 -126
  227. data/test/resources/vodataservice/unittest_v1_0.rb +0 -151
  228. data/test/resources/voregistry/authority_resource_v0_3.xml +0 -20
  229. data/test/resources/voregistry/authority_resource_v1_0.xml +0 -82
  230. data/test/resources/voregistry/registry_service_v0_3.xml +0 -20
  231. data/test/resources/voregistry/registry_service_v1_0.xml +0 -107
  232. data/test/resources/voregistry/unittest_v0_3.rb +0 -31
  233. data/test/resources/voregistry/unittest_v1_0.rb +0 -34
  234. data/test/resources/voresource/organisation_resource_v1_0.xml +0 -90
  235. data/test/resources/voresource/resource_organisation_v0_10.xml +0 -22
  236. data/test/resources/voresource/resource_service_v0_10.xml +0 -19
  237. data/test/resources/voresource/resource_v0_10.xml +0 -19
  238. data/test/resources/voresource/resource_v1_0.xml +0 -79
  239. data/test/resources/voresource/service_resource_v1_0.xml +0 -91
  240. data/test/resources/voresource/unittest_v0_10.rb +0 -61
  241. data/test/resources/voresource/unittest_v0_9.rb +0 -4
  242. data/test/resources/voresource/unittest_v1_0.rb +0 -190
  243. data/test/services/gestalt/unittest.rb +0 -74
  244. data/test/services/registry/unittest.rb +0 -34
  245. data/test/services/resolver/unittest.rb +0 -38
  246. data/test/simple/unittest.rb +0 -46
  247. data/test/spacetime/unittest.rb +0 -39
  248. data/test/stc/catalog_entry_location_v1_20.xml +0 -112
  249. data/test/stc/obs_data_location_v1_20.xml +0 -108
  250. data/test/stc/search_location_v1_20.xml +0 -54
  251. data/test/stc/stc_resource_profile_v1_20.xml +0 -60
  252. data/test/stc/unittest_v1_20.rb +0 -620
  253. data/test/voevent/unittest_v1_0.rb +0 -79
  254. data/test/voevent/unittest_v1_1.rb +0 -70
  255. data/test/voevent/voevent_v1_0.xml +0 -96
  256. data/test/votables/test.vot +0 -366
  257. data/test/votables/unittest.rb +0 -54
@@ -1,391 +1,512 @@
1
- require 'voruby/active_votable/loader'
1
+ require 'ostruct'
2
2
 
3
- require 'active_record/connection_adapters/abstract/quoting'
3
+ begin
4
+ require 'rubygems'
5
+ rescue LoadError; end
6
+ require 'activerecord'
7
+ require 'xml/libxml'
4
8
 
5
9
  module VORuby
6
- module ActiveVotable
10
+ module ActiveVOTable
11
+
12
+ class Callbacks #:nodoc:
13
+ include XML::SaxParser::Callbacks
7
14
 
8
- class LibXmlSaxParserWrapper
9
- attr_reader :filename
10
-
11
- def initialize(file)
12
- @parser = XML::SaxParser.new
13
- self.filename = file
15
+ attr_accessor :base_class,
16
+ :meta_location, :data_location, :metadata,
17
+ :row, :data_tables, :fields_tables, :inside_td, :in_error
18
+
19
+ def initialize(base_class)
20
+ self.base_class = base_class
21
+
22
+ self.row = []
23
+ self.metadata = []
24
+
25
+ self.meta_location = OpenStruct.new(:resource => 0, :table => 0, :field => 0)
26
+ self.data_location = OpenStruct.new(:resource => 0, :table => 0, :row => 0, :column => 0)
27
+ self.inside_td = false
28
+ self.in_error = false
14
29
  end
15
-
16
- def filename=(file)
17
- @filename = file
18
- @parser.filename = self.filename
30
+
31
+ def on_start_element(name, attrs)
32
+ case name
33
+ when 'RESOURCE'
34
+ self.meta_location.resource += 1
35
+ self.data_location.resource += 1
36
+ self.meta_location.table = 0
37
+ self.data_location.table = 0
38
+ when 'TABLE'
39
+ self.meta_location.table += 1
40
+ self.meta_location.field = 0
41
+
42
+ self.data_location.table += 1
43
+ self.data_location.row = 0
44
+ self.data_location.column = 0
45
+
46
+ create_db_tables()
47
+ when 'FIELD'
48
+ self.meta_location.field += 1
49
+ self.metadata << attrs
50
+ when 'DATA'
51
+ create_data_schema() # create the table and make it available
52
+ create_metadata_table()
53
+ when 'TABLEDATA'
54
+ self.base_class.connection.begin_db_transaction()
55
+ when 'TR'
56
+ self.data_location.resource = self.meta_location.resource
57
+ self.data_location.table = self.meta_location.table
58
+ self.data_location.row += 1
59
+ self.data_location.column = 0
60
+ self.row.clear
61
+ when 'TD'
62
+ self.data_location.column += 1
63
+ self.inside_td = true
64
+ when 'INFO'
65
+ self.in_error = true if attrs['name'] == 'QUERY_STATUS' and attrs['value'] == 'ERROR'
66
+ end
19
67
  end
20
-
21
- def add_listener(event, &proc)
22
- @parser.send(event, &proc)
68
+
69
+ def on_end_element(name)
70
+ case name
71
+ when 'TABLE'
72
+ self.metadata.clear
73
+ when 'TABLEDATA'
74
+ self.base_class.connection.commit_db_transaction()
75
+ when 'TR'
76
+ create_record()
77
+ when 'TD'
78
+ self.inside_td = false
79
+ when 'INFO'
80
+ self.in_error = false
81
+ end
23
82
  end
24
-
25
- def method_missing(symbol, *args)
26
- @parser.send(symbol, *args)
83
+
84
+ def on_characters(chars)
85
+ raise "Query status error: #{chars}" if self.in_error
86
+ self.row << chars if self.inside_td
27
87
  end
28
- end
29
88
 
30
- class VOTableExtractor < LibXmlSaxParserWrapper
31
- attr_reader :actions
32
-
33
- def initialize(file, actions={})
34
- super(file)
35
-
36
- @actions = actions
37
-
38
- register_listeners()
89
+ def data_table_name
90
+ "#{self.base_class.table_name}_#{Base::DATA_ID}_#{self.data_location.resource}_#{self.data_location.table}"
39
91
  end
40
-
41
- def register_listeners
42
- self.add_listener(:on_start_element) do |name, attrs|
43
- if self.actions.has_key?(:on_start_element) and self.actions[:on_start_element].has_key?(name)
44
- self.actions[:on_start_element][name].call(name, attrs)
45
- end
46
- end
47
-
48
- self.add_listener(:on_end_element) do |name|
49
- if self.actions.has_key?(:on_end_element) and self.actions[:on_end_element].has_key?(name)
50
- self.actions[:on_end_element][name].call(name)
51
- end
52
- end
53
-
54
- self.add_listener(:on_characters) do |chars|
55
- self.actions[:on_characters].call(chars) if self.actions.has_key?(:on_characters)
56
- end
57
-
58
- self.add_listener(:on_cdata_block) do |cdata|
59
- self.actions[:on_cdata_block].call(cdata) if self.actions.has_key?(:on_cdata_block)
60
- end
61
-
92
+
93
+ def fields_table_name
94
+ "#{self.base_class.table_name}_#{Base::SCHEMA_ID}_#{self.meta_location.resource}_#{self.meta_location.table}"
62
95
  end
63
- end
64
-
65
- # ActiveVotable is a simple, but hopefully useful, database-backed
66
- # VOTable parser. It uses LibXML's SAX parser to extract the fields
67
- # and data of a VOTable and loads those fields into a database table.
68
- # This allows all the power of a full database manager + ActiveRecord
69
- # to be used in searching and manipulating the VOTable. It assumes
70
- # that the VOTable has one resource with one table and that the table
71
- # is encoded as text and not binary.
72
- #
73
- # ActiveVotable.establish_connection(
74
- # :adapter => 'postgresql'
75
- # :database => 'votables'
76
- # :host => 'localhost'
77
- # :username => 'dbuser'
78
- # :password => 'dbpassword')
79
- #
80
- # ActiveVotable.build('test/active_votable/test.vot') do |vot|
81
- # records = vot.find(:all, :conditions => ['survey LIKE ?', 'Deep Lens Survey'], :order => 'id')
82
- #
83
- # records = vot.page(10)
84
- #
85
- # vot.foreach_page do |page_num, records|
86
- # puts "Page #{page_num}:"
87
- # records.each do |rec|
88
- # puts rec.inspect
89
- # end
90
- # end
91
- class ActiveVotable < ActiveRecord::Base
92
- @@end_of_cell = true
93
- @@arow = []
94
- @@ordered_columns = []
95
- @@items_per_page = 20
96
- @@added_col = false
97
-
98
- DATATYPE_CONVERSIONS = {
99
- 'boolean' => :boolean,
100
- 'bit' => :integer,
101
- 'unsignedByte' => :integer,
102
- 'short' => :integer,
103
- 'int' => :integer,
104
- 'long' => :integer,
105
- 'char' => :string,
106
- 'unicodeChar' => :string,
107
- 'float' => :float,
108
- 'double' => :float,
109
- 'floatComplex' => :string,
110
- 'doubleComplex' => :string
111
- }
112
-
113
- @@actions = {
114
- :on_start_element => {
115
- 'FIELD' => Proc.new { |name, attrs|
116
- vot_col_name = attrs['name'] || attrs['ID'] || "unknown_column_#{rand(1000000)}"
117
- field_name = ActiveVotable.columnize(vot_col_name)
118
- field_name = 'record_id' if field_name == 'id'
119
- field_type = ActiveVotable.column_type(attrs['datatype'] || 'char', attrs['arraysize'])
120
- @@ordered_columns << {
121
- :id => attrs['ID'],
122
- :vot_col_name => vot_col_name, :name => field_name,
123
- :datatype => attrs['datatype'], :type => field_type,
124
- :ucd => attrs['ucd'],
125
- :arraysize => attrs['arraysize']
126
- }
96
+
97
+ private
98
+
99
+ def create_db_tables
100
+ self.base_class.connection.create_table(data_table_name()){}
101
+ self.base_class.connection.create_table(fields_table_name()){}
102
+ end
103
+
104
+ def create_data_schema
105
+ # A couple additional columns representing the position of the table in the votable as a whole.
106
+ self.base_class.connection.add_column(self.data_table_name, :resource_num, :integer, :null => false, :default => 1)
107
+ self.base_class.connection.add_column(self.data_table_name, :table_num, :integer, :null => false, :default => 1)
127
108
 
128
- ActiveRecord::Schema.define do
129
- add_column(ActiveVotable.table_name(), field_name, field_type)
130
- end
131
- },
132
- 'TD' => Proc.new{ |name, attrs|
133
- @@added_col = false
134
- @@end_of_cell = false
135
- },
136
- 'TABLEDATA' => Proc.new{ |name, attrs|
137
- connection().begin_db_transaction()
138
- }
139
- },
140
- :on_end_element => {
141
- 'TR' => Proc.new { |name|
142
- row_def = {:resource_num => 1, :table_num => 1}
143
- @@ordered_columns.each_index do |i|
144
- row_def[@@ordered_columns[i][:name]] = cast(@@arow[i], @@ordered_columns[i][:type])
145
- end
146
-
147
- # On my machine, doing this rather than an ActiveRecord.create()
148
- # and using begin_db_transaction() and commit_db_transaction()
149
- # seems to speed things up by ~2x. The added complexity seemed worth
150
- # it to me.
151
- values = @@ordered_columns.collect{ |c| connection().quote(row_def[c[:name]]) }
152
- connection().insert(
153
- "INSERT INTO #{table_name()} " +
154
- "(#{@@ordered_columns.collect{ |c| connection().quote_column_name(c[:name]) }.join(', ')}) " +
155
- "VALUES (#{values.join(', ')})"
156
- )
157
-
158
- @@arow = []
159
- },
160
- 'TD' => Proc.new { |name|
161
- @@end_of_cell = true
162
- @@arow << nil if !@@added_col # This is necessary because a blank tag doesn't trigger an on_characters callback.
163
- },
164
- 'TABLEDATA' => Proc.new { |name|
165
- connection().commit_db_transaction()
166
- }
167
- },
168
- :on_characters => Proc.new { |chars|
169
- @@arow << chars if !@@end_of_cell
170
- @@added_col = true
171
- },
172
- :on_cdata_block => Proc.new{ |cdata|
173
- @@arow << cdata if !@@end_of_cell # not sure if this is safe
174
- }
175
- }
176
-
177
- # Parse a VOTable into a database table. If no table is specified, one will be
178
- # created with the signature 'votable_timestamp_randomnumber'. If the optional
179
- # block is provided, ActiveVotable.drop() will be called automatically.
180
- # [_src_:]
181
- # The name of the VOTable file.
182
- # [_tbl_name_:]
183
- # The name of the database table to dump the VOTable into.
184
- def self.build(src, tbl_name=nil, &block)
185
- if block
186
- build(src, tbl_name)
187
- block.call(self)
188
- drop()
189
- else
190
- ActiveVotable.src = src
191
- ActiveVotable.parser = VOTableExtractor.new(src, @@actions)
192
-
193
- tname = tbl_name || "votable_#{Time.now.to_i}_#{rand(1000000)}"
194
- ActiveVotable.table_name = tname
195
- set_table_name(tname)
196
-
197
- create_basic_schema()
198
- parse()
199
-
200
- return self
109
+ # The columns in the votable table itself.
110
+ self.metadata.each do |md|
111
+ column_name = (md['name'] || md['ID'] || '').downcase.gsub(/\W+/, '_')
112
+ #column_name = 'record_type' if column_name == 'type' # 'type' triggers ActiveRecord's
113
+ self.base_class.connection.add_column(
114
+ self.data_table_name,
115
+ column_name, db_data_type(md['datatype'], md['arraysize'])
116
+ )
201
117
  end
118
+
119
+ # Add a simple index.
120
+ self.base_class.connection.add_index(self.data_table_name, [:resource_num, :table_num])
202
121
  end
203
122
 
204
- # Instantiate a VOTable that already exists inside
205
- # the database. If the optional block is provided
206
- # ActiveVotable.drop() will be called automatically.
207
- # [_tbl_name:]
208
- # The name of the database table the VOTable lives in.
209
- def self.from_db_table(tbl_name, metadata=[], &block)
210
- ActiveVotable.table_name = tbl_name
211
- set_table_name(tbl_name)
212
- @@ordered_columns = metadata
123
+ def create_metadata_table
124
+ # A couple additional columns representing the position of the table in the votable as a whole.
125
+ self.base_class.connection.add_column(self.fields_table_name, :resource_num, :integer, :null => false, :default => 1)
126
+ self.base_class.connection.add_column(self.fields_table_name, :table_num, :integer, :null => false, :default => 1)
213
127
 
214
- if block
215
- block.call(self)
216
- drop()
217
- else
218
- return self
128
+ # The columns defined by the votable table fields itself.
129
+ self.base_class.connection.add_column(self.fields_table_name, :vid, :string)
130
+ self.base_class.connection.add_column(self.fields_table_name, :unit, :string)
131
+ self.base_class.connection.add_column(self.fields_table_name, :datatype, :string)
132
+ self.base_class.connection.add_column(self.fields_table_name, :precision, :string)
133
+ self.base_class.connection.add_column(self.fields_table_name, :width, :integer)
134
+ self.base_class.connection.add_column(self.fields_table_name, :ref, :string)
135
+ self.base_class.connection.add_column(self.fields_table_name, :name, :string)
136
+ self.base_class.connection.add_column(self.fields_table_name, :ucd, :string)
137
+ self.base_class.connection.add_column(self.fields_table_name, :utype, :string)
138
+ self.base_class.connection.add_column(self.fields_table_name, :arraysize, :string)
139
+ self.base_class.connection.add_column(self.fields_table_name, :type, :string)
140
+
141
+ # A simple index.
142
+ self.base_class.connection.add_index(self.fields_table_name, [:resource_num, :table_num])
143
+
144
+ # and its columns
145
+ column_names = [
146
+ self.base_class.connection.quote_column_name('resource_num'),
147
+ self.base_class.connection.quote_column_name('table_num'),
148
+ self.base_class.connection.quote_column_name('vid'),
149
+ self.base_class.connection.quote_column_name('unit'),
150
+ self.base_class.connection.quote_column_name('datatype'),
151
+ self.base_class.connection.quote_column_name('precision'),
152
+ self.base_class.connection.quote_column_name('width'),
153
+ self.base_class.connection.quote_column_name('ref'),
154
+ self.base_class.connection.quote_column_name('name'),
155
+ self.base_class.connection.quote_column_name('ucd'),
156
+ self.base_class.connection.quote_column_name('utype'),
157
+ self.base_class.connection.quote_column_name('arraysize'),
158
+ self.base_class.connection.quote_column_name('type')
159
+ ]
160
+
161
+ self.base_class.connection.begin_db_transaction()
162
+
163
+ columns = self.base_class.connection.columns(self.fields_table_name)
164
+ self.metadata.each do |md|
165
+ # quote each value
166
+ column_values = [
167
+ self.base_class.connection.quote(self.meta_location.resource, columns.find{ |c| c.name == 'resource_num'}),
168
+ self.base_class.connection.quote(self.meta_location.table, columns.find{ |c| c.name == 'table_num' }),
169
+ self.base_class.connection.quote(md['ID'], columns.find{ |c| c.name == 'vid'}),
170
+ self.base_class.connection.quote(md['unit'], columns.find{ |c| c.name == 'unit'}),
171
+ self.base_class.connection.quote(md['datatype'], columns.find{ |c| c.name == 'datatype'}),
172
+ self.base_class.connection.quote(md['precision'], columns.find{ |c| c.name == 'precision'}),
173
+ self.base_class.connection.quote(md['width'], columns.find{ |c| c.name == 'width'}),
174
+ self.base_class.connection.quote(md['ref'], columns.find{ |c| c.name == 'ref'}),
175
+ self.base_class.connection.quote(md['name'], columns.find{ |c| c.name == 'name'}),
176
+ self.base_class.connection.quote(md['ucd'], columns.find{ |c| c.name == 'ucd'}),
177
+ self.base_class.connection.quote(md['utype'], columns.find{ |c| c.name == 'utype'}),
178
+ self.base_class.connection.quote(md['arraysize'], columns.find{ |c| c.name == 'arraysize'}),
179
+ self.base_class.connection.quote(md['type'], columns.find{ |c| c.name == 'type'})
180
+ ]
181
+
182
+ column_names = columns.reject{ |c| c.name == 'id' }.collect{ |c| self.base_class.connection.quote_column_name(c.name) }
183
+ self.base_class.connection.insert(
184
+ "INSERT INTO #{self.base_class.connection.quote_table_name(self.fields_table_name)} (#{column_names.join(', ')}) VALUES (#{column_values.join(', ')})"
185
+ )
219
186
  end
187
+
188
+ self.base_class.connection.commit_db_transaction()
220
189
  end
221
-
222
- # Set the file name of the source VOTable.
223
- def self.src=(src)
224
- @@src = src
225
- end
226
-
227
- # Get the file name of the source VOTable.
228
- def self.src
229
- @@src
230
- end
231
-
232
- # Set the XML parser to use. Must of type VOTableExtractor.
233
- # Typically you'll never use this directly.
234
- def self.parser=(parser)
235
- @@parser = parser
190
+
191
+ def create_record
192
+ column_names = [
193
+ self.base_class.connection.quote_column_name('resource_num'),
194
+ self.base_class.connection.quote_column_name('table_num')
195
+ ]
196
+ column_values = [
197
+ self.base_class.connection.quote(self.data_location.resource),
198
+ self.base_class.connection.quote(self.data_location.table)
199
+ ]
200
+
201
+ # The first three columns are id (which is auto-incremented),
202
+ # resource_num and table_num (which we've taken care of above).
203
+ self.base_class.connection.columns(self.data_table_name)[3..-1].each_with_index { |c, i|
204
+ column_values << self.base_class.connection.quote(self.row[i], c)
205
+ column_names << self.base_class.connection.quote_column_name(c.name)
206
+ }
207
+
208
+ self.base_class.connection.insert(
209
+ "INSERT INTO #{self.base_class.connection.quote_table_name(self.data_table_name)} (#{column_names.join(', ')}) VALUES (#{column_values.join(', ')})"
210
+ )
236
211
  end
237
-
238
- # Get the XML parser in use.
239
- def self.parser
240
- @@parser
212
+
213
+ def db_data_type(xml_datatype, xml_arraysize=nil)
214
+ return :string if xml_arraysize # cop out if the arraysize is specified
215
+
216
+ case xml_datatype
217
+ when 'boolean' then :boolean
218
+ when 'bit' then :integer
219
+ when 'unsignedByte' then :text
220
+ when 'short' then :integer
221
+ when 'int' then :integer
222
+ when 'log' then :float
223
+ when 'char' then :string
224
+ when 'unicodeChar' then :string
225
+ when 'float' then :float
226
+ when 'double' then :float
227
+ when 'floatComplex' then :string
228
+ when 'doubleComplex' then :string
229
+ else
230
+ :string
231
+ end
241
232
  end
233
+ end
234
+
235
+ # Paging methods for data.
236
+ module Pager
237
+ DEFAULT_PER_PAGE = 25
242
238
 
243
- # Set the name of the database table to use to store the VOTable.
244
- # Typically you'll never use this directly but rather use build().
245
- def self.table_name=(tbl_name)
246
- @@table_name = tbl_name
239
+ def per_page=(n)
240
+ @per_page = n
247
241
  end
248
242
 
249
- # Get the name of the database table the VOTable is stored in.
250
- def self.table_name
251
- @@table_name
252
- end
253
-
254
- # Set the number of records per page for use in paging results.
255
- def self.items_per_page=(num)
256
- @@items_per_page = num
257
- end
258
-
259
- # Get the number of records per page.
260
- def self.items_per_page
261
- @@items_per_page
243
+ def per_page
244
+ @per_page || DEFAULT_PER_PAGE
262
245
  end
263
-
264
- # Parse the VOTable. Called automatically when you used ActiveVotable.build().
265
- # Typically you'll never use this directly.
266
- def self.parse
267
- parser().parse()
246
+
247
+ def page(p=1, page_size=nil, options={})
248
+ options ||= {}
249
+
250
+ options[:limit] = page_size || per_page
251
+ options[:offset] = options[:limit] * (p - 1)
252
+
253
+ find(:all, options)
268
254
  end
269
-
270
- # All parsed VOTables will have a standard set of columns which
271
- # are created by this method. Typically you'll never use this
272
- # directly.
273
- def self.create_basic_schema
274
- ActiveRecord::Schema.define do
275
- create_table ActiveVotable::table_name(), :primary_key => 'id' do |t|
276
- t.column :resource_num, :integer, :null => false, :default => 1
277
- t.column :table_num, :integer, :null => false, :default => 1
278
- end
255
+
256
+ def each_page(page_size=nil, options={})
257
+ num_per_page = page_size || per_page
258
+
259
+ (1..(count().to_f / num_per_page.to_f).ceil()).each do |n|
260
+ yield(page(n, num_per_page), n)
279
261
  end
280
262
  end
281
-
282
- # Erase the table out of the database.
283
- def self.drop
284
- ActiveRecord::Schema.define do
285
- begin
286
- drop_table ActiveVotable::table_name
287
- rescue ActiveRecord::StatementInvalid
288
- # If a table has already gone bye-bye, well, that's life.
289
- end
290
- end
263
+ end
264
+
265
+ # Have you ever wished you could treat your VOTable[http://www.ivoa.net/Documents/latest/VOT.html]
266
+ # as a database? Well, now you can. ActiveVOTable is a package for reading votables into
267
+ # a relational database. It uses the SAX parser in LibXML[http://libxml.rubyforge.org/] so it can
268
+ # handle votables of arbitrary size, and it wraps the resulting database tables in ActiveRecord[http://ar.rubyonrails.com/]
269
+ # so that you can easily execute queries against it. It can even handle votables with multiple tables
270
+ # in multiple resources.
271
+ #
272
+ # ActiveVOTable::Base.logger = Logger.new(STDOUT) # set the logger to output to STDOUT, exactly as in ActiveRecord
273
+ # ActiveVOTable::Base.establish_connection(:adapter => 'sqlite3', :database => 'votables.sqlite3') # connect to the database
274
+ #
275
+ # # read in the file results.vot into a SQLite database
276
+ # vot = ActiveVOTable::Base.from(:xml, File.new('results.vot'), 'results')
277
+ #
278
+ # table = vot.first # a votable can have more than one TABLE element, but we're interested in the first one.
279
+ #
280
+ # # every table has a data section and schema section, both of which are accessible
281
+ # data = table.data
282
+ # schema = table.schema
283
+ #
284
+ # # both data and schemata are ultimately simply subclasses of ActiveRecord::Base and have all the same methods
285
+ # puts data.find(:first).inspect
286
+ # # => <VORuby::ActiveVOTable::Base::Result11::ResultData11 id: 1, resource_num: 1, table_num: 1, ra: 10.68, dec: 41.27, name: "N 224", rvel: -297, e_rvel: 5, r: 0.7>
287
+ # puts schema.find(:first).inspect
288
+ # # => #<VORuby::ActiveVOTable::Base::Result11::ResultSchema11 id: 1, resource_num: 1, table_num: 1, vid: "col1", unit: "deg", datatype: "float", precision: "2", width: 6, ref: "J2000", name: "RA", ucd: "pos.eq.ra;meta.main", utype: nil, arraysize: nil, vtype: nil>
289
+ #
290
+ # Note that in the above example the #vid method of the ResultSchema11 instance corresponds to the <tt>ID</tt> attribute of the FIELD
291
+ # and the #vtype method corresponds to the FIELD's <tt>type</tt> attribute.
292
+ #
293
+ # # when you're done, you can optionally delete any tables hanging around in the database
294
+ # vot.cleanup()
295
+ #
296
+ # # if the tables are already sitting around in the database (perhaps from a previous run)...
297
+ # vot = ActiveVOTable::Base.from(:db, 'results')
298
+ #
299
+ # ActiveVOTable::Base#from and ActiveVOTable::Base#cleanup both take an optional hash of connection parameters
300
+ # (the same kind ActiveRecord::Base#establish_connection does). This allows you the flexibility to upload
301
+ # your votables to different databases if desired. So something like this works:
302
+ #
303
+ # vot1 = ActiveVOTable::Base.from(:xml,
304
+ # File.new('results1.vot'),
305
+ # 'results1', # this *must* be different from below
306
+ # :adapter => 'sqlite3', :database => 'votables.sqlite3'
307
+ # )
308
+ #
309
+ # vot2 = ActiveVOTable::Base.from(:xml,
310
+ # File.new('results2.vot'),
311
+ # 'results2', # *must* be different from above
312
+ # :adapter => 'mysql', :host => 'localhost', :username => 'me', :password => 'secret', :database => 'votables'
313
+ # )
314
+ #
315
+ # vot1.cleanup
316
+ # vot2.cleanup
317
+ class Base < ActiveRecord::Base
318
+ SCHEMA_ID = 'schema'
319
+ DATA_ID = 'data'
320
+
321
+ def self.table_search_pattern #:nodoc:
322
+ "_(#{SCHEMA_ID}|#{DATA_ID})_(\\d+)_(\\d+)"
323
+ end
324
+
325
+ # List the names of all the database tables associated with the
326
+ # votable in question. And addition <tt>regex</tt> may be given
327
+ # to further refine the list, if desired.
328
+ #
329
+ # puts vot.dbtables.inspect
330
+ # # => ['result_data_1_1', 'result_schema_1_1']
331
+ def self.dbtables(regex=nil)
332
+ raise "#{connection.adapter_name} does not support #tables" if !connection.respond_to?(:tables)
333
+
334
+ self.connection.tables.find_all{ |t|
335
+ base_match = t.match(/^(#{self.table_name()})#{self.table_search_pattern()}$/)
336
+ regex ? (base_match and t.match(regex)) : base_match
337
+ }.sort { |a, b|
338
+ a_matches = a.match(/^(#{self.table_name})#{self.table_search_pattern()}$/)
339
+ b_matches = b.match(/^(#{self.table_name})#{self.table_search_pattern()}$/)
340
+
341
+ a_matches[1] <=> b_matches[1] and
342
+ a_matches[2] <=> a_matches[2] and
343
+ a_matches[3].to_i <=> a_matches[3].to_i and
344
+ b_matches[4].to_i <=> b_matches[4].to_i
345
+ }
291
346
  end
292
-
293
- # Converts a VOTable name or ID into a (hopefully)
294
- # valid database column name. Typically you'll never use this
295
- # directly.
296
- def self.columnize(name)
297
- name = name.downcase
298
- name['-'] = '_' if name =~ /-/
299
- name[/\W+/] = '_' if name =~ /\W+/
300
- name
347
+
348
+ def self.find_or_create_class(klass_name, subclass=Base) #:nodoc:
349
+ const_defined?(klass_name) ? const_get(klass_name) : const_set(klass_name, Class.new(subclass))
301
350
  end
302
-
303
- # Converts a VOTable datatype into a corresponding
304
- # database column type. Typically you'll never use this
305
- # directly.
306
- def self.column_type(datatype, arraysize)
307
- if arraysize
308
- :string
309
- else
310
- DATATYPE_CONVERSIONS[datatype] || :string
351
+
352
+ # List all the schema objects associated with the votable.
353
+ # Typically, you'll use #tables instead (which associates
354
+ # a schema with its corresponding data), but this is occassionally
355
+ # useful.
356
+ #
357
+ # puts vot.schemata.inspect
358
+ # # => [ActiveVOTable::Base::ResultSchema11]
359
+ def self.schemata
360
+ self.dbtables(/_#{SCHEMA_ID}_\d+_\d+$/).collect do |t|
361
+ schema_klass = self.find_or_create_class(t.classify, self)
362
+ schema_klass.set_table_name(t)
363
+ schema_klass
311
364
  end
312
365
  end
313
-
314
- # Given one of the types specified in ActiveRecord::ConnectionAdapters::TableDefinition.column
315
- # casts a string into a corresponding value.
316
- def self.cast(value, type)
317
- case type
318
- when :integer then value.to_i
319
- when :float then value.to_f
320
- when :string then value.to_s
321
- when :boolean
322
- (value != 'false' and value != '0') ? true: false
323
- else value
366
+
367
+ # List all the data objects associated with the votable.
368
+ # Typically, you'll use #tables instead (which associates
369
+ # a schema with its corresponding data), but this is occassionally
370
+ # useful.
371
+ #
372
+ # puts vot.data.inspect
373
+ # # => [ActiveVOTable::Base::ResultData11]
374
+ def self.data
375
+ self.dbtables(/_#{DATA_ID}_\d+_\d+$/).collect do |t|
376
+ data_klass = self.find_or_create_class(t.classify, self)
377
+ data_klass.extend(Pager)
378
+ data_klass.set_table_name(t)
379
+ data_klass
324
380
  end
325
381
  end
326
-
327
- # Establish a connection to the database. See
328
- # ActiveRecord::Base.establish_connection() for further details.
329
- def self.establish_connection(config)
330
- self.superclass.establish_connection(config)
331
- end
332
-
333
- # Works exactly as ActiveRecord.find() except that an
334
- # additional option--:page--may be passed in. In that
335
- # case :limit and :offset are ignored.
336
- def self.find(*args)
337
- options = extract_options_from_args!(args)
338
-
339
- if options.has_key?(:page)
340
- options[:limit] = items_per_page()
341
- options[:offset] = items_per_page() * (options[:page] - 1)
342
- options.delete(:page)
343
- end
344
-
345
- validate_find_options(options)
346
- set_readonly_option!(options)
347
-
348
- case args.first
349
- when :first then find_initial(options)
350
- when :all then find_every(options)
351
- else find_from_ids(args, options)
382
+
383
+ # List all the tables associated with the votable.
384
+ # Each member of the turned array responds to a #schema and
385
+ # #data method.
386
+ #
387
+ # vot.tables.each do |t|
388
+ # puts t.schema
389
+ # puts t.data
390
+ # end
391
+ def self.tables
392
+ data_list = self.data
393
+
394
+ list = []
395
+ self.schemata.each_with_index do |s, i|
396
+ list << OpenStruct.new(:schema => s, :data => data_list[i])
352
397
  end
353
- end
354
-
355
- # Retrieve the specified page. Applies to
356
- # all rows in the VOTable.
357
- def self.page(page=1)
358
- find(:all, :order => 'id', :page => page)
398
+ list
359
399
  end
360
400
 
361
- # Find the total number of pages.
362
- def self.number_of_pages
363
- (count() / items_per_page().to_f).ceil()
401
+ # Delete any tables and (optionally) Ruby constants associated with an already existing votable.
402
+ #
403
+ # # ActiveVOTable::Base::Result11, ActiveVOTable::Base::Result11Schema
404
+ # # and ActiveVOTable::Base::Result11Data are still hanging around after this
405
+ # vot.cleanup
406
+ #
407
+ # or...
408
+ #
409
+ # # ActiveVOTable::Base::Result11, ActiveVOTable::Base::Result11Schema
410
+ # # and ActiveVOTable::Base::Result11Data no longer exist...
411
+ # vot.cleanup(true)
412
+ #
413
+ # There is also a block form which allows you access to the classes after their database
414
+ # tables have been destroyed but before the classes themselves have been disposed of. This
415
+ # is mostly for testing purposes and very rarely used in real life.
416
+ #
417
+ # vot.cleanup(true) do |v|
418
+ # # the db tables are gone, but I can still play with the classes if I want...
419
+ # end
420
+ def self.cleanup(remove_class_on_cleanup=false)
421
+ self.dbtables.each { |t| self.connection.drop_table(t) }
422
+ yield self if block_given?
423
+ superclass.send(:remove_const, self.table_name.classify) if remove_class_on_cleanup and superclass.send(:const_defined?, self.table_name.classify)
364
424
  end
425
+
426
+ # Instantiate a votable from XML.
427
+ #
428
+ # +xml+:: May be a string or a File object.
429
+ # +name+:: A string representing the root names of the tables that will be created in the database. If none is specified the string 'votable' + a timestamp will be used.
430
+ # +dboptions+:: An optional hash of database connection parameters (exactly as you'd pass to ActiveRecord::Base#establish_connection). Necessary only if ActiveVOTable::Base#establish_connection hasn't been called.
431
+ #
432
+ # An array of objects representing the tables in the votables is returned.
433
+ # Each of these objects has a #data method and a #schema method corresponding
434
+ # to the TABLEDATA and FIELD elements in the VOTable specification.
435
+ #
436
+ # vot = ActiveVOTable::Base.from_xml(
437
+ # File.new('votable.xml'), # parse the file votable.xml
438
+ # 'my_votable' # every table created will be prefixed with 'my_votable',
439
+ # :adapter => 'mysql', :host => 'localhost', :username => 'me', :password => 'secret', :database => 'votables', # stick it into a MySQL database
440
+ # )
441
+ #
442
+ # vot.each do |table|
443
+ # puts table.schema.find(:all).inspect # retrieve rich semantic information about each column
444
+ # puts table.data.find(:all, :conditions => ['ra > ?', 10.2]).inspect # find the actual data
445
+ # end
446
+ #
447
+ # Assuming votable.xml had 2 resources, each with one table (for example) the table structure created
448
+ # would look like: <tt>my_votable_fields_1_1, my_votable_rows_1_1, my_votable_fields_2_1, my_votable_rows_2_1</tt>.
449
+ def self.from_xml(xml, name=nil, conn_params=nil, logger=nil)
450
+ name ||= "votable_#{DateTime.now.strftime('%Y%m%d%H%M%S%L')}"
451
+ raise "XML source must be a string or a File object, not '#{xml}'" if !xml.is_a?(String) and !xml.is_a?(File)
452
+
453
+ parser = XML::SaxParser.new
454
+ xml.is_a?(String) ? parser.string = xml : parser.filename = xml.path
455
+
456
+ k = self.from_database(name, conn_params, logger, false)
457
+
458
+ callbacks = Callbacks.new(k)
459
+ parser.callbacks = callbacks
460
+ parser.parse
365
461
 
366
- # Iterate through each page in the VOTable. The
367
- # block receives the page number and the list of
368
- # records.
369
- def self.foreach_page(&block)
370
- num_pages = (count().to_f / items_per_page().to_f).ceil()
371
-
372
- (1..num_pages).each do |page_num|
373
- block.call(page_num, page(page_num))
374
- end
462
+ k
375
463
  end
376
-
377
- # Iterate through each record in the votable.
378
- def self.foreach_record(&block)
379
- find(:all, :order => 'id').each do |record|
380
- block.call(record)
381
- end
464
+
465
+ # Instantiate a votable from a database.
466
+ #
467
+ # Identical to #from_xml, except that it is assumed the appropriate tables already
468
+ # exist in the database.
469
+ #
470
+ # vot = ActiveVOTable::Base.from_database(
471
+ # 'my_votable' # for those tables that begin with 'my_votable',
472
+ # :adapter => 'mysql', :host => 'localhost', :username => 'me', :password => 'secret', :database => 'votables', # look in the MySQL database
473
+ # )
474
+ def self.from_database(name, conn_params=nil, logger=nil, tables_must_exist=true)
475
+ k = self.find_or_create_class(name.classify, Base)
476
+
477
+ k.establish_connection(conn_params) if conn_params
478
+ k.table_name = name
479
+ k.logger = logger if logger
480
+ k.inheritance_column = nil # 'type' is a common votable keyword, so we really want to turn simple inheritance off
481
+
482
+ raise "tables corresponding to '#{name}' do not appear to exist" if tables_must_exist and k.dbtables.size == 0
483
+
484
+ k
382
485
  end
383
486
 
384
- def self.metadata
385
- @@ordered_columns
487
+ # A convenience method around #from_xml and #from_database.
488
+ # The connection parameters are only necessary if you haven't previously called
489
+ # ActiveVOTable::Base#establish_connection.
490
+ #
491
+ # ActiveVOTable::Base.from(:xml,
492
+ # File.new('votable.xml'),
493
+ # 'my_votable'
494
+ # :adapter => 'mysql', :host => 'localhost', :username => 'me', :password => 'secret', :database => 'votables',
495
+ # )
496
+ #
497
+ # ActiveVOTable::Base.from(:db,
498
+ # 'my_votable',
499
+ # :adapter => 'mysql', :host => 'localhost', :username => 'me', :password => 'secret', :database => 'votables',
500
+ # )
501
+ def self.from(src, *args)
502
+ case src
503
+ when :xml then self.from_xml(*args)
504
+ when :db then self.from_database(*args)
505
+ else
506
+ raise "Source must one of: :xml, :db (not '#{src}')"
507
+ end
386
508
  end
387
-
388
509
  end
510
+
389
511
  end
390
- end
391
-
512
+ end