topographer 0.0.7 → 0.0.8

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 (53) hide show
  1. checksums.yaml +7 -0
  2. data/lib/topographer/exceptions.rb +5 -3
  3. data/lib/topographer/importer.rb +25 -9
  4. data/lib/topographer/importer/helpers.rb +19 -13
  5. data/lib/topographer/importer/helpers/write_log_to_csv.rb +75 -67
  6. data/lib/topographer/importer/importable.rb +7 -3
  7. data/lib/topographer/importer/input.rb +11 -4
  8. data/lib/topographer/importer/input/base.rb +21 -15
  9. data/lib/topographer/importer/input/delimited_spreadsheet.rb +55 -0
  10. data/lib/topographer/importer/input/roo.rb +31 -24
  11. data/lib/topographer/importer/input/source_data.rb +14 -8
  12. data/lib/topographer/importer/logger.rb +10 -6
  13. data/lib/topographer/importer/logger/base.rb +75 -66
  14. data/lib/topographer/importer/logger/fatal_error_entry.rb +22 -16
  15. data/lib/topographer/importer/logger/log_entry.rb +31 -25
  16. data/lib/topographer/importer/logger/simple.rb +25 -19
  17. data/lib/topographer/importer/mapper.rb +58 -53
  18. data/lib/topographer/importer/mapper/default_field_mapping.rb +23 -17
  19. data/lib/topographer/importer/mapper/field_mapping.rb +56 -49
  20. data/lib/topographer/importer/mapper/ignored_field_mapping.rb +14 -7
  21. data/lib/topographer/importer/mapper/mapper_builder.rb +57 -44
  22. data/lib/topographer/importer/mapper/mapping_columns.rb +51 -44
  23. data/lib/topographer/importer/mapper/mapping_validator.rb +39 -32
  24. data/lib/topographer/importer/mapper/result.rb +21 -15
  25. data/lib/topographer/importer/mapper/validation_field_mapping.rb +35 -28
  26. data/lib/topographer/importer/strategy.rb +12 -6
  27. data/lib/topographer/importer/strategy/base.rb +43 -38
  28. data/lib/topographer/importer/strategy/create_or_update_record.rb +39 -34
  29. data/lib/topographer/importer/strategy/import_new_record.rb +16 -10
  30. data/lib/topographer/importer/strategy/import_status.rb +27 -20
  31. data/lib/topographer/importer/strategy/update_record.rb +28 -24
  32. data/lib/topographer/version.rb +1 -1
  33. data/spec/assets/test_files/a_csv.csv +3 -0
  34. data/spec/topographer/importer/helpers/write_log_to_csv_spec.rb +4 -4
  35. data/spec/topographer/importer/importer_spec.rb +21 -5
  36. data/spec/topographer/importer/input/delimited_spreadsheet_spec.rb +90 -0
  37. data/spec/topographer/importer/input/source_data_spec.rb +2 -2
  38. data/spec/topographer/importer/logger/base_spec.rb +19 -0
  39. data/spec/topographer/importer/logger/fatal_error_entry_spec.rb +2 -2
  40. data/spec/topographer/importer/logger/simple_spec.rb +3 -3
  41. data/spec/topographer/importer/mapper/default_field_mapping_spec.rb +2 -2
  42. data/spec/topographer/importer/mapper/field_mapping_spec.rb +21 -21
  43. data/spec/topographer/importer/mapper/mapper_builder_spec.rb +9 -9
  44. data/spec/topographer/importer/mapper/mapping_validator_spec.rb +10 -10
  45. data/spec/topographer/importer/mapper/validation_field_mapping_spec.rb +3 -3
  46. data/spec/topographer/importer/mapper_spec.rb +25 -25
  47. data/spec/topographer/importer/strategy/base_spec.rb +16 -5
  48. data/spec/topographer/importer/strategy/create_or_update_record_spec.rb +3 -3
  49. data/spec/topographer/importer/strategy/import_new_records_spec.rb +5 -5
  50. data/spec/topographer/importer/strategy/mapped_model.rb +9 -0
  51. data/spec/topographer/importer/strategy/update_record_spec.rb +3 -3
  52. data/topographer.gemspec +3 -2
  53. metadata +101 -102
@@ -1,21 +1,27 @@
1
- class Topographer::Importer::Mapper::Result
2
- attr_reader :data, :errors, :source_identifier
1
+ module Topographer
2
+ class Importer
3
+ class Mapper
4
+ class Result
5
+ attr_reader :data, :errors, :source_identifier
3
6
 
4
- def initialize(source_identifier)
5
- @source_identifier = source_identifier
6
- @data = {}
7
- @errors = {}
8
- end
7
+ def initialize(source_identifier)
8
+ @source_identifier = source_identifier
9
+ @data = {}
10
+ @errors = {}
11
+ end
9
12
 
