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
data/tasks/db.rake ADDED
@@ -0,0 +1,120 @@
1
+ namespace :db do
2
+ desc "Obliterate the local database"
3
+ task :nuke do
4
+ confirm("This will completely obliterate all of Coupler's databases.")
5
+
6
+ require 'fileutils'
7
+ files = Dir.glob(File.join(Coupler::Base.settings.data_path, "db", "*"))
8
+ FileUtils.rm_rf(files.reject { |d| d =~ /migrate$/ }, :verbose => true)
9
+ end
10
+
11
+ desc "Purge the database"
12
+ task :purge => :environment do
13
+ FileUtils.rm(Dir[Coupler::Base.settings.db_path('coupler')+".*"])
14
+ end
15
+
16
+ desc "Run migrations"
17
+ task :migrate => :environment do
18
+ version = ENV['VERSION']
19
+ Coupler::Database.instance.migrate!(version ? version.to_i : nil)
20
+ end
21
+
22
+ namespace :migrate do
23
+ desc "Reset the database"
24
+ task :reset => ['db:purge', 'db:migrate']
25
+ end
26
+
27
+ desc "Roll the database back a version"
28
+ task :rollback => [:start, :environment] do
29
+ Coupler::Database.instance.rollback!
30
+ end
31
+
32
+ desc "Reset and bootstrap the database"
33
+ task :bootstrap => [:start, :environment] do
34
+ require 'test/factories'
35
+ confirm("This will delete any existing configuration data.") if ENV['COUPLER_ENV'] != "test"
36
+
37
+ Rake::Task["db:migrate:reset"].invoke
38
+
39
+ project = Factory(:project, :name => "Fake")
40
+ connection = Factory(:connection, :name => "Fake")
41
+ resource = Factory(:resource, :name => "People", :project => project, :connection => connection)
42
+ scenario = Factory(:scenario, :name => "First to last", :project => project, :resource_1 => resource)
43
+ matcher = Factory(:matcher, {
44
+ :scenario => scenario,
45
+ :comparisons_attributes => [{
46
+ :lhs_type => 'field', :lhs_value => resource.fields_dataset[:name => 'first_name'].id, :lhs_which => 1,
47
+ :rhs_type => 'field', :rhs_value => resource.fields_dataset[:name => 'last_name'].id, :rhs_which => 2,
48
+ :operator => 'equals'
49
+ }]
50
+ })
51
+ Factory(:transformer, :name => 'strlen', :code => 'value.length', :allowed_types => %w{string}, :result_type => 'integer')
52
+ Factory(:transformer, :name => 'square', :code => 'value * value', :allowed_types => %w{integer}, :result_type => 'integer')
53
+ end
54
+
55
+ require 'forgery'
56
+ require 'sequel'
57
+ desc "Create database with fake data"
58
+ task :fake do
59
+ db = Sequel.connect('jdbc:mysql://localhost/coupler_fake_data?user=coupler&password=cupla')
60
+ db.tables.each { |t| db.drop_table(t) }
61
+ db.create_table :people do
62
+ primary_key :id
63
+ String :first_name
64
+ String :last_name
65
+ Integer :age
66
+ index :first_name
67
+ index :last_name
68
+ end
69
+ db.create_table :pets do
70
+ primary_key :id
71
+ String :name
72
+ String :owner_first_name
73
+ String :owner_last_name
74
+ end
75
+ db.create_table :no_primary_key do
76
+ String :foo
77
+ String :bar
78
+ end
79
+ db.create_table :two_primary_keys do
80
+ String :foo
81
+ String :bar
82
+ primary_key [:foo, :bar]
83
+ end
84
+ db.execute("CREATE TABLE string_primary_key (foo VARCHAR(255), PRIMARY KEY(foo))")
85
+ db.create_table :avast_ye do
86
+ primary_key :arrr
87
+ String :scurvy_dog
88
+ end
89
+ db.create_table :lots_of_nulls do
90
+ primary_key :id
91
+ String :empty
92
+ end
93
+
94
+ people = db[:people]
95
+ pets = db[:pets]
96
+
97
+ num = ENV.has_key?('NUM') ? ENV['NUM'].to_i : 50
98
+ person_records = []
99
+ pet_records = []
100
+ num.times do |i|
101
+ person = [ Forgery(:name).first_name, Forgery(:name).last_name, rand(30) + 20 ]
102
+ pet = [ Forgery(:name).first_name, person[0], person[1] ]
103
+
104
+ person_records << person
105
+ pet_records << pet
106
+ if i % 10000 == 0
107
+ people.import([:first_name, :last_name, :age], person_records)
108
+ pets.import([:name, :owner_first_name, :owner_last_name], pet_records)
109
+ person_records.clear
110
+ pet_records.clear
111
+ end
112
+ end
113
+ people.import([:first_name, :last_name, :age], person_records)
114
+ pets.import([:name, :owner_first_name, :owner_last_name], pet_records)
115
+
116
+ pirates = db[:avast_ye]
117
+ pirates.insert(:scurvy_dog => "Pete")
118
+ pirates.insert(:scurvy_dog => "Westley")
119
+ end
120
+ end
@@ -0,0 +1,12 @@
1
+ task :environment do
2
+ require 'bundler'
3
+ Bundler.setup(:default, :development)
4
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'coupler')
5
+ end
6
+
7
+ namespace :environment do
8
+ task :test do
9
+ ENV['COUPLER_ENV'] = "test"
10
+ Rake::Task["environment"].execute
11
+ end
12
+ end
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+
3
+ require 'jeweler'
4
+ Jeweler::Tasks.new do |gem|
5
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
6
+ gem.name = "coupler"
7
+ gem.homepage = "http://github.com/coupler/coupler"
8
+ gem.license = "MIT"
9
+ gem.summary = %Q{Coupler is a desktop application for linking datasets together}
10
+ gem.description = %Q{Coupler is a (JRuby) desktop application designed to link datasets together}
11
+ gem.email = "jeremy.f.stephens@vanderbilt.edu"
12
+ gem.authors = ["Jeremy Stephens"]
13
+ gem.platform = 'java'
14
+ # dependencies defined in Gemfile
15
+ end
16
+ Jeweler::RubygemsDotOrgTasks.new
17
+
18
+ #require 'rake/testtask'
19
+ #Rake::TestTask.new(:test) do |test|
20
+ #test.libs << 'lib' << 'test'
21
+ #test.pattern = 'test/**/test_*.rb'
22
+ #test.verbose = true
23
+ #end
24
+
25
+ #require 'rcov/rcovtask'
26
+ #Rcov::RcovTask.new do |test|
27
+ #test.libs << 'test'
28
+ #test.pattern = 'test/**/test_*.rb'
29
+ #test.verbose = true
30
+ #test.rcov_opts << '--exclude "gems/*"'
31
+ #end
32
+
33
+ #task :default => :test
34
+
35
+ #require 'rake/rdoctask'
36
+ #Rake::RDocTask.new do |rdoc|
37
+ #version = File.exist?('VERSION') ? File.read('VERSION') : ""
38
+
39
+ #rdoc.rdoc_dir = 'rdoc'
40
+ #rdoc.title = "coupler #{version}"
41
+ #rdoc.rdoc_files.include('README*')
42
+ #rdoc.rdoc_files.include('lib/**/*.rb')
43
+ #end
@@ -0,0 +1,58 @@
1
+ require 'ant'
2
+
3
+ namespace :package do
4
+ build_dir = "build"
5
+ gem_inst_dir = File.join(build_dir, "gems")
6
+ coupler_home = File.join(build_dir, "coupler")
7
+
8
+ coupler_version = nil
9
+
10
+ task :init do
11
+ FileUtils.mkdir_p(gem_inst_dir)
12
+ FileUtils.mkdir_p(coupler_home)
13
+ coupler_version = `git rev-parse HEAD`
14
+ end
15
+
16
+ task :install_gems => :init do
17
+ require 'bundler'
18
+
19
+ begin
20
+ Bundler.settings[:path] = gem_inst_dir
21
+ Bundler.settings[:disable_shared_gems] = '1'
22
+ Bundler.settings.without = [:development]
23
+
24
+ Bundler::Installer.install(Bundler.root, Bundler.definition, { :path => gem_inst_dir, :without => [:development], :local => false })
25
+ ensure
26
+ Bundler.settings[:path] = nil
27
+ Bundler.settings[:disable_shared_gems] = nil
28
+ Bundler.settings.without = []
29
+ ant.delete :dir => '.bundle'
30
+ end
31
+ end
32
+
33
+ task :create_dependency_jar => [:install_gems, :environment] do
34
+ ant.jar :destfile => File.join(build_dir, "coupler-dependencies-#{coupler_version[0..6]}.jar"), :basedir => File.join(gem_inst_dir, 'jruby', '1.8') do
35
+ Coupler::Config.vendor_lib_paths('mysql-connector-java').each do |path|
36
+ zipfileset :src => path
37
+ end
38
+ Coupler::Config.vendor_lib_paths('mysql-connector-mxj').each do |path|
39
+ zipfileset :src => path
40
+ end
41
+ end
42
+ end
43
+
44
+ task :create_coupler_jar => :init do
45
+ ant.jar({
46
+ :destfile => File.join(build_dir, "coupler-#{coupler_version[0..6]}.jar"),
47
+ :basedir => '.',
48
+ :includes => "lib/**/* webroot/**/* db/migrate/* README.rdoc"
49
+ })
50
+ end
51
+
52
+ desc "Create a distributable JAR"
53
+ task :dist => [:create_dependency_jar, :create_coupler_jar]
54
+
55
+ task :clean do
56
+ ant.delete :dir => build_dir
57
+ end
58
+ end
data/tasks/rdoc.rake ADDED
@@ -0,0 +1,13 @@
1
+ require 'rake/rdoctask'
2
+ Rake::RDocTask.new do |rdoc|
3
+ if File.exist?('VERSION')
4
+ version = File.read('VERSION')
5
+ else
6
+ version = ""
7
+ end
8
+
9
+ rdoc.rdoc_dir = 'rdoc'
10
+ rdoc.title = "coupler #{version}"
11
+ rdoc.rdoc_files.include('README*')
12
+ rdoc.rdoc_files.include('lib/**/*.rb')
13
+ end
data/tasks/test.rake ADDED
@@ -0,0 +1,63 @@
1
+ require 'rake/testtask'
2
+
3
+ desc "Run all tests"
4
+ task :test => ['test:unit', 'test:integration']
5
+
6
+ namespace :test do
7
+ Rake::TestTask.new(:unit) do |test|
8
+ test.libs << 'lib' << 'test'
9
+ test.pattern = 'test/unit/**/test_*.rb'
10
+ #test.verbose = true
11
+ test.ruby_opts = %w{--debug}
12
+ end
13
+ task :unit => ['environment:test', 'db:purge', 'db:migrate', 'db:fake']
14
+
15
+ Rake::TestTask.new(:integration) do |test|
16
+ test.libs << 'lib' << 'test'
17
+ test.pattern = 'test/integration/**/test_*.rb'
18
+ #test.verbose = true
19
+ test.ruby_opts = %w{--debug}
20
+ end
21
+ task :integration => ['environment:test', 'db:purge', 'db:migrate', 'db:fake']
22
+ end
23
+
24
+ begin
25
+ require 'rcov/rcovtask'
26
+ Rcov::RcovTask.new do |test|
27
+ test.libs << 'test'
28
+ test.pattern = 'test/**/test_*.rb'
29
+ test.verbose = true
30
+ end
31
+ rescue LoadError
32
+ task :rcov do
33
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
34
+ end
35
+ end
36
+
37
+ begin
38
+ require 'cucumber/rake/task'
39
+ require 'git'
40
+
41
+ Cucumber::Rake::Task.new(:features)
42
+ task :features => ['environment:test', 'db:purge', 'db:migrate', 'db:fake']
43
+
44
+ Cucumber::Rake::Task.new(:features_html, "Run Cucumber features with HTML output") do |t|
45
+ outfile = "pages/_posts/#{Date.today.to_s}-features.html"
46
+ t.cucumber_opts = "--format Butternut::Formatter --out #{outfile} features"
47
+ end
48
+ task :features_html => ['environment:test', 'db:purge', 'db:migrate', 'db:fake']
49
+
50
+ desc "Update github pages for coupler"
51
+ task :update_pages => :features_html do
52
+ repos = Git.open("pages")
53
+ repos.add('.')
54
+ repos.commit("Added post (from Rake task)")
55
+ repos.push
56
+ end
57
+
58
+ rescue LoadError
59
+ task :features do
60
+ abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
61
+ end
62
+ end
63
+
data/tasks/vendor.rake ADDED
@@ -0,0 +1,43 @@
1
+ namespace :vendor do
2
+ desc "Fetch vendor packages"
3
+ task :fetch do
4
+ # FIXME: There is some fast and loose code right here.
5
+ require 'lib/coupler/config'
6
+
7
+ Coupler::Config.each_vendor_lib do |name, info|
8
+ type_dir = File.join(Dir.pwd, 'vendor', info[:type])
9
+ destination = File.join(type_dir, info[:dir] || info[:filename])
10
+ if File.exist?(destination)
11
+ puts "Not downloading #{name}"
12
+ next
13
+ end
14
+
15
+ puts "Downloading #{name}..."
16
+ io = info[:filename] ? File.open(destination, "w") : Tempfile.new(name)
17
+ io.write(open(info[:url]).read)
18
+ io.close
19
+
20
+ if info[:uncompress] != false
21
+ case info[:filetype]
22
+ when "tarball"
23
+ `tar -xzf #{io.path} -C #{type_dir}`
24
+ when "jar", "zip"
25
+ FileUtils.mkdir(destination)
26
+ Dir.chdir(destination) do
27
+ if info[:filetype] == "jar"
28
+ `jar -xf #{io.path}`
29
+ else
30
+ `unzip #{io.path}`
31
+ end
32
+ end
33
+ end
34
+ io.unlink
35
+ end
36
+ if info[:filename] && info[:symlink]
37
+ Dir.chdir(type_dir) do
38
+ FileUtils.ln_sf("./#{info[:filename]}", info[:symlink], :verbose => true)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
data/test/README.txt ADDED
@@ -0,0 +1,6 @@
1
+ Test notes
2
+ ==========
3
+
4
+ * You need a local MySQL database running on port 3306 with the database
5
+ 'coupler_fake_data' that the user 'coupler' can access with password
6
+ 'cupla'.
data/test/config.yml ADDED
@@ -0,0 +1,9 @@
1
+ h2:
2
+ :path: <%= File.join(File.dirname(__FILE__), '..', 'db', 'test', 'coupler_fake_data') %>
3
+
4
+ mysql:
5
+ :host: localhost
6
+ :port: 3306
7
+ :username: coupler
8
+ :password: cupla
9
+ :database_name: coupler_fake_data
@@ -0,0 +1,221 @@
1
+ require File.dirname(__FILE__) + '/../../helper'
2
+
3
+ module Coupler
4
+ module Models
5
+ class TestImport < Test::Unit::TestCase
6
+ def test_sequel_model
7
+ assert_equal ::Sequel::Model, Models::Import.superclass
8
+ assert_equal :imports, Import.table_name
9
+ end
10
+
11
+ def test_many_to_one_project
12
+ assert_respond_to Models::Import.new, :project
13
+ end
14
+
15
+ def test_gets_name_from_original_filename
16
+ import = Import.new(:data => fixture_file_upload('people.csv'))
17
+ assert_equal "People", import.name
18
+ end
19
+
20
+ def test_preview_with_headers
21
+ import = Factory.build(:import)
22
+ preview = import.preview
23
+ assert_kind_of Array, preview
24
+ assert_equal 50, preview.length
25
+ assert_kind_of Array, preview[0]
26
+ assert_not_equal %w{id first_name last_name age}, preview[0]
27
+ end
28
+
29
+ def test_discovers_field_names_and_types
30
+ import = Factory.build(:import, :data => fixture_file_upload("people.csv"))
31
+ expected_types = %w{integer string string integer}
32
+ expected_names = %w{id first_name last_name age}
33
+ assert_equal expected_names, import.field_names
34
+ assert_equal expected_types, import.field_types
35
+ assert_equal "id", import.primary_key_name
36
+ assert import.has_headers
37
+ end
38
+
39
+ def test_discover_for_csv_with_no_headers
40
+ tempfile = Tempfile.new('coupler-import')
41
+ tempfile.write("foo,bar,1,2,3\njunk,blah,4,5,6")
42
+ tempfile.close
43
+ import = Factory.build(:import, :data => file_upload(tempfile.path))
44
+ expected_types = %w{string string integer integer integer}
45
+ assert_equal expected_types, import.field_types
46
+ assert_nil import.field_names
47
+ assert_nil import.primary_key_name
48
+ assert !import.has_headers
49
+ end
50
+
51
+ def test_import!
52
+ project = Factory(:project)
53
+ import = Factory(:import, :data => fixture_file_upload("people.csv"), :project => project)
54
+ now = Time.now
55
+ Timecop.freeze(now) do
56
+ assert import.import!
57
+ assert_equal now, import.occurred_at
58
+ end
59
+
60
+ project.local_database do |db|
61
+ name = :"import_#{import.id}"
62
+ assert db.tables.include?(name)
63
+ schema = db.schema(name)
64
+ assert_equal [:integer, true], schema.assoc(:id)[1].values_at(:type, :primary_key)
65
+ assert_equal :string, schema.assoc(:first_name)[1][:type]
66
+ assert_equal :string, schema.assoc(:last_name)[1][:type]
67
+ assert_equal :integer, schema.assoc(:age)[1][:type]
68
+
69
+ ds = db[name]
70
+ assert_equal 50, ds.count
71
+ end
72
+ end
73
+
74
+ def test_requires_field_names
75
+ import = Factory.build(:import, :data => fixture_file_upload('no-headers.csv'))
76
+ assert_nil import.field_names
77
+ assert !import.valid?
78
+ end
79
+
80
+ def test_requires_primary_key_name
81
+ import = Factory.build(:import, :data => fixture_file_upload('no-headers.csv'))
82
+ import.field_names = %w{id first_name last_name age}
83
+ assert !import.valid?
84
+ end
85
+
86
+ def test_requires_valid_primary_key_name
87
+ import = Factory.build(:import, :data => fixture_file_upload('no-headers.csv'))
88
+ import.field_names = %w{id first_name last_name age}
89
+ import.primary_key_name = "foo"
90
+ assert !import.valid?
91
+ end
92
+
93
+ def test_flags_duplicate_primary_keys
94
+ tempfile = Tempfile.new('coupler-import')
95
+ tempfile.write("id,foo,bar\n1,abc,def\n2,ghi,jkl\n2,mno,pqr")
96
+ tempfile.close
97
+
98
+ project = Factory(:project)
99
+ import = Factory(:import, :data => file_upload(tempfile.path), :project => project)
100
+
101
+ now = Time.at(Time.now.to_i) # dumb usecs
102
+ Timecop.freeze(now) do
103
+ assert !import.import!
104
+ assert import.has_duplicate_keys
105
+ assert_equal now, import.occurred_at, "now: %d-%d; occurred_at: %d-%d" % [now.to_i, now.usec, import.occurred_at.to_i, import.occurred_at.usec]
106
+ end
107
+
108
+ project.local_database do |db|
109
+ ds = db[:"import_#{import.id}"]
110
+ assert ds.filter(:id => 2).select_map(:dup_key_count).all?
111
+ end
112
+ end
113
+
114
+ def test_requires_unique_field_names
115
+ tempfile = Tempfile.new('coupler-import')
116
+ tempfile.write("id,foo,foo\n1,abc,def\n2,ghi,jkl\n3,mno,pqr")
117
+ tempfile.close
118
+
119
+ import = Factory.build(:import, :data => file_upload(tempfile.path))
120
+ assert !import.valid?
121
+ end
122
+
123
+ def test_requires_unused_resource_name
124
+ project = Factory(:project)
125
+ resource = Factory(:resource, :name => "Foo", :project => project)
126
+ import = Factory.build(:import, :data => fixture_file_upload('people.csv'), :name => "Foo", :project => project)
127
+ assert !import.valid?
128
+ end
129
+
130
+ def test_dataset
131
+ project = Factory(:project)
132
+ import = Factory(:import, :project => project)
133
+ import.import!
134
+ project.local_database do |db|
135
+ import.dataset do |ds|
136
+ expected = db[:"import_#{import.id}"]
137
+ assert_equal expected.first_source, ds.first_source
138
+ assert_equal db.uri, ds.db.uri
139
+ end
140
+ end
141
+ end
142
+
143
+ def test_filenames_dont_conflict
144
+ dir = make_tmpdir
145
+ filename = File.join(dir, "people.csv")
146
+ tempfile = File.open(filename, 'w')
147
+ tempfile.puts("id,foo,bar\n")
148
+ tempfile.puts("abc,def,ghi\n")
149
+ tempfile.close
150
+ import_1 = Factory(:import, :data => fixture_file_upload('people.csv'))
151
+ import_2 = Factory(:import, :data => file_upload(filename))
152
+ assert_not_equal import_1.data.store_path, import_2.data.store_path
153
+ end
154
+
155
+ def test_repair_duplicate_keys
156
+ project = Factory(:project)
157
+ import = Factory(:import, :data => fixture_file_upload('duplicate-keys.csv'), :project => project)
158
+ import.import!
159
+
160
+ import.repair_duplicate_keys!(nil)
161
+ import.dataset do |ds|
162
+ assert !ds.columns!.include?(:dup_key_count)
163
+ assert_equal 1, ds.filter(:id => 2).count
164
+ assert_equal 1, ds.filter(:id => 4).count
165
+ end
166
+ project.local_database do |db|
167
+ assert db.schema(:"import_#{import.id}").assoc(:id)[1][:primary_key]
168
+ end
169
+ end
170
+
171
+ def test_repair_duplicate_keys_with_deletions
172
+ tempfile = Tempfile.new('coupler-import')
173
+ tempfile.write("id,foo,bar\n1,2,3\n1,4,5\n1,6,7\n123,456,789\n")
174
+ tempfile.close
175
+
176
+ project = Factory(:project)
177
+ #puts "Project: #{project.id}"
178
+ import = Factory(:import, :data => file_upload(tempfile.path), :project => project)
179
+ #puts "Import: #{import.id}"
180
+ import.import!
181
+
182
+ import.repair_duplicate_keys!({"1" => ["1"]})
183
+ import.dataset do |ds|
184
+ assert !ds.columns!.include?(:dup_key_count)
185
+ assert_equal 1, ds.filter(:id => 1).count
186
+ assert_equal 1, ds.filter(:id => 124).count
187
+ assert_equal 0, ds.filter(:id => 125).count
188
+ end
189
+ project.local_database do |db|
190
+ assert db.schema(:"import_#{import.id}").assoc(:id)[1][:primary_key]
191
+ end
192
+ end
193
+
194
+ def test_discover_fields_for_csv_with_headers_and_varying_number_of_fields
195
+ tempfile = Tempfile.new('coupler-import')
196
+ tempfile.write("id,foo,bar\n1,2,3\n1,4,5\n1,6,7,\n123,456,789,,\n")
197
+ tempfile.close
198
+
199
+ import = Factory.build(:import, :data => file_upload(tempfile.path))
200
+ expected_types = %w{integer integer integer string string}
201
+ expected_names = %w{id foo bar}
202
+ assert_equal expected_names, import.field_names
203
+ assert_equal expected_types, import.field_types
204
+ assert_equal "id", import.primary_key_name
205
+ assert import.has_headers
206
+ end
207
+
208
+ def test_importing_bad_integers
209
+ tempfile = Tempfile.new('coupler-import')
210
+ tempfile.write(%{id,foo,bar\n1,2,3\n2,4,5\n3,6,7\n4,456,""\n})
211
+ tempfile.close
212
+
213
+ project = Factory(:project)
214
+ import = Factory(:import, :data => file_upload(tempfile.path), :project => project)
215
+ assert_nothing_raised do
216
+ import.import!
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end
data/test/factories.rb ADDED
@@ -0,0 +1,91 @@
1
+ require 'factory_girl'
2
+
3
+ Factory.sequence(:connection_name) { |n| "Connection #{n}" }
4
+ Factory.define :connection, :class => Coupler::Models::Connection do |c|
5
+ c.name { Factory.next(:connection_name) }
6
+ c.adapter "h2"
7
+ end
8
+
9
+ Factory.sequence(:resource_name) { |n| "Resource #{n}" }
10
+ Factory.define :resource, :class => Coupler::Models::Resource do |r|
11
+ r.name { Factory.next(:resource_name) }
12
+ r.table_name "people"
13
+ r.association :connection
14
+ r.association :project
15
+ end
16
+
17
+ Factory.define :project, :class => Coupler::Models::Project do |d|
18
+ d.sequence(:name) { |n| "Project #{n}" }
19
+ end
20
+
21
+ Factory.define :transformation, :class => Coupler::Models::Transformation do |t|
22
+ t.association :transformer
23
+ t.association :resource
24
+ t.source_field do |record|
25
+ record.resource.fields_dataset.first rescue nil
26
+ end
27
+ end
28
+
29
+ Factory.define :scenario, :class => Coupler::Models::Scenario do |s|
30
+ s.sequence(:name) { |n| "Scenario #{n}" }
31
+ s.association :project
32
+ s.resource_1 { |x| x.project ? Factory(:resource, :project => x.project) : nil }
33
+ end
34
+
35
+ Factory.define :matcher, :class => Coupler::Models::Matcher do |m|
36
+ m.association :scenario
37
+ m.comparisons_attributes do |record|
38
+ resources = record.scenario.resources
39
+ [{
40
+ 'lhs_type' => 'field', 'raw_lhs_value' => resources[0].fields_dataset.order('id DESC').last.id, 'lhs_which' => 1,
41
+ 'rhs_type' => 'field', 'raw_rhs_value' => resources[-1].fields_dataset.order('id DESC').last.id, 'rhs_which' => 2,
42
+ 'operator' => 'equals'
43
+ }]
44
+ end
45
+ end
46
+
47
+ Factory.define :result, :class => Coupler::Models::Result do |r|
48
+ r.association :scenario
49
+ end
50
+
51
+ Factory.define :resource_job, :class => Coupler::Models::Job do |j|
52
+ j.name 'transform'
53
+ j.status 'scheduled'
54
+ j.association :resource
55
+ end
56
+
57
+ Factory.define :scenario_job, :class => Coupler::Models::Job do |j|
58
+ j.name 'run_scenario'
59
+ j.status 'scheduled'
60
+ j.association :scenario
61
+ end
62
+
63
+ Factory.define :transformer, :class => Coupler::Models::Transformer do |t|
64
+ t.sequence(:name) { |n| "Transformer #{n}" }
65
+ t.code "value"
66
+ t.allowed_types { |x| %w{string integer datetime} }
67
+ t.result_type "same"
68
+ end
69
+
70
+ Factory.define :field, :class => Coupler::Models::Field do |f|
71
+ f.sequence(:name) { |n| "field_#{n}" }
72
+ f.add_attribute :type, "integer"
73
+ f.db_type "int(11)"
74
+ f.is_primary_key 0
75
+ f.is_selected 1
76
+ f.association :resource
77
+ end
78
+
79
+ Factory.define :comparison, :class => Coupler::Models::Comparison do |c|
80
+ c.association :matcher
81
+ c.lhs_type "integer"
82
+ c.raw_lhs_value 1
83
+ c.rhs_type "integer"
84
+ c.raw_rhs_value 1
85
+ c.operator "equals"
86
+ end
87
+
88
+ Factory.define :import, :class => Coupler::Models::Import do |i|
89
+ i.data { File.open(File.join(File.dirname(__FILE__), 'fixtures', 'people.csv')) }
90
+ i.association :project
91
+ end
@@ -0,0 +1,5 @@
1
+ id,foo,bar
2
+ 1,abc,def
3
+ 2,ghi,jkl
4
+ 2,mno,pqr
5
+ 3,stu,vwx