jcangas-datagateway 1.2.2 → 1.4.4

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 (48) hide show
  1. data/VERSION.yml +2 -2
  2. data/lib/datagateway/conduits/csv_conduit.rb +0 -0
  3. data/lib/datagateway/conduits/yaml_conduit.rb +0 -0
  4. data/lib/datagateway/data_gateway.rb +27 -514
  5. data/lib/datagateway/dbchange_point.rb +0 -0
  6. data/lib/datagateway/encoder.rb +27 -0
  7. data/lib/datagateway/folder_writer.rb +0 -0
  8. data/lib/datagateway/job.rb +223 -0
  9. data/lib/datagateway/jobs/nexus_export.rb +7 -6
  10. data/lib/datagateway/jobs/nexus_import.rb +1 -4
  11. data/lib/datagateway/jobs/shopin_export.rb +9 -6
  12. data/lib/datagateway/jobs/shopin_import.rb +9 -8
  13. data/lib/datagateway/mapper.rb +226 -0
  14. data/lib/datagateway/nexus/nax_ar.rb +0 -0
  15. data/lib/datagateway/nexus/nax_tlb.rb +0 -0
  16. data/lib/datagateway/nexus/nexus_ar.rb +0 -0
  17. data/lib/datagateway/ssh_transfer.rb +0 -0
  18. data/lib/datagateway.rb +111 -77
  19. data/lib/version.rb +0 -0
  20. data/template-prj/{README → nexus/README} +0 -0
  21. data/template-prj/{config → nexus/config}/README +0 -0
  22. data/template-prj/nexus/config/database.yml +17 -0
  23. data/template-prj/{config → nexus/config}/settings.yml +0 -2
  24. data/template-prj/{data → nexus/data}/donebox/README +0 -0
  25. data/template-prj/{data → nexus/data}/inbox/README +0 -0
  26. data/template-prj/{data → nexus/data}/outbox/README +0 -0
  27. data/template-prj/nexus/export-data.rb +9 -0
  28. data/template-prj/nexus/import-data.rb +14 -0
  29. data/template-prj/{log → nexus/log}/README +0 -0
  30. data/template-prj/{ruby.exe → nexus/ruby.exe} +0 -0
  31. data/template-prj/nexus/sistema.ini +4 -0
  32. data/template-prj/shopin/README +2 -0
  33. data/template-prj/shopin/config/README +4 -0
  34. data/template-prj/shopin/config/database.yml +8 -0
  35. data/template-prj/shopin/config/settings.yml +13 -0
  36. data/template-prj/shopin/data/donebox/README +1 -0
  37. data/template-prj/shopin/data/inbox/README +1 -0
  38. data/template-prj/shopin/data/outbox/README +1 -0
  39. data/template-prj/shopin/export-data.rb +6 -0
  40. data/template-prj/shopin/import-data.rb +6 -0
  41. data/template-prj/shopin/log/README +2 -0
  42. data/test/datagateway_test.rb +0 -0
  43. data/test/test_helper.rb +0 -0
  44. metadata +41 -20
  45. data/template-prj/config/database.yml +0 -32
  46. data/template-prj/dgw-job.rb +0 -25
  47. data/template-prj/run.bat +0 -1
  48. data/template-prj/sistema.ini +0 -3
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
- :patch: 2
2
+ :patch: 4
3
3
  :major: 1
4
- :minor: 2
4
+ :minor: 4
File without changes
File without changes
@@ -1,33 +1,36 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'rubygems'
4
+ require 'jcode'
4
5
  require 'benchmark'
5
6
  require 'fileutils'
6
7
  require 'zip/zip'
7
8
  require 'folder_writer'
8
9
  require 'iconv'
9
10
  require 'ssh_transfer'
10
- require 'jcode'
11
+ require 'job'
12
+ require 'active_support'
13
+
11
14
 
12
15
  module DataGateway
13
16
  CONDUIT_DIR = File.join(File.dirname(__FILE__), 'conduits')
