coupler 0.0.1-java

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 (258) hide show
  1. data/.document +5 -0
  2. data/.gitmodules +3 -0
  3. data/.rvmrc +1 -0
  4. data/.vimrc +40 -0
  5. data/Gemfile +27 -0
  6. data/Gemfile.lock +71 -0
  7. data/LICENSE +20 -0
  8. data/NOTES +6 -0
  9. data/README.rdoc +18 -0
  10. data/Rakefile +42 -0
  11. data/TODO +11 -0
  12. data/VERSION +1 -0
  13. data/bin/coupler +7 -0
  14. data/db/.gitignore +6 -0
  15. data/db/migrate/001_initial_schema.rb +166 -0
  16. data/db/migrate/002_stub.rb +4 -0
  17. data/db/migrate/003_stub.rb +4 -0
  18. data/db/migrate/004_create_comparisons.rb +28 -0
  19. data/db/migrate/005_move_database_name.rb +19 -0
  20. data/db/migrate/006_upgrade_comparisons.rb +34 -0
  21. data/db/migrate/007_add_which_to_comparisons.rb +23 -0
  22. data/db/migrate/008_add_result_field_to_transformations.rb +33 -0
  23. data/db/migrate/009_add_generated_flag_to_fields.rb +13 -0
  24. data/db/migrate/010_create_imports.rb +24 -0
  25. data/db/migrate/011_add_primary_key_type.rb +13 -0
  26. data/db/migrate/012_add_transformed_with_to_resources.rb +13 -0
  27. data/db/migrate/013_add_run_count_to_scenarios.rb +13 -0
  28. data/db/migrate/014_add_last_accessed_at_to_some_tables.rb +13 -0
  29. data/db/migrate/015_add_run_number_to_results.rb +15 -0
  30. data/db/migrate/016_fix_scenario_run_count.rb +27 -0
  31. data/db/migrate/017_rename_comparison_columns.rb +14 -0
  32. data/db/migrate/018_fix_scenario_linkage_type.rb +8 -0
  33. data/db/migrate/019_add_columns_to_imports.rb +24 -0
  34. data/db/migrate/020_rename_import_columns.rb +12 -0
  35. data/db/migrate/021_add_fields_to_connections.rb +15 -0
  36. data/db/migrate/022_remove_database_name_from_resources.rb +11 -0
  37. data/features/connections.feature +28 -0
  38. data/features/matchers.feature +35 -0
  39. data/features/projects.feature +11 -0
  40. data/features/resources.feature +62 -0
  41. data/features/scenarios.feature +45 -0
  42. data/features/step_definitions/coupler_steps.rb +145 -0
  43. data/features/step_definitions/matchers_steps.rb +26 -0
  44. data/features/step_definitions/resources_steps.rb +12 -0
  45. data/features/step_definitions/scenarios_steps.rb +7 -0
  46. data/features/step_definitions/transformations_steps.rb +3 -0
  47. data/features/support/env.rb +128 -0
  48. data/features/transformations.feature +22 -0
  49. data/features/wizard.feature +10 -0
  50. data/gfx/coupler-header.svg +213 -0
  51. data/gfx/coupler-sidebar.svg +656 -0
  52. data/gfx/coupler.svg +184 -0
  53. data/gfx/icon.svg +75 -0
  54. data/lib/coupler/base.rb +63 -0
  55. data/lib/coupler/config.rb +128 -0
  56. data/lib/coupler/data_uploader.rb +20 -0
  57. data/lib/coupler/database.rb +31 -0
  58. data/lib/coupler/extensions/connections.rb +57 -0
  59. data/lib/coupler/extensions/exceptions.rb +58 -0
  60. data/lib/coupler/extensions/imports.rb +43 -0
  61. data/lib/coupler/extensions/jobs.rb +21 -0
  62. data/lib/coupler/extensions/matchers.rb +64 -0
  63. data/lib/coupler/extensions/projects.rb +62 -0
  64. data/lib/coupler/extensions/resources.rb +89 -0
  65. data/lib/coupler/extensions/results.rb +100 -0
  66. data/lib/coupler/extensions/scenarios.rb +50 -0
  67. data/lib/coupler/extensions/transformations.rb +70 -0
  68. data/lib/coupler/extensions/transformers.rb +58 -0
  69. data/lib/coupler/extensions.rb +16 -0
  70. data/lib/coupler/helpers.rb +121 -0
  71. data/lib/coupler/import_buffer.rb +48 -0
  72. data/lib/coupler/logger.rb +16 -0
  73. data/lib/coupler/models/common_model.rb +104 -0
  74. data/lib/coupler/models/comparison.rb +166 -0
  75. data/lib/coupler/models/connection.rb +59 -0
  76. data/lib/coupler/models/field.rb +55 -0
  77. data/lib/coupler/models/import.rb +238 -0
  78. data/lib/coupler/models/job.rb +42 -0
  79. data/lib/coupler/models/jobify.rb +17 -0
  80. data/lib/coupler/models/matcher.rb +36 -0
  81. data/lib/coupler/models/project.rb +40 -0
  82. data/lib/coupler/models/resource.rb +287 -0
  83. data/lib/coupler/models/result.rb +92 -0
  84. data/lib/coupler/models/scenario/runner.rb +357 -0
  85. data/lib/coupler/models/scenario.rb +115 -0
  86. data/lib/coupler/models/transformation.rb +117 -0
  87. data/lib/coupler/models/transformer/runner.rb +28 -0
  88. data/lib/coupler/models/transformer.rb +110 -0
  89. data/lib/coupler/models.rb +30 -0
  90. data/lib/coupler/runner.rb +76 -0
  91. data/lib/coupler/scheduler.rb +56 -0
  92. data/lib/coupler.rb +34 -0
  93. data/log/.gitignore +1 -0
  94. data/misc/README +5 -0
  95. data/misc/jruby-json.license +57 -0
  96. data/misc/rack-flash.license +22 -0
  97. data/script/dbconsole.rb +5 -0
  98. data/src/edu/vanderbilt/coupler/Main.java +116 -0
  99. data/src/edu/vanderbilt/coupler/jruby.properties +1 -0
  100. data/tasks/annotations.rake +84 -0
  101. data/tasks/db.rake +120 -0
  102. data/tasks/environment.rake +12 -0
  103. data/tasks/jeweler.rake +43 -0
  104. data/tasks/package.rake +58 -0
  105. data/tasks/rdoc.rake +13 -0
  106. data/tasks/test.rake +63 -0
  107. data/tasks/vendor.rake +43 -0
  108. data/test/README.txt +6 -0
  109. data/test/config.yml +9 -0
  110. data/test/coupler/models/test_import.rb +221 -0
  111. data/test/factories.rb +91 -0
  112. data/test/fixtures/duplicate-keys.csv +5 -0
  113. data/test/fixtures/no-headers.csv +50 -0
  114. data/test/fixtures/people.csv +51 -0
  115. data/test/fixtures/varying-row-size.csv +4 -0
  116. data/test/helper.rb +156 -0
  117. data/test/integration/extensions/test_connections.rb +80 -0
  118. data/test/integration/extensions/test_imports.rb +94 -0
  119. data/test/integration/extensions/test_jobs.rb +52 -0
  120. data/test/integration/extensions/test_matchers.rb +134 -0
  121. data/test/integration/extensions/test_projects.rb +82 -0
  122. data/test/integration/extensions/test_resources.rb +150 -0
  123. data/test/integration/extensions/test_results.rb +89 -0
  124. data/test/integration/extensions/test_scenarios.rb +88 -0
  125. data/test/integration/extensions/test_transformations.rb +113 -0
  126. data/test/integration/extensions/test_transformers.rb +80 -0
  127. data/test/integration/test_field.rb +45 -0
  128. data/test/integration/test_import.rb +78 -0
  129. data/test/integration/test_running_scenarios.rb +379 -0
  130. data/test/integration/test_transformation.rb +56 -0
  131. data/test/integration/test_transforming.rb +154 -0
  132. data/test/table_sets.rb +76 -0
  133. data/test/unit/models/test_common_model.rb +130 -0
  134. data/test/unit/models/test_comparison.rb +619 -0
  135. data/test/unit/models/test_connection.rb +115 -0
  136. data/test/unit/models/test_field.rb +99 -0
  137. data/test/unit/models/test_import.rb +130 -0
  138. data/test/unit/models/test_job.rb +115 -0
  139. data/test/unit/models/test_matcher.rb +82 -0
  140. data/test/unit/models/test_project.rb +102 -0
  141. data/test/unit/models/test_resource.rb +564 -0
  142. data/test/unit/models/test_result.rb +90 -0
  143. data/test/unit/models/test_scenario.rb +199 -0
  144. data/test/unit/models/test_transformation.rb +193 -0
  145. data/test/unit/models/test_transformer.rb +188 -0
  146. data/test/unit/test_base.rb +60 -0
  147. data/test/unit/test_data_uploader.rb +27 -0
  148. data/test/unit/test_database.rb +23 -0
  149. data/test/unit/test_helpers.rb +58 -0
  150. data/test/unit/test_logger.rb +10 -0
  151. data/test/unit/test_models.rb +12 -0
  152. data/test/unit/test_runner.rb +76 -0
  153. data/test/unit/test_scheduler.rb +66 -0
  154. data/uploads/.gitignore +2 -0
  155. data/vendor/java/.gitignore +5 -0
  156. data/webroot/public/css/960.css +1 -0
  157. data/webroot/public/css/dataTables.css +1057 -0
  158. data/webroot/public/css/jquery-ui.css +572 -0
  159. data/webroot/public/css/jquery.treeview.css +68 -0
  160. data/webroot/public/css/reset.css +1 -0
  161. data/webroot/public/css/style.css +504 -0
  162. data/webroot/public/css/text.css +1 -0
  163. data/webroot/public/favicon.ico +0 -0
  164. data/webroot/public/images/12_col.gif +0 -0
  165. data/webroot/public/images/16_col.gif +0 -0
  166. data/webroot/public/images/add.png +0 -0
  167. data/webroot/public/images/ajax-loader.gif +0 -0
  168. data/webroot/public/images/cog.png +0 -0
  169. data/webroot/public/images/coupler.png +0 -0
  170. data/webroot/public/images/foo.png +0 -0
  171. data/webroot/public/images/hammer.png +0 -0
  172. data/webroot/public/images/header.png +0 -0
  173. data/webroot/public/images/home.gif +0 -0
  174. data/webroot/public/images/jobs.gif +0 -0
  175. data/webroot/public/images/sidebar-bottom.png +0 -0
  176. data/webroot/public/images/sidebar.png +0 -0
  177. data/webroot/public/images/treeview-default-line.gif +0 -0
  178. data/webroot/public/images/treeview-default.gif +0 -0
  179. data/webroot/public/images/ui-anim_basic_16x16.gif +0 -0
  180. data/webroot/public/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  181. data/webroot/public/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  182. data/webroot/public/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  183. data/webroot/public/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  184. data/webroot/public/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  185. data/webroot/public/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  186. data/webroot/public/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  187. data/webroot/public/images/ui-bg_highlight-hard_30_565356_1x100.png +0 -0
  188. data/webroot/public/images/ui-bg_highlight-hard_75_888588_1x100.png +0 -0
  189. data/webroot/public/images/ui-bg_highlight-soft_30_6e3b3a_1x100.png +0 -0
  190. data/webroot/public/images/ui-bg_highlight-soft_35_8e8b8e_1x100.png +0 -0
  191. data/webroot/public/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  192. data/webroot/public/images/ui-icons_222222_256x240.png +0 -0
  193. data/webroot/public/images/ui-icons_2e83ff_256x240.png +0 -0
  194. data/webroot/public/images/ui-icons_454545_256x240.png +0 -0
  195. data/webroot/public/images/ui-icons_888888_256x240.png +0 -0
  196. data/webroot/public/images/ui-icons_cd0a0a_256x240.png +0 -0
  197. data/webroot/public/images/ui-icons_ffffff_256x240.png +0 -0
  198. data/webroot/public/js/ajaxupload.js +673 -0
  199. data/webroot/public/js/application.js +40 -0
  200. data/webroot/public/js/jquery-ui.combobox.js +98 -0
  201. data/webroot/public/js/jquery-ui.js +9867 -0
  202. data/webroot/public/js/jquery-ui.min.js +559 -0
  203. data/webroot/public/js/jquery.dataTables.min.js +587 -0
  204. data/webroot/public/js/jquery.min.js +154 -0
  205. data/webroot/public/js/jquery.timeago.js +140 -0
  206. data/webroot/public/js/jquery.tooltip.min.js +19 -0
  207. data/webroot/public/js/jquery.treeview.min.js +15 -0
  208. data/webroot/public/js/resource.js +11 -0
  209. data/webroot/public/js/results.js +56 -0
  210. data/webroot/public/js/transformations.js +95 -0
  211. data/webroot/views/connections/index.erb +5 -0
  212. data/webroot/views/connections/list.erb +34 -0
  213. data/webroot/views/connections/new.erb +55 -0
  214. data/webroot/views/connections/show.erb +36 -0
  215. data/webroot/views/imports/edit.erb +60 -0
  216. data/webroot/views/imports/form.erb +81 -0
  217. data/webroot/views/imports/new.erb +89 -0
  218. data/webroot/views/index.erb +12 -0
  219. data/webroot/views/jobs/index.erb +7 -0
  220. data/webroot/views/jobs/list.erb +24 -0
  221. data/webroot/views/layout.erb +38 -0
  222. data/webroot/views/matchers/form.erb +250 -0
  223. data/webroot/views/matchers/list.erb +32 -0
  224. data/webroot/views/projects/form.erb +14 -0
  225. data/webroot/views/projects/index.erb +96 -0
  226. data/webroot/views/projects/show.erb +24 -0
  227. data/webroot/views/resources/edit.erb +88 -0
  228. data/webroot/views/resources/index.erb +5 -0
  229. data/webroot/views/resources/list.erb +27 -0
  230. data/webroot/views/resources/new.erb +121 -0
  231. data/webroot/views/resources/show.erb +86 -0
  232. data/webroot/views/resources/transform.erb +2 -0
  233. data/webroot/views/results/csv.erb +12 -0
  234. data/webroot/views/results/details.erb +15 -0
  235. data/webroot/views/results/index.erb +2 -0
  236. data/webroot/views/results/list.erb +22 -0
  237. data/webroot/views/results/record.erb +24 -0
  238. data/webroot/views/results/show.erb +68 -0
  239. data/webroot/views/scenarios/index.erb +5 -0
  240. data/webroot/views/scenarios/list.erb +20 -0
  241. data/webroot/views/scenarios/new.erb +99 -0
  242. data/webroot/views/scenarios/run.erb +2 -0
  243. data/webroot/views/scenarios/show.erb +50 -0
  244. data/webroot/views/sidebar.erb +106 -0
  245. data/webroot/views/transformations/create.erb +115 -0
  246. data/webroot/views/transformations/for.erb +16 -0
  247. data/webroot/views/transformations/index.erb +2 -0
  248. data/webroot/views/transformations/list.erb +29 -0
  249. data/webroot/views/transformations/new.erb +126 -0
  250. data/webroot/views/transformations/preview.erb +46 -0
  251. data/webroot/views/transformers/edit.erb +6 -0
  252. data/webroot/views/transformers/form.erb +58 -0
  253. data/webroot/views/transformers/index.erb +2 -0
  254. data/webroot/views/transformers/list.erb +25 -0
  255. data/webroot/views/transformers/new.erb +5 -0
  256. data/webroot/views/transformers/preview.erb +23 -0
  257. data/webroot/views/transformers/show.erb +0 -0
  258. metadata +558 -0
