coupler 0.0.7-java → 0.0.8-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.
- data/VERSION +1 -1
- data/coupler.gemspec +11 -2
- data/db/migrate/024_add_error_msg_to_jobs.rb +5 -0
- data/db/migrate/025_add_notifications.rb +16 -0
- data/db/migrate/026_add_status_to_resources.rb +7 -0
- data/db/migrate/027_add_notification_id_to_jobs.rb +8 -0
- data/lib/coupler.rb +6 -2
- data/lib/coupler/base.rb +1 -0
- data/lib/coupler/extensions.rb +3 -1
- data/lib/coupler/extensions/imports.rb +11 -14
- data/lib/coupler/extensions/notifications.rb +26 -0
- data/lib/coupler/models.rb +1 -0
- data/lib/coupler/models/comparison.rb +0 -1
- data/lib/coupler/models/connection.rb +0 -1
- data/lib/coupler/models/field.rb +6 -6
- data/lib/coupler/models/import.rb +9 -3
- data/lib/coupler/models/job.rb +64 -20
- data/lib/coupler/models/matcher.rb +0 -1
- data/lib/coupler/models/notification.rb +7 -0
- data/lib/coupler/models/project.rb +0 -1
- data/lib/coupler/models/resource.rb +52 -31
- data/lib/coupler/models/result.rb +0 -1
- data/lib/coupler/models/scenario.rb +0 -1
- data/lib/coupler/models/transformation.rb +2 -3
- data/lib/coupler/models/transformer.rb +0 -1
- data/lib/coupler/scheduler.rb +8 -0
- data/tasks/db.rake +2 -2
- data/test/functional/test_imports.rb +21 -13
- data/test/functional/test_notifications.rb +38 -0
- data/test/integration/test_import.rb +25 -1
- data/test/unit/models/test_field.rb +2 -13
- data/test/unit/models/test_import.rb +4 -0
- data/test/unit/models/test_job.rb +59 -6
- data/test/unit/models/test_notification.rb +17 -0
- data/test/unit/models/test_resource.rb +114 -29
- data/test/unit/models/test_transformation.rb +4 -4
- data/test/unit/test_base.rb +1 -1
- data/test/unit/test_scheduler.rb +11 -0
- data/webroot/public/css/style.css +23 -0
- data/webroot/public/js/application.js +31 -10
- data/webroot/views/jobs/list.erb +2 -0
- data/webroot/views/layout.erb +3 -1
- data/webroot/views/notifications/index.erb +16 -0
- data/webroot/views/resources/list.erb +12 -7
- data/webroot/views/sidebar.erb +2 -0
- metadata +11 -2
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.8
|
data/coupler.gemspec
CHANGED
@@ -5,12 +5,12 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{coupler}
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.8"
|
9
9
|
s.platform = %q{java}
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
12
|
s.authors = ["Jeremy Stephens"]
|
13
|
-
s.date = %q{2011-
|
13
|
+
s.date = %q{2011-08-16}
|
14
14
|
s.default_executable = %q{coupler}
|
15
15
|
s.description = %q{Coupler is a (JRuby) desktop application designed to link datasets together}
|
16
16
|
s.email = %q{jeremy.f.stephens@vanderbilt.edu}
|
@@ -59,6 +59,10 @@ Gem::Specification.new do |s|
|
|
59
59
|
"db/migrate/021_add_fields_to_connections.rb",
|
60
60
|
"db/migrate/022_remove_database_name_from_resources.rb",
|
61
61
|
"db/migrate/023_add_import_jobs.rb",
|
62
|
+
"db/migrate/024_add_error_msg_to_jobs.rb",
|
63
|
+
"db/migrate/025_add_notifications.rb",
|
64
|
+
"db/migrate/026_add_status_to_resources.rb",
|
65
|
+
"db/migrate/027_add_notification_id_to_jobs.rb",
|
62
66
|
"features/connections.feature",
|
63
67
|
"features/matchers.feature",
|
64
68
|
"features/projects.feature",
|
@@ -86,6 +90,7 @@ Gem::Specification.new do |s|
|
|
86
90
|
"lib/coupler/extensions/imports.rb",
|
87
91
|
"lib/coupler/extensions/jobs.rb",
|
88
92
|
"lib/coupler/extensions/matchers.rb",
|
93
|
+
"lib/coupler/extensions/notifications.rb",
|
89
94
|
"lib/coupler/extensions/projects.rb",
|
90
95
|
"lib/coupler/extensions/resources.rb",
|
91
96
|
"lib/coupler/extensions/results.rb",
|
@@ -104,6 +109,7 @@ Gem::Specification.new do |s|
|
|
104
109
|
"lib/coupler/models/job.rb",
|
105
110
|
"lib/coupler/models/jobify.rb",
|
106
111
|
"lib/coupler/models/matcher.rb",
|
112
|
+
"lib/coupler/models/notification.rb",
|
107
113
|
"lib/coupler/models/project.rb",
|
108
114
|
"lib/coupler/models/resource.rb",
|
109
115
|
"lib/coupler/models/result.rb",
|
@@ -138,6 +144,7 @@ Gem::Specification.new do |s|
|
|
138
144
|
"test/functional/test_imports.rb",
|
139
145
|
"test/functional/test_jobs.rb",
|
140
146
|
"test/functional/test_matchers.rb",
|
147
|
+
"test/functional/test_notifications.rb",
|
141
148
|
"test/functional/test_projects.rb",
|
142
149
|
"test/functional/test_resources.rb",
|
143
150
|
"test/functional/test_results.rb",
|
@@ -158,6 +165,7 @@ Gem::Specification.new do |s|
|
|
158
165
|
"test/unit/models/test_import.rb",
|
159
166
|
"test/unit/models/test_job.rb",
|
160
167
|
"test/unit/models/test_matcher.rb",
|
168
|
+
"test/unit/models/test_notification.rb",
|
161
169
|
"test/unit/models/test_project.rb",
|
162
170
|
"test/unit/models/test_resource.rb",
|
163
171
|
"test/unit/models/test_result.rb",
|
@@ -242,6 +250,7 @@ Gem::Specification.new do |s|
|
|
242
250
|
"webroot/views/layout.erb",
|
243
251
|
"webroot/views/matchers/form.erb",
|
244
252
|
"webroot/views/matchers/list.erb",
|
253
|
+
"webroot/views/notifications/index.erb",
|
245
254
|
"webroot/views/projects/form.erb",
|
246
255
|
"webroot/views/projects/index.erb",
|
247
256
|
"webroot/views/projects/show.erb",
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Sequel.migration do
|
2
|
+
up do
|
3
|
+
create_table(:notifications) do
|
4
|
+
primary_key :id
|
5
|
+
String :message
|
6
|
+
String :url
|
7
|
+
TrueClass :seen
|
8
|
+
Integer :import_id
|
9
|
+
Time :created_at
|
10
|
+
Time :updated_at
|
11
|
+
end
|
12
|
+
end
|
13
|
+
down do
|
14
|
+
drop_table(:notifications)
|
15
|
+
end
|
16
|
+
end
|
data/lib/coupler.rb
CHANGED
@@ -21,8 +21,12 @@ require 'json'
|
|
21
21
|
require 'fastercsv'
|
22
22
|
require 'carrierwave/sequel'
|
23
23
|
require 'mongrel'
|
24
|
-
|
25
|
-
|
24
|
+
|
25
|
+
=begin
|
26
|
+
# Sequel will automatically include these as needed
|
27
|
+
require 'jdbc/mysql'
|
28
|
+
require 'jdbc/h2'
|
29
|
+
=end
|
26
30
|
|
27
31
|
module Coupler
|
28
32
|
def self.environment
|
data/lib/coupler/base.rb
CHANGED
data/lib/coupler/extensions.rb
CHANGED
@@ -3,6 +3,8 @@ module Coupler
|
|
3
3
|
end
|
4
4
|
end
|
5
5
|
|
6
|
+
require File.dirname(__FILE__) + "/extensions/exceptions"
|
7
|
+
|
6
8
|
require File.dirname(__FILE__) + "/extensions/connections"
|
7
9
|
require File.dirname(__FILE__) + "/extensions/projects"
|
8
10
|
require File.dirname(__FILE__) + "/extensions/resources"
|
@@ -13,4 +15,4 @@ require File.dirname(__FILE__) + "/extensions/results"
|
|
13
15
|
require File.dirname(__FILE__) + "/extensions/jobs"
|
14
16
|
require File.dirname(__FILE__) + "/extensions/transformers"
|
15
17
|
require File.dirname(__FILE__) + "/extensions/imports"
|
16
|
-
require File.dirname(__FILE__) + "/extensions/
|
18
|
+
require File.dirname(__FILE__) + "/extensions/notifications"
|
@@ -9,19 +9,16 @@ module Coupler
|
|
9
9
|
|
10
10
|
app.post "/projects/:project_id/imports" do
|
11
11
|
@import = Models::Import.new(params[:import].merge(:project_id => @project.id))
|
12
|
-
|
13
|
-
if @import.
|
14
|
-
@
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
22
|
-
end
|
12
|
+
@resource = Models::Resource.new(:name => @import.name, :project_id => @project.id, :status => 'pending')
|
13
|
+
if @import.valid? && @resource.valid?
|
14
|
+
@import.save
|
15
|
+
@resource.import = @import
|
16
|
+
@resource.save
|
17
|
+
Scheduler.instance.schedule_import_job(@import)
|
18
|
+
redirect("/projects/#{@project.id}/resources")
|
19
|
+
else
|
20
|
+
erb(:'imports/new')
|
23
21
|
end
|
24
|
-
erb(:'imports/new')
|
25
22
|
end
|
26
23
|
|
27
24
|
app.get "/projects/:project_id/imports/:id/edit" do
|
@@ -34,8 +31,8 @@ module Coupler
|
|
34
31
|
@import = Models::Import[:id => params[:id], :project_id => @project.id]
|
35
32
|
raise ImportNotFound unless @import
|
36
33
|
@import.repair_duplicate_keys!(params[:delete])
|
37
|
-
@resource
|
38
|
-
redirect("/projects/#{@project.id}/resources/#{@resource.id}")
|
34
|
+
@import.resource.activate!
|
35
|
+
redirect("/projects/#{@project.id}/resources/#{@import.resource.id}")
|
39
36
|
end
|
40
37
|
end
|
41
38
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Coupler
|
2
|
+
module Extensions
|
3
|
+
module Notifications
|
4
|
+
include Models
|
5
|
+
|
6
|
+
def self.registered(app)
|
7
|
+
app.before do
|
8
|
+
Notification.filter(~{:seen => true}, {:url => request.path_info}).update(:seen => true)
|
9
|
+
end
|
10
|
+
|
11
|
+
app.get "/notifications" do
|
12
|
+
@notifications = Notification.order(:created_at).all
|
13
|
+
erb :"notifications/index"
|
14
|
+
end
|
15
|
+
|
16
|
+
app.get "/notifications/unseen.json" do
|
17
|
+
content_type 'application/json'
|
18
|
+
notifications = Notification.filter(~{:seen => true}).order(:created_at).all
|
19
|
+
notifications.collect do |n|
|
20
|
+
{ 'id' => n.id, 'message' => n.message, 'url' => n.url, 'created_at' => n.created_at.iso8601 }
|
21
|
+
end.to_json
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/coupler/models.rb
CHANGED
@@ -16,6 +16,7 @@ module Coupler
|
|
16
16
|
autoload :Scenario, File.dirname(__FILE__) + "/models/scenario"
|
17
17
|
autoload :Transformation, File.dirname(__FILE__) + "/models/transformation"
|
18
18
|
autoload :Transformer, File.dirname(__FILE__) + "/models/transformer"
|
19
|
+
autoload :Notification, File.dirname(__FILE__) + "/models/notification"
|
19
20
|
end
|
20
21
|
end
|
21
22
|
|
data/lib/coupler/models/field.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
pp caller
|
2
1
|
module Coupler
|
3
2
|
module Models
|
4
3
|
class Field < Sequel::Model
|
@@ -6,13 +5,14 @@ module Coupler
|
|
6
5
|
many_to_one :resource
|
7
6
|
one_to_many :transformations, :key => :source_field_id
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
TYPES = {
|
9
|
+
'integer' => {:type => Integer},
|
10
|
+
'string' => {:type => String, :size => 255},
|
11
|
+
'datetime' => {:type => DateTime}
|
12
|
+
}
|
12
13
|
|
13
14
|
def local_column_options
|
14
|
-
{ :name => name, :
|
15
|
-
:primary_key => is_primary_key }
|
15
|
+
{ :name => name, :primary_key => is_primary_key }.merge!(TYPES[final_type])
|
16
16
|
end
|
17
17
|
|
18
18
|
def final_type
|
@@ -1,4 +1,3 @@
|
|
1
|
-
pp caller
|
2
1
|
module Coupler
|
3
2
|
module Models
|
4
3
|
class Import < Sequel::Model
|
@@ -15,6 +14,7 @@ module Coupler
|
|
15
14
|
\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2} )\z /x
|
16
15
|
|
17
16
|
many_to_one :project
|
17
|
+
one_to_one :resource
|
18
18
|
plugin :serialization
|
19
19
|
serialize_attributes :marshal, :field_types, :field_names
|
20
20
|
mount_uploader :data, DataUploader
|
@@ -50,7 +50,7 @@ module Coupler
|
|
50
50
|
@preview
|
51
51
|
end
|
52
52
|
|
53
|
-
def import!
|
53
|
+
def import!(&progress)
|
54
54
|
project.local_database do |db|
|
55
55
|
column_info = []
|
56
56
|
column_names = []
|
@@ -79,7 +79,9 @@ module Coupler
|
|
79
79
|
buffer = ImportBuffer.new(column_names, ds)
|
80
80
|
skip = has_headers
|
81
81
|
primary_key_index = field_names.index(primary_key_name)
|
82
|
-
|
82
|
+
io = File.open(data.file.file, 'rb')
|
83
|
+
csv = FasterCSV.new(io)
|
84
|
+
csv.each do |row|
|
83
85
|
if skip
|
84
86
|
# skip header if necessary
|
85
87
|
skip = false
|
@@ -97,6 +99,10 @@ module Coupler
|
|
97
99
|
self.has_duplicate_keys = true if num > 1
|
98
100
|
|
99
101
|
buffer.add(row)
|
102
|
+
|
103
|
+
if block_given?
|
104
|
+
yield io.pos
|
105
|
+
end
|
100
106
|
end
|
101
107
|
buffer.flush
|
102
108
|
|
data/lib/coupler/models/job.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
pp caller
|
2
1
|
module Coupler
|
3
2
|
module Models
|
4
3
|
class Job < Sequel::Model
|
@@ -6,6 +5,8 @@ module Coupler
|
|
6
5
|
|
7
6
|
many_to_one :resource
|
8
7
|
many_to_one :scenario
|
8
|
+
many_to_one :import
|
9
|
+
many_to_one :notification
|
9
10
|
|
10
11
|
def percent_completed
|
11
12
|
total > 0 ? completed * 100 / total : 0
|
@@ -13,31 +14,74 @@ module Coupler
|
|
13
14
|
|
14
15
|
def execute
|
15
16
|
Logger.instance.info("Starting job #{id} (#{name})")
|
17
|
+
|
18
|
+
opts = {}
|
16
19
|
case name
|
17
20
|
when 'transform'
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
resource.transform! { |n| update(:completed => completed + n) }
|
23
|
-
new_status = 'done'
|
24
|
-
ensure
|
25
|
-
update(:status => new_status, :completed_at => Time.now)
|
26
|
-
end
|
21
|
+
opts[:total] = resource.source_dataset_count
|
22
|
+
when 'import'
|
23
|
+
opts[:total] = import.data.file.size
|
24
|
+
end
|
27
25
|
|
28
|
-
|
29
|
-
|
26
|
+
begin
|
27
|
+
opts[:status] = 'running'
|
28
|
+
opts[:started_at] = Time.now
|
29
|
+
update(opts)
|
30
|
+
send("execute_#{name}")
|
31
|
+
rescue Exception => e
|
32
|
+
message = "%s: %s\n %s" % [e.class.to_s, e.to_s, e.backtrace.join("\n ")]
|
33
|
+
update({
|
34
|
+
:status => 'failed',
|
35
|
+
:completed_at => Time.now,
|
36
|
+
:error_msg => message
|
37
|
+
})
|
38
|
+
Logger.instance.error("Job #{id} (#{name}) crashed: #{message}")
|
39
|
+
raise e
|
40
|
+
end
|
41
|
+
self.status = 'done'
|
42
|
+
self.completed_at = Time.now
|
43
|
+
save
|
44
|
+
Logger.instance.info("Job #{id} (#{name}) finished successfully")
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
30
48
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
49
|
+
def execute_transform
|
50
|
+
resource.transform! { |n| update(:completed => completed + n) }
|
51
|
+
end
|
52
|
+
|
53
|
+
def execute_run_scenario
|
54
|
+
scenario.run!
|
55
|
+
end
|
56
|
+
|
57
|
+
def execute_import
|
58
|
+
last = Time.now # don't slam the database
|
59
|
+
result = import.import! do |pos|
|
60
|
+
now = Time.now
|
61
|
+
if now - last >= 1
|
62
|
+
last = now
|
63
|
+
update(:completed => pos)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
if result
|
67
|
+
# NOTE: This is a bug waiting to happen. Import doesn't verify
|
68
|
+
# that it has a resource, but supposedly, it will always have
|
69
|
+
# one. The resource gets created at the same time in the
|
70
|
+
# controller.
|
71
|
+
|
72
|
+
resource = import.resource
|
73
|
+
resource.activate!
|
74
|
+
self.notification = Notification.create({
|
75
|
+
:message => "Import finished successfully",
|
76
|
+
:url => "/projects/#{import.project_id}/resources/#{resource.id}"
|
77
|
+
})
|
78
|
+
else
|
79
|
+
self.notification = Notification.create({
|
80
|
+
:message => "Import finished, but with errors",
|
81
|
+
:url => "/projects/#{import.project_id}/imports/#{import.id}/edit"
|
82
|
+
})
|
37
83
|
end
|
38
84
|
end
|
39
|
-
Logger.instance.info("Job #{id} (#{name}) finished")
|
40
|
-
end
|
41
85
|
end
|
42
86
|
end
|
43
87
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
pp caller
|
2
1
|
module Coupler
|
3
2
|
module Models
|
4
3
|
class Resource < Sequel::Model
|
@@ -30,8 +29,6 @@ module Coupler
|
|
30
29
|
def import=(*args)
|
31
30
|
result = super
|
32
31
|
if new?
|
33
|
-
self.project = import.project
|
34
|
-
self.name = import.name
|
35
32
|
self.table_name = "import_#{import.id}"
|
36
33
|
end
|
37
34
|
result
|
@@ -106,21 +103,20 @@ module Coupler
|
|
106
103
|
end
|
107
104
|
end
|
108
105
|
|
109
|
-
def status
|
110
|
-
if transformed_with.to_s != transformation_ids.join(",") || transformations_dataset.filter("updated_at > ?", transformed_at).count > 0
|
111
|
-
"out_of_date"
|
112
|
-
else
|
113
|
-
"ok"
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
106
|
def scenarios
|
118
107
|
Scenario.filter(["resource_1_id = ? OR resource_2_id = ?", id, id]).all
|
119
108
|
end
|
120
109
|
|
121
|
-
def
|
110
|
+
def transformations_updated!
|
111
|
+
last_updated_at = transformed_at
|
112
|
+
transformation_ids = []
|
113
|
+
|
122
114
|
fields_dataset.update(:local_db_type => nil, :local_type => nil)
|
123
115
|
transformations_dataset.order(:position).each do |transformation|
|
116
|
+
transformation_ids << transformation.id
|
117
|
+
if last_updated_at.nil? || transformation.updated_at > last_updated_at
|
118
|
+
last_updated_at = transformation.updated_at
|
119
|
+
end
|
124
120
|
if transformation.source_field_id == transformation.result_field_id
|
125
121
|
source_field = transformation.source_field
|
126
122
|
changes = transformation.field_changes[source_field.id]
|
@@ -130,6 +126,12 @@ module Coupler
|
|
130
126
|
})
|
131
127
|
end
|
132
128
|
end
|
129
|
+
|
130
|
+
if transformed_with.to_s != transformation_ids.join(",") || (last_updated_at && last_updated_at > transformed_at)
|
131
|
+
update(:status => "out_of_date")
|
132
|
+
else
|
133
|
+
update(:status => "ok")
|
134
|
+
end
|
133
135
|
end
|
134
136
|
|
135
137
|
def transform!(&progress)
|
@@ -137,6 +139,7 @@ module Coupler
|
|
137
139
|
create_local_table!
|
138
140
|
_transform(&progress)
|
139
141
|
self.update({
|
142
|
+
:status => 'ok',
|
140
143
|
:transformed_at => Time.now,
|
141
144
|
:transformed_with => t_ids
|
142
145
|
})
|
@@ -161,6 +164,13 @@ module Coupler
|
|
161
164
|
primary_key_name.to_sym
|
162
165
|
end
|
163
166
|
|
167
|
+
# Activate resource that was pending until import was completed
|
168
|
+
def activate!
|
169
|
+
set_primary_key
|
170
|
+
create_fields
|
171
|
+
update(:status => "ok")
|
172
|
+
end
|
173
|
+
|
164
174
|
private
|
165
175
|
def transformation_ids
|
166
176
|
transformations_dataset.select(:id).order(:id).all.collect(&:id)
|
@@ -170,6 +180,15 @@ module Coupler
|
|
170
180
|
Coupler.connection_string("project_#{project.id}")
|
171
181
|
end
|
172
182
|
|
183
|
+
def set_primary_key
|
184
|
+
source_database do |db|
|
185
|
+
schema = db.schema(table_name.to_sym)
|
186
|
+
info = schema.detect { |x| x[1][:primary_key] }
|
187
|
+
self.primary_key_name = info[0].to_s
|
188
|
+
self.primary_key_type = info[1][:type].to_s
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
173
192
|
def create_fields
|
174
193
|
source_schema.each do |(name, info)|
|
175
194
|
add_field({
|
@@ -233,19 +252,21 @@ module Coupler
|
|
233
252
|
validates_presence [:project_id, :name]
|
234
253
|
validates_presence :slug
|
235
254
|
validates_unique [:name, :project_id], [:slug, :project_id]
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
255
|
+
|
256
|
+
if status != 'pending'
|
257
|
+
validates_presence [:table_name]
|
258
|
+
if errors.on(:table_name).nil?
|
259
|
+
source_database do |db|
|
260
|
+
sym = self.table_name.to_sym
|
261
|
+
if !db.tables.include?(sym)
|
262
|
+
errors.add(:table_name, "is invalid")
|
263
|
+
else
|
264
|
+
keys = db.schema(sym).select { |info| info[1][:primary_key] }
|
265
|
+
if keys.empty?
|
266
|
+
errors.add(:table_name, "doesn't have a primary key")
|
267
|
+
elsif keys.length > 1
|
268
|
+
errors.add(:table_name, "has too many primary keys")
|
269
|
+
end
|
249
270
|
end
|
250
271
|
end
|
251
272
|
end
|
@@ -257,11 +278,9 @@ module Coupler
|
|
257
278
|
# NOTE: I'm doing this instead of using before_create because
|
258
279
|
# serialization happens in before_save, which gets called before
|
259
280
|
# the before_create hook
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
self.primary_key_name = info[0].to_s
|
264
|
-
self.primary_key_type = info[1][:type].to_s
|
281
|
+
if status != "pending"
|
282
|
+
set_primary_key
|
283
|
+
self.status = "ok"
|
265
284
|
end
|
266
285
|
end
|
267
286
|
super
|
@@ -269,7 +288,9 @@ module Coupler
|
|
269
288
|
|
270
289
|
def after_create
|
271
290
|
super
|
272
|
-
|
291
|
+
if status != "pending"
|
292
|
+
create_fields
|
293
|
+
end
|
273
294
|
end
|
274
295
|
|
275
296
|
def after_destroy
|