10
- def add_data (key, value)
11
- @data[key] = value
12
- end
13
+ def add_data (key, value)
14
+ @data[key] = value
15
+ end
13
16
 
14
- def add_error (key, value)
15
- @errors[key] = value
16
- end
17
+ def add_error (key, value)
18
+ @errors[key] = value
19
+ end
17
20
 
18
- def errors?
19
- errors.any?
21
+ def errors?
22
+ errors.any?
23
+ end
24
+ end
25
+ end
20
26
  end
21
27
  end
@@ -1,36 +1,43 @@
1
- class Topographer::Importer::Mapper::ValidationFieldMapping < Topographer::Importer::Mapper::FieldMapping
2
- attr_reader :name
1
+ module Topographer
2
+ class Importer
3
+ class Mapper
4
+ class ValidationFieldMapping < Topographer::Importer::Mapper::FieldMapping
5
+ attr_reader :name
3
6
 
4
- def initialize(name, input_columns, &validation_block)
5
- unless block_given?
6
- raise Topographer::InvalidMappingError, 'Validation fields must have a behavior block'
7
- end
8
- @name = name
9
- @input_columns = Array(input_columns)
10
- @validation_block = validation_block
11
- @output_field = nil
12
- end
7
+ def initialize(name, input_columns, &validation_block)
8
+ unless block_given?
9
+ raise Topographer::InvalidMappingError, 'Validation fields must have a behavior block'
10
+ end
11
+ @name = name
12
+ @input_columns = Array(input_columns)
13
+ @validation_block = validation_block
14
+ @output_field = nil
15
+ end
13
16
 
14
- def process_input(input, result)
15
- mapping_input = input.slice(*input_columns)
16
- @invalid_keys = get_invalid_keys(mapping_input)
17
- if @invalid_keys.blank?
18
- @validation_block.(mapping_input)
19
- else
20
- result.add_error(name, invalid_input_error)
21
- end
17
+ def process_input(input, result)
18
+ mapping_input = input.slice(*input_columns)
19
+ @invalid_keys = get_invalid_keys(mapping_input)
20
+ if @invalid_keys.blank?
21
+ @validation_block.(mapping_input)
22
+ else
23
+ result.add_error(name, invalid_input_error)
24
+ end
22
25
 
23
- rescue => exception
24
- result.add_error(name, exception.message)
26
+ rescue => exception
27
+ result.add_error(name, exception.message)
25
28
 
26
- end
29
+ end
27
30
 
28
- def required?
29
- true
30
- end
31
+ def required?
32
+ true
33
+ end
34
+
35
+ private
31
36
 
32
- private
33
- def get_invalid_keys(input)
34
- @input_columns - input.keys
37
+ def get_invalid_keys(input)
38
+ @input_columns - input.keys
39
+ end
40
+ end
35
41
  end
42
+ end
36
43
  end
@@ -1,7 +1,13 @@
1
- module Topographer::Importer::Strategy
2
- require_relative 'strategy/base'
3
- require_relative 'strategy/import_new_record'
4
- require_relative 'strategy/update_record'
5
- require_relative 'strategy/create_or_update_record'
6
- require_relative 'strategy/import_status'
1
+ require_relative 'strategy/base'
2
+ require_relative 'strategy/import_new_record'
3
+ require_relative 'strategy/update_record'
4
+ require_relative 'strategy/create_or_update_record'
5
+ require_relative 'strategy/import_status'
6
+
7
+ module Topographer
8
+ class Importer
9
+ module Strategy
10
+
11
+ end
12
+ end
7
13
  end
@@ -1,42 +1,47 @@
1
- class Topographer::Importer::Strategy::Base
1
+ module Topographer
2
+ class Importer
3
+ module Strategy
4
+ class Base
5
+
6
+ attr_accessor :dry_run, :mapper
7
+
8
+ def initialize(mapper)
9
+ @mapper = mapper
10
+ @dry_run = false
11
+ end
12
+
13
+ def import_record (record_input)
14
+ raise NotImplementedError
15
+ end
16
+
17
+ def success_message
18
+ 'Imported'
19
+ end
20
+
21
+ def failure_message
22
+ 'Unable to import'
23
+ end
24
+
25
+ def should_persist_import?(status)
26
+ (@dry_run || status.errors?) ? false : true
27
+ end
28
+
29
+ private
30
+
31
+ def get_import_status(mapping_result, new_model_errors)
32
+ status = Topographer::Importer::Strategy::ImportStatus.new(mapping_result.source_identifier)
33
+ mapping_result.errors.values.each do |error|
34
+ status.add_error(:mapping, error)
35
+ end
36
+ new_model_errors.each do |error|
37
+ status.add_error(:validation, error)
38
+ end
39
+ status.message = (status.errors?) ? failure_message : success_message
40
+ status.set_timestamp
41
+ status
42
+ end
2
43
 