14
17
  @@resource_path = ''
15
18
  @@use_resources = false
16
19
  class << self
17
- attr_accessor :logger, :inbox, :outbox, :donebox
20
+ attr_accessor :last_time, :logger, :inbox, :outbox, :donebox
18
21
 
19
22
  def use_resources
20
- @@use_resources
21
- end
23
+ @@use_resources
24
+ end
22
25
 
23
26
  def use_resources=(value)
24
27
  @@use_resources = value
25
- end
28
+ end
26
29
 
27
30
  def conduit_for(formatid)
28
31
  conduitid = File.basename(formatid.to_s, '.zip').split('.').last
29
32
  conduit_class = conduitid.upcase + 'Conduit'
30
- unless Object.const_defined?(conduit_class)
33
+ unless Object.const_defined?(conduit_class)
31
34
  conduit_file = conduitid.downcase + '_' + 'conduit.rb'
32
35
  require File.join(CONDUIT_DIR, conduit_file)
33
36
  end
@@ -46,498 +49,10 @@ module DataGateway
46
49
  Object.const_get(class_name)
47
50
  end
48
51
  end
49
-
50
- class NullEncoder
51
- def pack(data)
52
- data
53
- end
54
-
55
- def unpack(data)
56
- data
57
- end
58
- end
59
-
60
- class Encoder
61
- def initialize(options)
62
- @pack = Iconv.new(options[:to], options[:from])
63
- @unpack = Iconv.new(options[:from], options[:to])
64
- end
65
-
66
- def pack(data)
67
- @pack.iconv(data)
68
- end
69
-
70
- def unpack(data)
71
- @unpack.iconv(data)
72
- end
73
- end
74
-
75
- class ColumnMapper
76
- attr :options
77
- def initialize(table_mapper, options = {})
78
- @table_mapper = table_mapper
79
- @options = options
80
- end
81
-
82
- def apply_to(record, value)
83
- DataGateway.logger.debug "apply_to(#{record.inspect}, #{value}) options #{@options.inspect}"
84
- send(@options[:method], record, value)
85
- end
86
-
87
- # value mappers
88
- def none(record, value)
89
- # do nothing
90
- end
91
-
92
- def write_to(record, value)
93
- record[@options[:write_to]] = value
94
- end
95
-
96
- def lookup(record, value)
97
-
98
- result_status = {}
99
- new_value = lookup_map(value, result_status)
100
- if result_status[:found]
101
- write_to(record, new_value)
102
- else
103
- @table_mapper.register_fixup(value, self)
104
- end
105
- end
106
-
107
- private
108
-
109
- def lookup_map(value, result_status)
110
- DataGateway.logger.debug "lookup_map(#{value}, #{result_status}) options #{@options.inspect}"
111
- lookup_model = DataGateway.model_for(@options[:table])
112
- finder = "find_or_create_by_#{@options[:key]}"
113
- lookup_rec = lookup_model.send(finder, value)
114
- result_status[:found] = !(lookup_rec.nil?)
115
- if result_status[:found]
116
- lookup_rec.send(@options[:value]).to_s # valor mapeado
117
- else
118
- nil
119
- end
120
- end
121
-
122
- def solve_fixup(record, value)
123
- DataGateway.logger.debug "mapper #{@map_method} opt. #{@options.inspect}"
124
- result_status = {}
125
- DataGateway.logger.debug "lookup #{record.inspect} -> <#{value}>"
126
- value = lookup_map(value, result_status)
127
- if result_status[:found]
128
- DataGateway.logger.debug "FOUND: #{value}"
129
- write_to(record, value)
130
- else
131
- DataGateway.logger.debug "NOT FOUND!"
132
- end
133
- end
134
- end
135
-
136
- class TableMapper
137
- attr :source
138
- attr :identity
139
-
140
- def initialize(job, table)
141
- @job = job
142
- @source = table
143
- @target = table
144
- @column_map = {}
145
- @identity = [:id]
146
- @ar_fixups = []
147
- @force_case = false
148
- clear_fixups
149
- end
150
-
151
- def ignore!
152
- @target = nil
153
- end
154
-
155
- def ignore?
156
- @target == nil
157
- end
158
-
159
- def target(table_name = nil)
160
- @target = table_name if table_name
161
- @target
162
- end
163
-
164
- def force_downcase
165
- @force_case = :downcase
166
- end
167
-
168
- def force_upcase
169
- @force_case = :upcase
170
- end
171
-
172
- def target_model
173
- DataGateway.model_for(@target)
174
- end
175
-
176
- # DSL syntax sugar for declare column mappers
177
- def columns(column_map)
178
- column_map.each_pair do |key, value|
179
- case value
180
- when Symbol, String: column_map[key] = write_to(value.to_sym)
181
- end
182
- end
183
- @column_map.merge! column_map
184
-
185
- @column_map.each_pair do |key, value|
186
- defaults = column_mapper_defaults(key)
187
- @column_map[key].options.replace(defaults.merge!(@column_map[key].options))
188
- #puts "map for #{key} => #{@column_map[key].options.inspect}"
189
- end
190
- @column_map
191
- end
192
-
193
- def none
194
- create_column_mapper({:method => :none})
195
- end
196
-
197
- def write_to(column)
198
- create_column_mapper({:method => :write_to, :write_to => column})
199
- end
200
-
201
- def lookup(options)
202
- options[:method] = :lookup
203
- create_column_mapper(options)
204
- end
205
52
 
