marty 0.5.15 → 0.5.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +27 -0
- data/.rspec +3 -0
- data/.travis.yml +23 -0
- data/Gemfile +23 -0
- data/INDEPENDENCE_ISSUES.md +23 -0
- data/app/assets/images/marty/.gitkeep +0 -0
- data/app/components/marty/report_form.rb +9 -4
- data/gemini_deprecations.md +6 -0
- data/lib/marty/data_change.rb +99 -0
- data/lib/marty/data_conversion.rb +11 -3
- data/lib/marty/data_exporter.rb +9 -0
- data/lib/marty/version.rb +1 -1
- data/marty.gemspec +35 -0
- data/script/rails +8 -0
- data/spec/controllers/application_controller_spec.rb +52 -0
- data/spec/controllers/job_controller_spec.rb +226 -0
- data/spec/controllers/rpc_controller_spec.rb +379 -0
- data/spec/controllers/rpc_import_spec.rb +45 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/components_controller.rb +7 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/models/gemini/amortization_type.rb +5 -0
- data/spec/dummy/app/models/gemini/bud_category.rb +7 -0
- data/spec/dummy/app/models/gemini/entity.rb +2 -0
- data/spec/dummy/app/models/gemini/extras/data_import.rb +5 -0
- data/spec/dummy/app/models/gemini/extras/settlement_import.rb +28 -0
- data/spec/dummy/app/models/gemini/fannie_bup.rb +29 -0
- data/spec/dummy/app/models/gemini/grouping.rb +8 -0
- data/spec/dummy/app/models/gemini/grouping_head_version.rb +14 -0
- data/spec/dummy/app/models/gemini/head.rb +7 -0
- data/spec/dummy/app/models/gemini/head_version.rb +14 -0
- data/spec/dummy/app/models/gemini/helper.rb +44 -0
- data/spec/dummy/app/models/gemini/loan_program.rb +11 -0
- data/spec/dummy/app/models/gemini/mortgage_type.rb +5 -0
- data/spec/dummy/app/models/gemini/simple.rb +6 -0
- data/spec/dummy/app/models/gemini/streamline_type.rb +16 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +82 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml.example +10 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +35 -0
- data/spec/dummy/config/environments/production.rb +69 -0
- data/spec/dummy/config/environments/test.rb +39 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/delayed_job.rb +5 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +12 -0
- data/spec/dummy/db/migrate/20140801000000_create_groupings.rb +11 -0
- data/spec/dummy/db/migrate/20150406171536_create_categories.rb +27 -0
- data/spec/dummy/db/migrate/20150408200916_create_loan_programs.rb +26 -0
- data/spec/dummy/db/migrate/20150408201429_create_types.rb +21 -0
- data/spec/dummy/db/migrate/20150420000001_create_heads.rb +14 -0
- data/spec/dummy/db/migrate/20150420000002_create_head_versions.rb +15 -0
- data/spec/dummy/db/migrate/20150420000003_create_grouping_head_versions.rb +12 -0
- data/spec/dummy/db/migrate/20151023000001_create_simple.rb +12 -0
- data/spec/dummy/db/seeds.rb +8 -0
- data/spec/dummy/delorean/blame_report.dl +171 -0
- data/spec/dummy/delorean/data_report.dl +105 -0
- data/spec/dummy/delorean/fields.dl +52 -0
- data/spec/dummy/delorean/styles.dl +134 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/lib/class_list.rb +3 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/icons/READ.txt +3 -0
- data/spec/dummy/public/icons/application_cascade.png +0 -0
- data/spec/dummy/public/icons/application_delete.png +0 -0
- data/spec/dummy/public/icons/application_put.png +0 -0
- data/spec/dummy/public/icons/application_view_detail.png +0 -0
- data/spec/dummy/public/icons/arrow_in.png +0 -0
- data/spec/dummy/public/icons/arrow_refresh.png +0 -0
- data/spec/dummy/public/icons/database_save.png +0 -0
- data/spec/dummy/public/icons/door_in.png +0 -0
- data/spec/dummy/public/icons/door_out.png +0 -0
- data/spec/dummy/public/icons/group.png +0 -0
- data/spec/dummy/public/icons/page_lightning.png +0 -0
- data/spec/dummy/public/icons/printer.png +0 -0
- data/spec/dummy/public/icons/report_disk.png +0 -0
- data/spec/dummy/public/icons/report_go.png +0 -0
- data/spec/dummy/public/icons/report_magnify.png +0 -0
- data/spec/dummy/public/icons/script.png +0 -0
- data/spec/dummy/public/icons/script_add.png +0 -0
- data/spec/dummy/public/icons/script_go.png +0 -0
- data/spec/dummy/public/icons/script_key.png +0 -0
- data/spec/dummy/public/icons/table_go.png +0 -0
- data/spec/dummy/public/icons/time.png +0 -0
- data/spec/dummy/public/icons/time_add.png +0 -0
- data/spec/dummy/public/icons/time_go.png +0 -0
- data/spec/dummy/public/icons/timeline_marker.png +0 -0
- data/spec/dummy/public/icons/user_add.png +0 -0
- data/spec/dummy/public/icons/user_delete.png +0 -0
- data/spec/dummy/public/icons/user_edit.png +0 -0
- data/spec/dummy/public/icons/wrench.png +0 -0
- data/spec/dummy/script/delayed_job +6 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/features/javascripts/job_dashboard_live_search.js.coffee +8 -0
- data/spec/features/javascripts/login.js.coffee +8 -0
- data/spec/features/jobs_dashboard_netzke_spec.rb +24 -0
- data/spec/features/jobs_dashboard_spec.rb +49 -0
- data/spec/fixtures/scripts/load_tests/script1.dl +2 -0
- data/spec/fixtures/scripts/load_tests/script2.dl +2 -0
- data/spec/job_helper.rb +102 -0
- data/spec/lib/data_exporter_spec.rb +71 -0
- data/spec/lib/data_importer_spec.rb +461 -0
- data/spec/lib/xl_spec.rb +198 -0
- data/spec/lib/xl_styles_spec.rb +115 -0
- data/spec/models/api_auth_spec.rb +187 -0
- data/spec/models/posting_spec.rb +107 -0
- data/spec/models/promise_spec.rb +65 -0
- data/spec/models/script_spec.rb +187 -0
- data/spec/models/user_spec.rb +68 -0
- data/spec/requests/routes_spec.rb +12 -0
- data/spec/spec_helper.rb +61 -0
- data/spec/support/clean_db_helpers.rb +18 -0
- data/spec/support/delayed_job_helpers.rb +12 -0
- data/spec/support/user_helpers.rb +12 -0
- metadata +139 -89
- data/app/components/marty/auth_app.rb~ +0 -51
- data/app/components/marty/auth_app/javascripts/auth_app.js~ +0 -91
- data/app/components/marty/cm_form_panel.rb~ +0 -5
- data/app/components/marty/cm_grid_panel.rb~ +0 -35
- data/app/components/marty/data_import_view.rb~ +0 -142
- data/app/components/marty/extras/layout.rb~ +0 -46
- data/app/components/marty/live_search_grid_panel.rb~ +0 -49
- data/app/components/marty/main_auth_app.rb~ +0 -238
- data/app/components/marty/mcfly_grid_panel.rb~ +0 -80
- data/app/components/marty/new_posting_form.rb~ +0 -46
- data/app/components/marty/new_posting_window.rb~ +0 -21
- data/app/components/marty/pivot_grid.rb +0 -52
- data/app/components/marty/pivot_grid/endpoints.rb +0 -45
- data/app/components/marty/pivot_grid/javascripts/extensions.js +0 -150
- data/app/components/marty/pivot_grid/javascripts/pivot_grid.js +0 -86
- data/app/components/marty/pivot_grid/services.rb +0 -44
- data/app/components/marty/posting_grid.rb~ +0 -140
- data/app/components/marty/promise_view.rb~ +0 -157
- data/app/components/marty/promise_view/stylesheets/promise_view.css~ +0 -15
- data/app/components/marty/report_form.rb~ +0 -217
- data/app/components/marty/report_select.rb~ +0 -133
- data/app/components/marty/reporting.rb~ +0 -39
- data/app/components/marty/script_detail.rb~ +0 -430
- data/app/components/marty/script_form.rb~ +0 -233
- data/app/components/marty/script_form/javascripts/Ext.ux.form.field.CodeMirror.js~ +0 -909
- data/app/components/marty/script_grid.rb~ +0 -99
- data/app/components/marty/script_tester.rb~ +0 -213
- data/app/components/marty/scripting.rb~ +0 -124
- data/app/components/marty/select_report.rb~ +0 -143
- data/app/components/marty/simple_app.rb~ +0 -101
- data/app/components/marty/tag_grid.rb~ +0 -89
- data/app/components/marty/tree_panel.rb~ +0 -256
- data/app/components/marty/tree_panel/javascripts/tree_panel.js~ +0 -317
- data/app/components/marty/user_pivot.rb +0 -128
- data/app/components/marty/user_view.rb~ +0 -188
- data/app/controllers/marty/application_controller.rb~ +0 -133
- data/app/controllers/marty/components_controller.rb~ +0 -37
- data/app/controllers/marty/job_controller.rb~ +0 -28
- data/app/controllers/marty/rpc_controller.rb~ +0 -61
- data/app/helpers/marty/script_set.rb~ +0 -59
- data/app/models/marty/api_auth.rb~ +0 -48
- data/app/models/marty/data_change.rb~ +0 -141
- data/app/models/marty/enum.rb~ +0 -16
- data/app/models/marty/import_type.rb~ +0 -48
- data/app/models/marty/poop.rb~ +0 -169
- data/app/models/marty/posting.rb~ +0 -86
- data/app/models/marty/posting_type.rb~ +0 -21
- data/app/models/marty/promise.rb~ +0 -196
- data/app/models/marty/role.rb~ +0 -10
- data/app/models/marty/script.rb~ +0 -62
- data/app/models/marty/tag.rb~ +0 -91
- data/app/models/marty/user.rb~ +0 -148
- data/app/models/marty/user_role.rb~ +0 -13
- data/app/views/layouts/marty/application.html.erb~ +0 -11
- data/config/routes.rb~ +0 -10
- data/db/migrate/019_create_marty_postings.rb~ +0 -19
- data/db/migrate/095_create_marty_tags.rb~ +0 -19
- data/lib/marty.rb~ +0 -13
- data/lib/marty/content_handler.rb~ +0 -93
- data/lib/marty/data_exporter.rb~ +0 -137
- data/lib/marty/data_importer.rb~ +0 -114
- data/lib/marty/data_row_processor.rb~ +0 -206
- data/lib/marty/drop_folder_hook.rb~ +0 -17
- data/lib/marty/folder_hook.rb~ +0 -9
- data/lib/marty/lazy_column_loader.rb~ +0 -47
- data/lib/marty/mcfly_query.rb~ +0 -188
- data/lib/marty/migrations.rb~ +0 -65
- data/lib/marty/monkey.rb~ +0 -160
- data/lib/marty/permissions.rb~ +0 -69
- data/lib/marty/promise.rb~ +0 -41
- data/lib/marty/promise_job.rb~ +0 -121
- data/lib/marty/promise_proxy.rb~ +0 -69
- data/lib/marty/util.rb~ +0 -80
- data/lib/marty/version.rb~ +0 -3
- data/lib/marty/xl.rb~ +0 -526
- data/lib/pyxll/README.txt~ +0 -16
- data/lib/pyxll/gemini.py~ +0 -110
- data/lib/pyxll/pyxll.cfg~ +0 -12
data/lib/marty/data_exporter.rb~
DELETED
@@ -1,137 +0,0 @@
|
|
1
|
-
require 'base64'
|
2
|
-
require 'zlib'
|
3
|
-
|
4
|
-
class Marty::DataExporter
|
5
|
-
# given an array of hashes, return set of all keys
|
6
|
-
def self.hash_array_keys(hl)
|
7
|
-
hl.each_with_object(Set.new) { |h, keys| keys.merge(h.keys) }
|
8
|
-
end
|
9
|
-
|
10
|
-
def self.hash_array_merge(hl, transpose)
|
11
|
-
# given a list of hashes hl, generates a merged hash. The
|
12
|
-
# resulting hash contains a superset of all the hash keys. The
|
13
|
-
# values are corresponding values from each hash in hl.
|
14
|
-
keys = hash_array_keys(hl)
|
15
|
-
|
16
|
-
if transpose
|
17
|
-
keys.each_with_object({}) { |k, rh|
|
18
|
-
rh[k] = hl.map { |h| h[k] }
|
19
|
-
}
|
20
|
-
else
|
21
|
-
[keys.to_a] + hl.map {|h| keys.map {|k| h[k]}}
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def self.encode_json(s)
|
26
|
-
Base64.strict_encode64 Zlib.deflate(s)
|
27
|
-
end
|
28
|
-
|
29
|
-
def self.decode_json(s)
|
30
|
-
Zlib.inflate Base64.strict_decode64(s)
|
31
|
-
end
|
32
|
-
|
33
|
-
def self.to_csv(obj, config=nil)
|
34
|
-
obj = [obj] unless obj.respond_to? :map
|
35
|
-
|
36
|
-
config ||= {}
|
37
|
-
|
38
|
-
# if all array items are hashes, we merge them
|
39
|
-
obj = hash_array_merge(obj, config["transpose"]) if
|
40
|
-
obj.is_a?(Array) && obj.map {|x| x.is_a? Hash}.all?
|
41
|
-
|
42
|
-
# symbolize config keys as expected by CSV.generate
|
43
|
-
conf = config.each_with_object({}) { |(k,v), h|
|
44
|
-
h[k.to_sym] = v unless k.to_s == "transpose"
|
45
|
-
}
|
46
|
-
|
47
|
-
# FIXME: very hacky to default row_sep to CRLF
|
48
|
-
conf[:row_sep] ||= "\r\n"
|
49
|
-
|
50
|
-
# FIXME: the following is ridiculously complex. We have different
|
51
|
-
# data paths for hashes and arrays. Also, arrays can turn into
|
52
|
-
# hashes is all their items are hashes! We map to complex objects
|
53
|
-
# to JSON when inside hashes, but not arrays. Really need to
|
54
|
-
# rethink this. Probably should have separate functions for
|
55
|
-
# to_csv for hash and arrays.
|
56
|
-
|
57
|
-
if obj.is_a? Hash
|
58
|
-
csv_string = CSV.generate(conf) do |csv|
|
59
|
-
obj.each do |x|
|
60
|
-
csv << x.flatten(1).map(&:to_s)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
else
|
64
|
-
csv_string = CSV.generate(conf) do |csv|
|
65
|
-
obj.each do |x|
|
66
|
-
x = [x] unless x.respond_to? :map
|
67
|
-
csv << x.map { |v|
|
68
|
-
v.is_a?(Array) || v.is_a?(Hash) ? encode_json(v.to_json) : v.to_s
|
69
|
-
}
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def self.class_info(klass)
|
76
|
-
@class_info ||= {}
|
77
|
-
|
78
|
-
return @class_info[klass] if @class_info[klass]
|
79
|
-
|
80
|
-
associations = klass.reflect_on_all_associations.map(&:name)
|
81
|
-
|
82
|
-
@class_info[klass] = {
|
83
|
-
cols:
|
84
|
-
klass.columns.map(&:name) - Marty::DataRowProcessor::MCFLY_COLUMNS.to_a,
|
85
|
-
|
86
|
-
assoc:
|
87
|
-
associations.each_with_object({}) { |a, h|
|
88
|
-
h["#{a}_id"] = Marty::DataRowProcessor.assoc_info(klass, a)
|
89
|
-
},
|
90
|
-
}
|
91
|
-
end
|
92
|
-
|
93
|
-
def self.export_attr(obj, c, info)
|
94
|
-
v = obj.send(c.to_sym)
|
95
|
-
assoc_info = info[:assoc][c] unless v.nil?
|
96
|
-
return [v] unless assoc_info
|
97
|
-
|
98
|
-
assoc_obj = assoc_info[:assoc_class].find(v)
|
99
|
-
|
100
|
-
assoc_info[:assoc_keys].map {|k| assoc_obj.send(k.to_sym)}
|
101
|
-
end
|
102
|
-
|
103
|
-
def self.export_header_attr(c, info)
|
104
|
-
assoc_info = info[:assoc][c]
|
105
|
-
return c unless assoc_info
|
106
|
-
|
107
|
-
# remove _id
|
108
|
-
c = c[0..-4]
|
109
|
-
|
110
|
-
assoc_keys = assoc_info[:assoc_keys]
|
111
|
-
|
112
|
-
# FIXME: this doesn't work if k is also an association. Needs to
|
113
|
-
# be recursive.
|
114
|
-
assoc_keys.length > 1 ? assoc_keys.map {|k| "#{c}__#{k}"} : c
|
115
|
-
end
|
116
|
-
|
117
|
-
# Given a Mcfly klass, generate an export array. Can potentially
|
118
|
-
# use up a lot of memory if the result set is large.
|
119
|
-
def self.do_export(ts, klass, sort_field=nil)
|
120
|
-
info = class_info(klass)
|
121
|
-
|
122
|
-
# strip _id from assoc fields
|
123
|
-
header = [ info[:cols].map {|c| export_header_attr(c, info)}.flatten(1) ]
|
124
|
-
|
125
|
-
query = klass
|
126
|
-
|
127
|
-
# is it Mcfly?
|
128
|
-
if (klass.const_get(:MCFLY_UNIQUENESS) rescue nil)
|
129
|
-
ts = Mcfly.normalize_infinity(ts)
|
130
|
-
query = query.where("obsoleted_dt >= ? AND created_dt < ?", ts, ts)
|
131
|
-
end
|
132
|
-
|
133
|
-
header + query.
|
134
|
-
order(sort_field || :id).
|
135
|
-
map {|obj| info[:cols].map {|c| export_attr(obj, c, info)}.flatten(1)}
|
136
|
-
end
|
137
|
-
end
|
data/lib/marty/data_importer.rb~
DELETED
@@ -1,114 +0,0 @@
|
|
1
|
-
module Marty
|
2
|
-
class DataImporterError < StandardError
|
3
|
-
attr_reader :lines
|
4
|
-
|
5
|
-
def initialize(message, lines)
|
6
|
-
super(message)
|
7
|
-
@lines = lines
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
class DataImporter
|
12
|
-
# perform cleaning and do_import and summarize its results
|
13
|
-
def self.do_import_summary(klass,
|
14
|
-
data,
|
15
|
-
dt='infinity',
|
16
|
-
cleaner_function=nil,
|
17
|
-
validation_function=nil,
|
18
|
-
col_sep="\t",
|
19
|
-
allow_dups=false
|
20
|
-
)
|
21
|
-
|
22
|
-
recs = self.do_import(klass,
|
23
|
-
data,
|
24
|
-
dt,
|
25
|
-
cleaner_function,
|
26
|
-
validation_function,
|
27
|
-
col_sep,
|
28
|
-
allow_dups,
|
29
|
-
)
|
30
|
-
|
31
|
-
recs.each_with_object(Hash.new(0)) {|(op, id), h|
|
32
|
-
h[op] += 1
|
33
|
-
}
|
34
|
-
end
|
35
|
-
|
36
|
-
# Given a Mcfly klass and CSV data, import data into the database
|
37
|
-
# and report on affected rows. Result is an array of tuples.
|
38
|
-
# Each tuple is associated with one data row and looks like [tag,
|
39
|
-
# id]. Tag is one of :same, :update, :create and "id" is the id
|
40
|
-
# of the affected row.
|
41
|
-
def self.do_import(klass,
|
42
|
-
data,
|
43
|
-
dt='infinity',
|
44
|
-
cleaner_function=nil,
|
45
|
-
validation_function=nil,
|
46
|
-
col_sep="\t",
|
47
|
-
allow_dups=false
|
48
|
-
)
|
49
|
-
|
50
|
-
parsed = data.is_a?(Array) ? data :
|
51
|
-
CSV.new(data, headers: true, col_sep: col_sep)
|
52
|
-
|
53
|
-
klass.transaction do
|
54
|
-
cleaner_ids = cleaner_function ? klass.send(cleaner_function.to_sym) :
|
55
|
-
[]
|
56
|
-
|
57
|
-
raise "bad cleaner function result" unless
|
58
|
-
cleaner_ids.all? {|id| id.is_a?(Fixnum) }
|
59
|
-
|
60
|
-
row_proc = nil
|
61
|
-
eline = 0
|
62
|
-
|
63
|
-
begin
|
64
|
-
res = parsed.each_with_index.map { |row, line|
|
65
|
-
eline = line
|
66
|
-
|
67
|
-
row_proc ||= Marty::DataRowProcessor.
|
68
|
-
new(klass,
|
69
|
-
row.respond_to?(:headers) ? row.headers : row.keys,
|
70
|
-
dt,
|
71
|
-
)
|
72
|
-
# skip lines which are all nil
|
73
|
-
next :blank if row.to_hash.values.none?
|
74
|
-
|
75
|
-
row_proc.create_or_update(row)
|
76
|
-
}
|
77
|
-
rescue => exc
|
78
|
-
# to find problems with the importer, comment out the rescue block
|
79
|
-
raise Marty::DataImporterError.new(exc.to_s, [eline])
|
80
|
-
end
|
81
|
-
|
82
|
-
ids = {}
|
83
|
-
|
84
|
-
# raise an error if record referenced more than once.
|
85
|
-
res.each_with_index do
|
86
|
-
|(op, id), line|
|
87
|
-
raise Marty::DataImporterError.
|
88
|
-
new("record referenced more than once", [ids[id], line]) if
|
89
|
-
op != :blank && ids.member?(id) && !allow_dups
|
90
|
-
|
91
|
-
ids[id] = line
|
92
|
-
end
|
93
|
-
|
94
|
-
begin
|
95
|
-
# Validate affected rows if necessary
|
96
|
-
klass.send(validation_function.to_sym, ids.keys) if
|
97
|
-
validation_function
|
98
|
-
rescue => exc
|
99
|
-
raise Marty::DataImporterError.new(exc.to_s, [])
|
100
|
-
end
|
101
|
-
|
102
|
-
remainder_ids = cleaner_ids - ids.keys
|
103
|
-
|
104
|
-
raise Marty::DataImporterError.
|
105
|
-
new("Missing import data. " +
|
106
|
-
"Please provide header line and at least one data line.", [1]) if
|
107
|
-
ids.keys.compact.count == 0
|
108
|
-
|
109
|
-
klass.delete(remainder_ids)
|
110
|
-
res + remainder_ids.map {|id| [:clean, id]}
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
@@ -1,206 +0,0 @@
|
|
1
|
-
class Marty::DataRowProcessor
|
2
|
-
attr_accessor :klass, :headers, :dt, :key_attrs, :hmap
|
3
|
-
|
4
|
-
EXCEL_START_DATE = Date.parse('1/1/1900')-2
|
5
|
-
|
6
|
-
MCFLY_COLUMNS = Set[
|
7
|
-
"id",
|
8
|
-
"group_id",
|
9
|
-
"user_id",
|
10
|
-
"created_dt",
|
11
|
-
"obsoleted_dt",
|
12
|
-
"o_user_id",
|
13
|
-
]
|
14
|
-
|
15
|
-
# Given a Mcfly class, return the set of attributes (excluding id)
|
16
|
-
# used to uniquely identify an instance.
|
17
|
-
def self.get_keys(klass)
|
18
|
-
raise "bad class arg #{klass}" unless
|
19
|
-
klass.is_a?(Class) && klass < ActiveRecord::Base
|
20
|
-
|
21
|
-
attrs = klass.const_get(:MCFLY_UNIQUENESS)
|
22
|
-
|
23
|
-
raise "class has no :MCFLY_UNIQUENESS" unless attrs
|
24
|
-
|
25
|
-
attrs = attrs[0..-2] + attrs.last.fetch(:scope, []) if
|
26
|
-
attrs.last.is_a?(Hash)
|
27
|
-
attrs -= [:obsoleted_dt]
|
28
|
-
|
29
|
-
raise "key list for #{klass} is empty" if attrs.empty?
|
30
|
-
attrs
|
31
|
-
end
|
32
|
-
|
33
|
-
def self.assoc_info(klass, a)
|
34
|
-
assoc_class = klass.reflect_on_association(a.to_sym).klass
|
35
|
-
keys = self.get_keys(assoc_class) rescue nil
|
36
|
-
|
37
|
-
assoc_keys = keys || [assoc_class.attribute_names.reject{|x| x=="id"}.first]
|
38
|
-
|
39
|
-
{assoc_keys: assoc_keys, assoc_class: assoc_class, mcfly: keys}
|
40
|
-
end
|
41
|
-
|
42
|
-
FLOAT_PAT = /^-?\d+(\.\d+)?$/
|
43
|
-
|
44
|
-
PATS = {
|
45
|
-
integer: /^-?\d+(\.0+)?$/,
|
46
|
-
float: FLOAT_PAT,
|
47
|
-
decimal: FLOAT_PAT,
|
48
|
-
}
|
49
|
-
|
50
|
-
def convert(v, type)
|
51
|
-
pat = PATS[type]
|
52
|
-
|
53
|
-
raise "bad #{type} #{v.inspect}" if
|
54
|
-
v.is_a?(String) && pat && !(v =~ pat)
|
55
|
-
|
56
|
-
case type
|
57
|
-
when :boolean
|
58
|
-
case v.downcase
|
59
|
-
when "true" then true
|
60
|
-
when "false" then false
|
61
|
-
else raise "unknown boolean #{v}"
|
62
|
-
end
|
63
|
-
when :string, :text
|
64
|
-
v
|
65
|
-
when :integer
|
66
|
-
v.to_i
|
67
|
-
when :float
|
68
|
-
v.to_f
|
69
|
-
when :decimal
|
70
|
-
v.to_d
|
71
|
-
when :date
|
72
|
-
# Dates are kept as float in Google spreadsheets. Need to
|
73
|
-
# convert them to dates. FIXME: 'infinity' as a date in
|
74
|
-
# Rails 3.2 appears to be broken. Setting a date field to
|
75
|
-
# 'infinity' sets it to nil.
|
76
|
-
v =~ FLOAT_PAT ? EXCEL_START_DATE + v.to_f :
|
77
|
-
Mcfly.is_infinity(v) ? 'infinity' : v.to_date
|
78
|
-
when :datetime
|
79
|
-
Mcfly.is_infinity(v) ? 'infinity' : v.to_datetime
|
80
|
-
when :numrange, :int4range, :int8range
|
81
|
-
v.to_s
|
82
|
-
when :float_array, :json
|
83
|
-
JSON.parse Marty::DataExporter.decode_json(v)
|
84
|
-
else
|
85
|
-
raise "unknown type #{type} for #{v}"
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def initialize(klass, headers, dt)
|
90
|
-
@klass = klass
|
91
|
-
@headers = headers
|
92
|
-
@dt = dt
|
93
|
-
@key_attrs = self.class.get_keys(klass)
|
94
|
-
|
95
|
-
# # HACK: not sure why there's a nil at the end of headers sometimes
|
96
|
-
# headers.pop if headers[-1].nil?
|
97
|
-
|
98
|
-
raise "row headers have nil! #{headers.inspect}" unless headers.all?
|
99
|
-
|
100
|
-
associations = klass.reflect_on_all_associations.map(&:name)
|
101
|
-
|
102
|
-
cols = klass.columns.each_with_object({}) { |c, h|
|
103
|
-
h[c.name] = c
|
104
|
-
}
|
105
|
-
|
106
|
-
@hmap = headers.each_with_object({}) do
|
107
|
-
|a, h|
|
108
|
-
# handle klass__attr type headers generated by Netzke. Just
|
109
|
-
# keeps klass since we should be able to find the key attr.
|
110
|
-
aclass = a.split('__').first
|
111
|
-
|
112
|
-
if associations.member?(aclass.to_sym)
|
113
|
-
h[a] = self.class.assoc_info(klass, aclass)
|
114
|
-
next
|
115
|
-
end
|
116
|
-
|
117
|
-
raise "unknown column #{a}" unless cols[a]
|
118
|
-
|
119
|
-
# for JSON fields in Rails 3.x type is nil, so use sql_type
|
120
|
-
type = cols[a].type || cols[a].sql_type
|
121
|
-
type = "#{type}_array" if cols[a].array
|
122
|
-
h[a] = type.to_sym
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
def create_or_update(row)
|
127
|
-
assoc_options = {}
|
128
|
-
|
129
|
-
options = row.each_with_object({}) do |(a, v), h|
|
130
|
-
# ignore Mcfly columns
|
131
|
-
next if MCFLY_COLUMNS.member? a
|
132
|
-
|
133
|
-
if hmap[a].is_a?(Hash)
|
134
|
-
assoc_options[a] = v
|
135
|
-
else
|
136
|
-
raise "bad col #{a} value #{v}, row: #{row}" unless hmap[a]
|
137
|
-
|
138
|
-
# if it's not a hash (association) then its a type symbol
|
139
|
-
h[a] = v && convert(v, hmap[a])
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
assoc_groups = assoc_options.keys.group_by {|x| x.split('__').first}
|
144
|
-
|
145
|
-
assoc_groups.each do |aclass, attrs|
|
146
|
-
# if group has only one attr and the attr is nil or AR obj, then
|
147
|
-
# we don't need to search.
|
148
|
-
if attrs.length == 1
|
149
|
-
a = attrs.first
|
150
|
-
v = assoc_options[a]
|
151
|
-
|
152
|
-
if v.nil? || v.is_a?(ActiveRecord::Base)
|
153
|
-
options["#{aclass}_id"] = v && v.id
|
154
|
-
next
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
srch = attrs.each_with_object({}) do |a, h|
|
159
|
-
v = assoc_options[a]
|
160
|
-
ac, aa = a.split('__')
|
161
|
-
|
162
|
-
aa ||= hmap[a][:assoc_keys].first
|
163
|
-
|
164
|
-
h[aa] = v
|
165
|
-
h[:obsoleted_dt] = 'infinity' if hmap[a][:mcfly]
|
166
|
-
end
|
167
|
-
|
168
|
-
srch_class = hmap[attrs.first][:assoc_class]
|
169
|
-
|
170
|
-
av = srch_class.where(srch).first
|
171
|
-
|
172
|
-
raise "#{aclass} not found #{srch}" unless av
|
173
|
-
|
174
|
-
options[ "#{aclass}_id" ] = av.id
|
175
|
-
end
|
176
|
-
|
177
|
-
find_options = options.select { |k,v| key_attrs.member? k.to_sym }
|
178
|
-
|
179
|
-
raise "invalid entry" if find_options.empty?
|
180
|
-
|
181
|
-
find_options['obsoleted_dt'] = 'infinity'
|
182
|
-
|
183
|
-
obj = klass.where(find_options).first || klass.new
|
184
|
-
|
185
|
-
options.each do
|
186
|
-
|k, v|
|
187
|
-
# For each attr, check to see if it's begin changed before
|
188
|
-
# setting it. The AR obj.changed? doesn't work properly
|
189
|
-
# with array, JSON or lazy attrs.
|
190
|
-
obj.send("#{k}=", v) if obj.send(k) != v
|
191
|
-
end
|
192
|
-
|
193
|
-
# FIXME: obj.changed? doesn't work properly for timestamp
|
194
|
-
# fields in Rails 3.2. It evaluates to true even when datetime
|
195
|
-
# is not changed. Caused by lack of awareness of timezones.
|
196
|
-
tag = obj.new_record? ? :create : (obj.changed? ? :update : :same)
|
197
|
-
|
198
|
-
raise "old created_dt >= current #{obj} #{obj.created_dt} #{dt}" if
|
199
|
-
(tag == :update) && !Mcfly.is_infinity(dt) && (obj.created_dt > dt)
|
200
|
-
|
201
|
-
obj.created_dt = dt unless tag == :same || Mcfly.is_infinity(dt)
|
202
|
-
obj.save!
|
203
|
-
|
204
|
-
[tag, obj.id]
|
205
|
-
end
|
206
|
-
end
|