3
- attr_reader :mapper
4
- attr_accessor :dry_run
5
-
6
- def initialize(mapper)
7
- @mapper = mapper
8
- @dry_run = false
9
- end
10
-
11
- def import_record (record_input)
12
- raise NotImplementedError
13
- end
14
-
15
- def success_message
16
- 'Imported'
17
- end
18
-
19
- def failure_message
20
- 'Unable to import'
21
- end
22
-
23
- def should_persist_import?(status)
24
- (@dry_run || status.errors?) ? false : true
25
- end
26
-
27
- private
28
-
29
- def get_import_status(mapping_result, new_model_errors)
30
- status = Topographer::Importer::Strategy::ImportStatus.new(mapping_result.source_identifier)
31
- mapping_result.errors.values.each do |error|
32
- status.add_error(:mapping, error)
33
44
  end
34
- new_model_errors.each do |error|
35
- status.add_error(:validation, error)
36
- end
37
- status.message = (status.errors?) ? failure_message : success_message
38
- status.set_timestamp
39
- status
40
45
  end
41
-
46
+ end
42
47
  end
@@ -1,50 +1,55 @@
1
- class Topographer::Importer::Strategy::CreateOrUpdateRecord < Topographer::Importer::Strategy::Base
1
+ module Topographer
2
+ class Importer
3
+ module Strategy
4
+ class CreateOrUpdateRecord < Topographer::Importer::Strategy::Base
2
5
 
3
- def import_record (source_data)
4
- mapping_result = mapper.map_input(source_data)
6
+ def import_record (source_data)
7
+ mapping_result = mapper.map_input(source_data)
5
8
 
6
- search_params = mapping_result.data.slice(*mapper.key_fields)
7
- model_instances = mapper.model_class.where(search_params)
9
+ search_params = mapping_result.data.slice(*mapper.key_fields)
10
+ model_instances = mapper.model_class.where(search_params)
8
11
 
9
- if model_instances.any?
10
- model_instance = model_instances.first
11
- else
12
- model_instance = mapper.model_class.new(search_params)
13
- end
12
+ if model_instances.any?
13
+ model_instance = model_instances.first
14
+ else
15
+ model_instance = mapper.model_class.new(search_params)
16
+ end
14
17
 
15
- generate_messages(model_instance, search_params)
18
+ generate_messages(model_instance, search_params)
16
19
 
17
- model_instance.attributes = mapping_result.data
18
- model_instance.valid?
20
+ model_instance.attributes = mapping_result.data
21
+ model_instance.valid?
19
22
 
20
- model_errors = model_instance.errors.full_messages
21
- status = get_import_status(mapping_result, model_errors)
23
+ model_errors = model_instance.errors.full_messages
24
+ status = get_import_status(mapping_result, model_errors)
22
25
 
23
- model_instance.save if should_persist_import?(status)
26
+ model_instance.save if should_persist_import?(status)
24
27
 
25
- status
26
- end
28
+ status
29
+ end
27
30
 
31
+ def success_message
32
+ @success_message
33
+ end
28
34
 
29
- def success_message
30
- @success_message
31
- end
35
+ def failure_message
36
+ @failure_message
37
+ end
32
38
 
33
- def failure_message
34
- @failure_message
35
- end
39
+ private
36
40
 
37
- private
41
+ def generate_messages(model_instance, search_params)
42
+ if model_instance.new_record?
43
+ @success_message = 'Imported record'
44
+ @failure_message = 'Import failed'
45
+ else
46
+ params_string = search_params.map { |k, v| "#{k}: #{v}" }.join(', ')
47
+ @success_message = "Updated record matching `#{params_string}`"
48
+ @failure_message = "Update failed for record matching `#{params_string}`"
49
+ end
50
+ end
38
51
 
39
- def generate_messages(model_instance, search_params)
40
- if model_instance.new_record?
41
- @success_message = 'Imported record'
42
- @failure_message = 'Import failed'
43
- else
44
- params_string = search_params.map{|k, v| "#{k}: #{v}"}.join(', ')
45
- @success_message = "Updated record matching `#{params_string}`"
46
- @failure_message = "Update failed for record matching `#{params_string}`"
47
52
  end
48
53
  end
49
-
54
+ end
50
55
  end