206
- def identity_by(*cols)
207
- @identity = cols
208
- end
209
-
210
- # utilities
211
- def column_mapper_for(column_name)
212
- @column_map[column_name] ||= create_column_mapper(column_mapper_defaults(column_name))
213
- end
214
-
215
- def column_mapper_defaults(column_name)
216
- column_name = column_name.to_s.send(@force_case).to_sym if @force_case
217
- case column_name.to_s
218
- when /(.*)_id$/i: { :method => :lookup,
219
- :write_to => column_name,
220
- :table => $1.downcase.camelize.to_sym,
221
- :key => @job.get_mapper($1.downcase.camelize.to_sym).identity,
222
- :value => :id
223
- }
224
- else { :method => :write_to,
225
- :write_to => column_name
226
- }
227
- end
228
-
229
- end
230
-
231
- def update_all(records)
232
- DataGateway.logger.debug "importing #{records.size} recods into table #{source}"
233
- klass = target_model
234
- DataGateway.logger.debug "target class #{klass} (PK: #{klass.primary_key})"
235
- records.each { |rec|
236
- rec = map(rec)
237
- update_rec(rec) unless current_fixups(rec)
238
- }
239
- end
240
-
241
- def clear_fixups
242
- @fixups = {}
243
- end
244
-
245
- def register_fixup(value, col_mapper)
246
- @ar_fixups << [value, col_mapper]
247
- end
248
-
249
- def current_fixups(rec)
250
- DataGateway.logger.debug "current fixups for: #{rec.inspect}"
251
- DataGateway.logger.debug @ar_fixups.inspect
252
- return false if @ar_fixups.empty?
253
- @fixups[rec] ||= []
254
- @fixups[rec].concat @ar_fixups
255
- @ar_fixups = []
256
- true
257
- end
258
-
259
- def solve_fixups
260
- DataGateway.logger.debug "start solve_fixups #{@fixups.size}"
261
- @fixups.each_pair { |record, data|
262
- data.each{|item|
263
- value = item[0]
264
- col_mapper = item[1]
265
- col_mapper.solve_fixup(record, value)
266
- }
267
- update_rec(record)
268
- }
269
- DataGateway.logger.debug "end solve_fixups"
270
- ensure
271
- clear_fixups
272
- end
273
-
274
- def update_rec(rec)
275
- DataGateway.logger.debug "update rec class #{target_model} '->' #{rec.inspect}"
276
- conditions = identity.map{|key| key.to_s}
277
- values = conditions.map{|key| rec.delete(key.to_sym)}
278
- finder = 'find_or_create_by_' + conditions.join('_')
279
- DataGateway.logger.debug "finder: #{finder}(#{values.join(',')})"
280
-
281
- ar = target_model.send(finder, *values)
282
- DataGateway.logger.debug "get: #{ar.inspect}"
283
- DataGateway.logger.debug "upd atrt: #{rec.inspect}"
284
- ar.update_attributes(rec)
285
- return ar[:id]
286
- end
287
-
288
- def map(ar)
289
- new_ar = {}
290
- ar.each_pair do |key, value|
291
- column_mapper_for(key).apply_to(new_ar, value)
292
- end
293
- new_ar
294
- end
295
- private
296
- def create_column_mapper( options)
297
- ColumnMapper.new(self, options)
298
- end
299
- end
300
-
301
- class Job
302
- attr_accessor :format_id
303
- attr :transfer
304
- attr :name
305
-
306
- def initialize(name, &block)
307
- @name = name
308
- @use_resources = DataGateway.use_resources
309
- @encoder = NullEncoder.new
310
- @transfer = nil
311
- instance_eval(&block) if block_given?
312
- end
313
-
314
- def transfer_for(options)
315
- options.merge! :logger => DataGateway.logger
316
- @transfer = SSHTransfer.new(options)
317
- end
318
-
319
- def run
320
- AppConfig.logger.info "running job #{name}"
321
- do_run
322
- AppConfig.logger.info "job #{name} finished"
323
- end
324
-
325
- private
326
- # only for debug purpouse
327
- def use_resources(bool)
328
- @use_resources = bool
329
- end
330
-
331
- def encode(options)
332
- @encoder = Encoder.new(options)
333
- end
334
-
335
- def pack(data)
336
- DataGateway.conduit_for(self.format_id).pack(data)
337
- end
338
-
339
- def unpack(data)
340
- DataGateway.conduit_for(format_id).unpack(data)
341
- end
342
-
343
- def dbchange_point
344
- @dbchange_point ||= DBChangePoint.class_for(ActiveRecord::Base.connection).new
345
- end
346
- end
347
-
348
- class Import < Job
349
- def initialize(name, &block)
350
- @mappers = {} #import mappers
351
- super
352
- end
353
-
354
- def do_run
355
- transfer.download if transfer and transfer.download_from
356
-
357
- Dir.glob(File.join(DataGateway::inbox, '*.zip')).sort.each do |filename|
358
- DataGateway.logger.info "Importing #{filename}"
359
- import_file(filename)
360
- FileUtils.mv(filename, DataGateway::donebox)
361
- DataGateway.logger.info "Import done for #{filename}"
362
- end
363
- end
364
-
365
- def get_mapper(table)
366
- @mappers[table] ||= TableMapper.new(self, table)
367
- end
368
-
369
- public # DSL
370
-
371
- def download_from(options)
372
- options.merge! 'local_files' => DataGateway.inbox
373
- transfer_for(options)
374
- end
375
-
376
- def importing(table, &block)
377
- get_mapper(table).instance_eval(&block) if block_given?
378
- end
379
-
380
- private
381
-
382
- def import_file(file_name)
383
- self.format_id = file_name
384
- clear_fixups
385
- begin
386
- Zip::ZipInputStream::open(file_name) { |io|
387
- while (entry = io.get_next_entry)
388
- content = io.read
389
- fname = entry.name
390
- if fname =~ /^attach\//
391
- fname = fname.gsub(/^attach\//, '')
392
- import_attach(fname, content)
393
- else
394
- class_name = File.basename(fname, '.*').downcase.camelize.to_sym
395
- import_class(class_name, content)
396
- end
397
- end
398
- }
399
- ensure
400
- solve_fixups
401
- end
402
- end
403
-
404
- def clear_fixups
405
- @mappers.each_value {|mapper|
406
- mapper.clear_fixups
407
- }
408
- end
409
-
410
- def solve_fixups
411
- @mappers.each_value {|mapper|
412
- mapper.solve_fixups
413
- }
414
- end
415
-
416
- def import_class(class_name, data)
417
- DataGateway.logger.info "Importing data for #{class_name}"
418
- mapper = get_mapper(class_name)
419
- if mapper.ignore?
420
- DataGateway.logger.info "Ignored"
421
- return
422
- end
423
- mapper.update_all unpack(data)
424
- #p dbchange_point.capture_for(klass)
425
- DataGateway.logger.info "done for #{class_name}"
426
- end
427
-
428
- def import_attach(fname, content)
429
- return unless @use_resources
430
- DataGateway.logger.info "importing attach #{fname}"
431
- target_file = File.expand_path(fname, DataGateway.resource_path)
432
- FileUtils.mkdir_p(File.dirname(target_file))
433
- File.open(target_file, "wb") {|f| f.write content}
434
- end
435
- end
436
-
437
- class Export < Job
438
- def initialize(name, &block)
439
- @exports = {}
440
- @exports_order = []
441
- export_to :csv #default format
442
- super
443
- end
444
-
445
- def each
446
- tables = @exports_order
447
- tables.each { |cname|
448
- yield cname, export_class(cname)
449
- }
450
- end
451
-
452
- def export_folder
453
- @export_folder ||= create_folder
454
- end
455
-
456
- def close_export_folder
457
- @export_folder.close if @export_folder
458
- end
459
-
460
- def do_run
461
- begin
462
- self.each { |cname, records|
463
- file_name = "#{cname}.#{self.format_id}"
464
- DataGateway.logger.debug "Exporting archive #{file_name}"
465
- #records.each { |rec|
466
- # rec.each {|key, value|
467
- # rec[key] = @encoder.pack(rec[key]) unless (key =~ /^img/i)
468
- # }
469
- #}
470
- write_file(file_name, records)
471
- }
472
- ensure
473
- close_export_folder
474
- end
475
-
476
- transfer.upload if transfer and transfer.upload_to
477
- end
478
-
479
- private # DSL
480
- def upload_to(options)
481
- options.merge! 'local_files' => File.join(DataGateway.outbox, "*.zip")
482
- transfer_for(options)
483
- end
484
-
485
- def exporting(table_name, options = {})
486
- @exports_order << table_name
487
- @exports[table_name] = options
488
- end
489
-
490
- def export_to(format)
491
- self.format_id = format
492
- end
493
- private
494
- def create_folder(outbox = DataGateway::outbox)
495
- FileUtils.mkpath(outbox)
496
- stamp = Time.now.strftime("%Y%m%d%H%M%S")
497
- folder_name = File.join(outbox, "#{stamp}.#{self.format_id}")
498
- #(APP_ENV == 'development') ? FolderWriter.new(folder_name) :
499
- ZipFolderWriter.new(folder_name)
500
- end
501
-
502
- def export_class(cname)
503
- result = []
504
- bm = Benchmark.measure {
505
- klass = DataGateway.model_for(cname.to_s.downcase.camelize)
506
- DataGateway.logger.info "Exporting #{klass} (#{self.format_id})"
507
- records = klass.find(:all, @exports[cname])
508
- records.each { |r| export_attachments(r) } if @use_resources
509
- result = pack(records) unless records.empty?
510
- }
511
- DataGateway.logger.info "data exported in #{bm}"
512
- result
513
- end
514
-
515
- def export_attachments(record)
516
- record.attributes.keys.select { |key| key =~ /^img/i }.each { |key|
517
- file = record.attributes[key]
518
- #TODO Si el path del attach es absoluto, quiza debemos tener cuidado al descomprimir el zip
519
- next if file.blank?
520
- path = File.expand_path(file, DataGateway.resource_path)
521
- DataGateway.logger.info "exporting attach #{path}"
522
- #TODO Si el fichero ya se exporto solo en este vuelta, no repetir!
523
- next unless File.exist?(path)
524
- attach_data = File.open(path, 'rb') { |f| f.read }
525
- write_file(File.join('attach', file), attach_data, 'b')
526
- }
527
- end
528
-
529
- def write_file(file_name, data, options = '')
530
- op = StringIO.new("", "w")
531
- op.puts data
532
- if (options == 'b')
533
- export_folder.open(file_name, "w" + options){|f| f.puts op.string}
534
- else
535
- export_folder.open(file_name, "w" + options){|f| f.puts @encoder.pack(op.string)}
536
- end
537
- end
538
- end
539
53
  public
540
- # DataGateway DSL
54
+
55
+ # DataGateway DSL
541
56
  def resource_path(path= nil)
542
57
  if path
543
58
  @@resource_path = File.expand_path(path)
@@ -545,9 +60,13 @@ module DataGateway
545
60
  @@resource_path || ''
546
61
  end
547
62
  end
548
-
63
+
549
64
  module_function(:resource_path)
550
-
65
+
66
+ def last_time(key)
67
+ DataGateway.last_time[key.to_s].utc.to_s(:db)
68
+ end
69
+
551
70
  def db_connection(conn)
552
71
  ::ActiveRecord::Base.establish_connection conn
553
72
  if conn['nax_empresa']
@@ -557,13 +76,11 @@ module DataGateway
557
76
 
558
77
  def nax_connection(connection)
559
78
  NAX::ActiveRecord::Base.establish_connection connection
560
- end
79
+ end
561
80
 
562
81
  def import(name, &block)
563
82
  name = name.to_sym
564
- self.class.class_eval {
565
- attr_reader name
566
- }
83
+ self.class.class_eval {attr_reader name }
567
84
  new_job = Import.new(name, &block)
568
85
  instance_variable_set('@' + name.to_s, new_job)
569
86
  return new_job
@@ -571,25 +88,21 @@ module DataGateway
571
88
 
572
89
  def export(name, &block)
573
90
  name = name.to_sym
574
- self.class.class_eval {
575
- attr_reader name
576
- }
91
+ self.class.class_eval { attr_reader name }
577
92
  new_job = Export.new(name, &block)
578
93
  instance_variable_set('@' + name.to_s, new_job)
579
94
  return new_job
580
95
  end
581
-
582
- def upload_to(options)
583
- Export.new('upload') {
584
- upload_to options
585
- }.run
586
96
 
97
+ def upload_to(options)
98
+ options.merge! :logger => DataGateway.logger
99
+ options.merge! 'local_files' => File.join(DataGateway.outbox, "*.zip")
100
+ SSHTransfer.new(options).upload
587
101
  end
588
102
 
589
103
  def download_from(options)
590
- Import.new('download') {
591
- download_from options
592
- }.run
593
-
104
+ options.merge! :logger => DataGateway.logger
105
+ options.merge! 'local_files' => DataGateway.inbox
106
+ SSHTransfer.new(options).download
594
107
  end
595
108
  end
File without changes
@@ -0,0 +1,27 @@
1
+ module DataGateway
2
+
3
+ class NullEncoder
4
+ def pack(data)
5
+ data
6
+ end
7
+
8
+ def unpack(data)
9
+ data
10
+ end
11
+ end
12
+
13
+ class Encoder
14
+ def initialize(options)
15
+ @pack = Iconv.new(options[:to], options[:from])
16
+ @unpack = Iconv.new(options[:from], options[:to])
17
+ end
18
+
19
+ def pack(data)
20
+ @pack.iconv(data)
21
+ end
22
+
23
+ def unpack(data)
24
+ @unpack.iconv(data)
25
+ end
26
+ end
27
+ end
File without changes