@@ -0,0 +1,42 @@
1
+ module Coupler
2
+ module Models
3
+ class Job < Sequel::Model
4
+ include CommonModel
5
+
6
+ many_to_one :resource
7
+ many_to_one :scenario
8
+
9
+ def percent_completed
10
+ total > 0 ? completed * 100 / total : 0
11
+ end
12
+
13
+ def execute
14
+ Logger.instance.info("Starting job #{id} (#{name})")
15
+ case name
16
+ when 'transform'
17
+ update(:status => 'running', :started_at => Time.now, :total => resource.source_dataset_count)
18
+
19
+ new_status = 'failed'
20
+ begin
21
+ resource.transform! { |n| update(:completed => completed + n) }
22
+ new_status = 'done'
23
+ ensure
24
+ update(:status => new_status, :completed_at => Time.now)
25
+ end
26
+
27
+ when 'run_scenario'
28
+ update(:status => 'running', :started_at => Time.now)
29
+
30
+ new_status = 'failed'
31
+ begin
32
+ scenario.run!
33
+ new_status = 'done'
34
+ ensure
35
+ update(:status => new_status, :completed_at => Time.now)
36
+ end
37
+ end
38
+ Logger.instance.info("Job #{id} (#{name}) finished")
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,17 @@
1
+ module Coupler
2
+ module Models
3
+ module Jobify
4
+ def self.included(base)
5
+ base.one_to_many :jobs
6
+ base.one_to_many(:running_jobs, {
7
+ :class => "Coupler::Models::Job",
8
+ :conditions => { :status => 'running' }, :read_only => true
9
+ })
10
+ base.one_to_many(:scheduled_jobs, {
11
+ :class => "Coupler::Models::Job",
12
+ :conditions => { :status => 'scheduled' }, :read_only => true
13
+ })
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,36 @@
1
+ module Coupler
2
+ module Models
3
+ class Matcher < Sequel::Model
4
+ include CommonModel
5
+ many_to_one :scenario
6
+ one_to_many :comparisons
7
+
8
+ plugin :nested_attributes
9
+ nested_attributes :comparisons, :destroy => true
10
+
11
+ def cross_match?
12
+ comparisons.any? { |c| c.cross_match? }
13
+ end
14
+
15
+ private
16
+ def validate
17
+ super
18
+ # use comparisons instead of comparisons_dataset, because the
19
+ # comparisons aren't created yet
20
+ result = comparisons.any? do |comparison|
21
+ comparison.lhs_type == "field" && comparison.rhs_type == "field"
22
+ end
23
+ if !result
24
+ errors.add(:base, "At least one field-to-field comparison is required.")
25
+ end
26
+ end
27
+
28
+ def after_save
29
+ super
30
+ s = scenario
31
+ s.set_linkage_type
32
+ s.save
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,40 @@
1
+ module Coupler
2
+ module Models
3
+ class Project < Sequel::Model
4
+ include CommonModel
5
+ one_to_many :resources
6
+ one_to_many :scenarios
7
+
8
+ def local_database(&block)
9
+ Sequel.connect(local_connection_string, {
10
+ :loggers => [Coupler::Logger.instance],
11
+ :max_connections => 50,
12
+ :pool_timeout => 60
13
+ }, &block)
14
+ end
15
+
16
+ private
17
+ def local_connection_string
18
+ Base.connection_string("project_#{id}")
19
+ end
20
+
21
+ def before_validation
22
+ super
23
+ self.slug ||= name.downcase.gsub(/\s+/, "_") if name
24
+ end
25
+
26
+ def validate
27
+ super
28
+ validates_presence :name
29
+ validates_unique :name, :slug
30
+ end
31
+
32
+ def after_destroy
33
+ super
34
+ FileUtils.rm(Dir[Base.db_path("project_#{id}")+".*"], :force => true)
35
+ resources_dataset.each { |r| r.delete_versions_on_destroy = self.delete_versions_on_destroy; r.destroy }
36
+ scenarios_dataset.each { |s| s.delete_versions_on_destroy = self.delete_versions_on_destroy; s.destroy }
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,287 @@
1
+ module Coupler
2
+ module Models
3
+ class Resource < Sequel::Model
4
+ LIMIT = 10000
5
+
6
+ include CommonModel
7
+ include Jobify
8
+
9
+ many_to_one :connection
10
+ many_to_one :project
11
+ many_to_one :import
12
+ one_to_many :transformations
13
+ one_to_many :fields
14
+ one_to_many :selected_fields, {
15
+ :class => 'Coupler::Models::Field', :key => 'resource_id',
16
+ :conditions => {:is_selected => 1}, :read_only => true
17
+ }
18
+
19
+ plugin :nested_attributes
20
+ nested_attributes(:connection, :destroy => false)
21
+ nested_attributes(:fields, :destroy => false, :fields => [:is_selected]) { |h| !(h.has_key?('id') || h.has_key?(:id)) }
22
+
23
+ attr_accessor :resource_type
24
+
25
+ def self.count_by_project
26
+ dataset.naked.group_and_count(:project_id).to_hash(:project_id, :count)
27
+ end
28
+
29
+ def import=(*args)
30
+ result = super
31
+ if new?
32
+ self.project = import.project
33
+ self.name = import.name
34
+ self.table_name = "import_#{import.id}"
35
+ end
36
+ result
37
+ end
38
+
39
+ def name=(*args)
40
+ result = super
41
+ if new?
42
+ self.slug ||= name.downcase.gsub(/\s+/, "_")
43
+ end
44
+ result
45
+ end
46
+
47
+ def source_database(&block)
48
+ if import
49
+ project.local_database(&block)
50
+ else
51
+ connection.database(&block)
52
+ end
53
+ end
54
+
55
+ def source_dataset
56
+ if block_given?
57
+ source_database do |database|
58
+ columns = fields_dataset.filter(:is_selected => true, :is_generated => false).collect { |f| f.name.to_sym }
59
+ yield database[table_name.to_sym].select(*columns.collect(&:to_sym))
60
+ end
61
+ else
62
+ database = source_database
63
+ columns = fields_dataset.filter(:is_selected => true, :is_generated => false).collect { |f| f.name.to_sym }
64
+ database[table_name.to_sym].select(*columns.collect(&:to_sym))
65
+ end
66
+ end
67
+
68
+ def source_dataset_count
69
+ count = nil
70
+ source_dataset { |ds| count = ds.count }
71
+ count
72
+ end
73
+
74
+ def source_schema
75
+ schema = nil
76
+ source_database { |db| schema = db.schema(table_name.to_sym) }
77
+ schema
78
+ end
79
+
80
+ def local_dataset
81
+ if block_given?
82
+ project.local_database do |database|
83
+ ds = database[:"resource_#{id}"]
84
+ yield ds
85
+ end
86
+ else
87
+ database = project.local_database
88
+ database[:"resource_#{id}"]
89
+ end
90
+ end
91
+
92
+ def final_database(&block)
93
+ if transformations_dataset.count == 0
94
+ source_database(&block)
95
+ else
96
+ project.local_database(&block)
97
+ end
98
+ end
99
+
100
+ def final_dataset(&block)
101
+ if transformations_dataset.count == 0
102
+ source_dataset(&block)
103
+ else
104
+ local_dataset(&block)
105
+ end
106
+ end
107
+
108
+ def status
109
+ if transformed_with.to_s != transformation_ids.join(",") || transformations_dataset.filter("updated_at > ?", transformed_at).count > 0
110
+ "out_of_date"
111
+ else
112
+ "ok"
113
+ end
114
+ end
115
+
116
+ def scenarios
117
+ Scenario.filter(["resource_1_id = ? OR resource_2_id = ?", id, id]).all
118
+ end
119
+
120
+ def refresh_fields!
121
+ fields_dataset.update(:local_db_type => nil, :local_type => nil)
122
+ transformations_dataset.order(:position).each do |transformation|
123
+ if transformation.source_field_id == transformation.result_field_id
124
+ source_field = transformation.source_field
125
+ changes = transformation.field_changes[source_field.id]
126
+ source_field.update({
127
+ :local_db_type => changes[:db_type] || source_field[:db_type],
128
+ :local_type => changes[:type] || source_field[:type]
129
+ })
130
+ end
131
+ end
132
+ end
133
+
134
+ def transform!(&progress)
135
+ t_ids = transformation_ids.join(",")
136
+ create_local_table!
137
+ _transform(&progress)
138
+ self.update({
139
+ :transformed_at => Time.now,
140
+ :transformed_with => t_ids
141
+ })
142
+ end
143
+
144
+ def preview_transformation(transformation)
145
+ result = []
146
+ _iterate_over_source_and_transform(50) { |r| result << r }
147
+ result.each_index do |i|
148
+ begin
149
+ after = transformation.transform(result[i].dup)
150
+ result[i] = { :before => result[i], :after => after }
151
+ rescue Exception => e # yes, I know rescuing Exception is "bad"
152
+ return e
153
+ end
154
+ end
155
+ fields = result[0][:before].keys | result[0][:after].keys
156
+ { :fields => fields, :data => result }
157
+ end
158
+
159
+ def primary_key_sym
160
+ primary_key_name.to_sym
161
+ end
162
+
163
+ private
164
+ def transformation_ids
165
+ transformations_dataset.select(:id).order(:id).all.collect(&:id)
166
+ end
167
+
168
+ def local_connection_string
169
+ Base.connection_string("project_#{project.id}")
170
+ end
171
+
172
+ def create_fields
173
+ source_schema.each do |(name, info)|
174
+ add_field({
175
+ :name => name,
176
+ :type => info[:type],
177
+ :db_type => info[:db_type],
178
+ :is_primary_key => info[:primary_key]
179
+ })
180
+ end
181
+ end
182
+
183
+ def create_local_table!
184
+ fields = selected_fields_dataset.order(:id).all
185
+ cols = fields.collect { |f| f.local_column_options }
186
+ project.local_database do |l_db|
187
+ # create intermediate table
188
+ l_db.create_table!("resource_#{id}") do
189
+ columns.push(*cols)
190
+ end
191
+ end
192
+ end
193
+
194
+ def _transform(&progress)
195
+ local_dataset do |l_ds|
196
+ field_names = selected_fields_dataset.order(:id).naked.select(:name).map { |r| r[:name].to_sym }
197
+ buffer = ImportBuffer.new(field_names, l_ds, &progress)
198
+ _iterate_over_source_and_transform { |r| buffer.add(r) }
199
+ buffer.flush
200
+ end
201
+ end
202
+
203
+ def _iterate_over_source_and_transform(total = nil)
204
+ tw = ThreadsWait.new
205
+ transformations = transformations_dataset.order(:position).all
206
+ source_dataset do |s_ds|
207
+ total ||= s_ds.count
208
+ limit = (total && total < LIMIT) ? total : LIMIT
209
+ offset = 0
210
+ count = 0
211
+ while count < total
212
+ s_ds = s_ds.limit(limit, offset)
213
+ offset += limit
214
+ count += limit
215
+
216
+ thr = Thread.new(s_ds) do |ds|
217
+ ds.each do |row|
218
+ hash = transformations.inject(row) { |x, t| t.transform(x) }
219
+ yield hash
220
+ end
221
+ end
222
+ thr.abort_on_exception = true
223
+ tw.join_nowait(thr)
224
+ tw.next_wait if tw.threads.length == 10
225
+ end
226
+ tw.all_waits
227
+ end
228
+ end
229
+
230
+ def validate
231
+ super
232
+ validates_presence [:project_id, :name]
233
+ validates_presence :slug
234
+ validates_unique [:name, :project_id], [:slug, :project_id]
235
+ validates_presence [:table_name]
236
+
237
+ if import.nil? && errors.on(:table_name).nil?
238
+ source_database do |db|
239
+ sym = self.table_name.to_sym
240
+ if !db.tables.include?(sym)
241
+ errors.add(:table_name, "is invalid")
242
+ else
243
+ keys = db.schema(sym).select { |info| info[1][:primary_key] }
244
+ if keys.empty?
245
+ errors.add(:table_name, "doesn't have a primary key")
246
+ elsif keys.length > 1
247
+ errors.add(:table_name, "has too many primary keys")
248
+ end
249
+ end
250
+ end
251
+ end
252
+ end
253
+
254
+ def before_save
255
+ if new?
256
+ # NOTE: I'm doing this instead of using before_create because
257
+ # serialization happens in before_save, which gets called before
258
+ # the before_create hook
259
+ source_database do |db|
260
+ schema = db.schema(table_name.to_sym)
261
+ info = schema.detect { |x| x[1][:primary_key] }
262
+ self.primary_key_name = info[0].to_s
263
+ self.primary_key_type = info[1][:type].to_s
264
+ end
265
+ end
266
+ super
267
+ end
268
+
269
+ def after_create
270
+ super
271
+ create_fields
272
+ end
273
+
274
+ def after_destroy
275
+ super
276
+ tds = transformations_dataset
277
+ if tds.count > 0 && !transformed_at.nil?
278
+ project.local_database do |db|
279
+ db.drop_table(:"resource_#{id}")
280
+ end
281
+ end
282
+ fields_dataset.each { |f| f.delete_versions_on_destroy = self.delete_versions_on_destroy; f.destroy }
283
+ tds.each { |t| t.delete_versions_on_destroy = self.delete_versions_on_destroy; t.destroy }
284
+ end
285
+ end
286
+ end
287
+ end
@@ -0,0 +1,92 @@
1
+ module Coupler
2
+ module Models
3
+ class Result < Sequel::Model
4
+ include CommonModel
5
+ many_to_one :scenario
6
+
7
+ def snapshot
8
+ origin_scenario = Scenario.as_of_version(scenario_id, scenario_version)
9
+ time = origin_scenario.updated_at
10
+ project = Project.as_of_time(origin_scenario.project_id, time)
11
+ resource_1 = Resource.as_of_time(origin_scenario.resource_1_id, time)
12
+ resource_2 = Resource.as_of_time(origin_scenario.resource_2_id, time)
13
+ {
14
+ :project => project,
15
+ :scenario => origin_scenario,
16
+ :resource_1 => resource_1,
17
+ :resource_2 => resource_2
18
+ }
19
+ end
20
+
21
+ def groups_table_name
22
+ :"groups_#{run_number}"
23
+ end
24
+
25
+ def groups_dataset
26
+ if block_given?
27
+ scenario.local_database do |db|
28
+ yield db[groups_table_name]
29
+ end
30
+ nil
31
+ else
32
+ db = scenario.local_database
33
+ db[groups_table_name]
34
+ end
35
+ end
36
+
37
+ def groups_records_table_name
38
+ :"groups_records_#{run_number}"
39
+ end
40
+
41
+ def groups_records_dataset
42
+ if block_given?
43
+ scenario.local_database do |db|
44
+ yield db[groups_records_table_name]
45
+ end
46
+ nil
47
+ else
48
+ db = scenario.local_database
49
+ db[groups_records_table_name]
50
+ end
51
+ end
52
+
53
+ def to_csv
54
+ # grab primary keys and datasets from the scenario's resources
55
+ rdatasets = []
56
+ rkeys = []
57
+ headers = []
58
+ scenario.resources.each do |resource|
59
+ field_names = resource.selected_fields_dataset.select(:name).order(:id).naked.map { |f| f[:name].to_sym }
60
+ rdatasets << resource.final_dataset.select(*field_names)
61
+ rkeys << resource.primary_key_sym
62
+ headers |= field_names
63
+ end
64
+ headers << :coupler_group_id
65
+
66
+ csv = FasterCSV.new("", :headers => true)
67
+ csv << headers
68
+
69
+ groups_records_dataset.each do |group_record|
70
+ # 'which' is either 0 or 1 (or nil for self-linkages)
71
+ # rdatasets can be either length 1 or 2
72
+ which = group_record[:which] || 0
73
+ rdataset = rdatasets[-which]
74
+ rkey = rkeys[-which]
75
+
76
+ record = rdataset[rkey => group_record[:record_id]]
77
+ record[:coupler_group_id] = group_record[:group_id]
78
+ csv << record # slots everything correctly
79
+ end
80
+
81
+ rdatasets.each { |r| r.db.disconnect }
82
+ csv.string
83
+ end
84
+
85
+ private
86
+ def before_save
87
+ super
88
+ self[:scenario_version] = scenario.version
89
+ end
90
+ end
91
+ end
92
+ end