@@ -1,17 +1,23 @@
1
- class Topographer::Importer::Strategy::ImportNewRecord < Topographer::Importer::Strategy::Base
1
+ module Topographer
2
+ class Importer
3
+ module Strategy
4
+ class ImportNewRecord < Topographer::Importer::Strategy::Base
2
5
 
3
- def import_record (source_data)
4
- mapping_result = mapper.map_input(source_data)
5
- new_model = mapper.model_class.new(mapping_result.data)
6
- new_model.valid?
7
- model_errors = new_model.errors.full_messages
8
- status = get_import_status(mapping_result, model_errors)
6
+ def import_record (source_data)
7
+ mapping_result = mapper.map_input(source_data)
8
+ new_model = mapper.model_class.new(mapping_result.data)
9
+ new_model.valid?
10
+ model_errors = new_model.errors.full_messages
11
+ status = get_import_status(mapping_result, model_errors)
9
12
 
10
- new_model.save if should_persist_import?(status)
13
+ new_model.save if should_persist_import?(status)
11
14
 
12
- status
13
- end
15
+ status
16
+ end
14
17
 
18
+ end
19
+ end
20
+ end
15
21
  end
16
22
 
17
23
 
@@ -1,28 +1,35 @@
1
- class Topographer::Importer::Strategy::ImportStatus
2
- attr_reader :errors, :input_identifier, :timestamp
3
- attr_accessor :message
1
+ module Topographer
2
+ class Importer
3
+ module Strategy
4
+ class ImportStatus
5
+ attr_reader :errors, :input_identifier, :timestamp
6
+ attr_accessor :message
4
7
 
5
- def initialize(input_identifier)
6
- @input_identifier = input_identifier
7
- @errors = {mapping: [],
8
- validation: []}
8
+ def initialize(input_identifier)
9
+ @input_identifier = input_identifier
10
+ @errors = {mapping: [],
11
+ validation: []}
9
12
 
10
- end
13
+ end
11
14
 
12
- def set_timestamp
13
- @timestamp ||= DateTime.now
14
- end
15
+ def set_timestamp
16
+ @timestamp ||= DateTime.now
17
+ end
15
18
 
16
- def add_error(error_source, error)
17
- errors[error_source] << error
18
- end
19
+ def add_error(error_source, error)
20
+ errors[error_source] << error
21
+ end
19
22
 
20
- def error_count
21
- errors.values.flatten.length
22
- end
23
+ def error_count
24
+ errors.values.flatten.length
25
+ end
23
26
 
24
- def errors?
25
- errors.values.flatten.any?
26
- end
27
+ def errors?
28
+ errors.values.flatten.any?
29
+ end
30
+
31
+ end
27
32
 
33
+ end
34
+ end
28
35
  end
@@ -1,33 +1,37 @@
1
- class Topographer::Importer::Strategy::UpdateRecord < Topographer::Importer::Strategy::Base
1
+ module Topographer
2
+ class Importer
3
+ module Strategy
4
+ class UpdateRecord < Topographer::Importer::Strategy::Base
2
5
 
3
- def import_record (source_data)
4
- mapping_result = mapper.map_input(source_data)
6
+ def import_record (source_data)
7
+ mapping_result = mapper.map_input(source_data)
5
8
 
6
- search_params = mapping_result.data.slice(*mapper.key_fields)
7
- model_instance = mapper.model_class.where(search_params).first
9
+ search_params = mapping_result.data.slice(*mapper.key_fields)
10
+ model_instance = mapper.model_class.where(search_params).first
8
11
 
9
- if model_instance
10
- model_instance.attributes = mapping_result.data
11
- model_instance.valid?
12
- model_errors = model_instance.errors.full_messages
13
- status = get_import_status(mapping_result, model_errors)
12
+ if model_instance
13
+ model_instance.attributes = mapping_result.data
14
+ model_instance.valid?
15
+ model_errors = model_instance.errors.full_messages
16
+ status = get_import_status(mapping_result, model_errors)
14
17
 
15
- model_instance.save if should_persist_import?(status)
16
- else
17
- status = get_import_status(mapping_result, ["Record not found with params: #{search_params.to_yaml}"])
18
- end
18
+ model_instance.save if should_persist_import?(status)
19
+ else
20
+ status = get_import_status(mapping_result, ["Record not found with params: #{search_params.to_yaml}"])
21
+ end
19
22
 
20
- status
21
- end
23
+ status
24
+ end
22
25
 
23
- def success_message
24
- 'Updated'
25
- end
26
+ def success_message
27
+ 'Updated'
28
+ end
26
29
 
27
- def failure_message
28
- 'Unable to update from import'
29
- end
30
+ def failure_message
31
+ 'Unable to update from import'
32
+ end
30
33
 
34
+ end
35
+ end
36
+ end
31
37
  end
32
-
33
-