csv_row_model 1.0.0.beta1 → 1.0.0.beta2

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -1
  3. data/README.md +22 -9
  4. data/lib/csv_row_model/concerns/attributes_base.rb +86 -0
  5. data/lib/csv_row_model/concerns/check_options.rb +7 -9
  6. data/lib/csv_row_model/concerns/dynamic_columns_base.rb +47 -0
  7. data/lib/csv_row_model/concerns/export/attributes.rb +26 -0
  8. data/lib/csv_row_model/{export → concerns/export}/base.rb +1 -1
  9. data/lib/csv_row_model/concerns/export/dynamic_columns.rb +33 -0
  10. data/lib/csv_row_model/concerns/hidden_module.rb +15 -0
  11. data/lib/csv_row_model/concerns/import/attributes.rb +52 -0
  12. data/lib/csv_row_model/{import → concerns/import}/base.rb +10 -18
  13. data/lib/csv_row_model/{import → concerns/import}/csv_string_model.rb +2 -3
  14. data/lib/csv_row_model/concerns/import/dynamic_columns.rb +50 -0
  15. data/lib/csv_row_model/{import → concerns/import}/represents.rb +2 -2
  16. data/lib/csv_row_model/concerns/inspect.rb +5 -6
  17. data/lib/csv_row_model/{model/columns.rb → concerns/model/attributes.rb} +8 -31
  18. data/lib/csv_row_model/{model → concerns/model}/base.rb +1 -11
  19. data/lib/csv_row_model/{model → concerns/model}/children.rb +7 -1
  20. data/lib/csv_row_model/{model → concerns/model}/dynamic_columns.rb +23 -42
  21. data/lib/csv_row_model/export/file_model.rb +2 -15
  22. data/lib/csv_row_model/export.rb +3 -3
  23. data/lib/csv_row_model/import/file.rb +62 -22
  24. data/lib/csv_row_model/import/file_model.rb +3 -4
  25. data/lib/csv_row_model/import.rb +5 -9
  26. data/lib/csv_row_model/internal/attribute_base.rb +22 -0
  27. data/lib/csv_row_model/internal/concerns/column_shared.rb +21 -0
  28. data/lib/csv_row_model/internal/concerns/dynamic_column_shared.rb +28 -0
  29. data/lib/csv_row_model/internal/dynamic_column_attribute_base.rb +42 -0
  30. data/lib/csv_row_model/internal/export/attribute.rb +15 -0
  31. data/lib/csv_row_model/internal/export/dynamic_column_attribute.rb +21 -0
  32. data/lib/csv_row_model/{import/cell.rb → internal/import/attribute.rb} +4 -13
  33. data/lib/csv_row_model/{import → internal/import}/csv.rb +11 -13
  34. data/lib/csv_row_model/internal/import/dynamic_column_attribute.rb +33 -0
  35. data/lib/csv_row_model/{import → internal/import}/representation.rb +2 -2
  36. data/lib/csv_row_model/internal/model/dynamic_column_header.rb +22 -0
  37. data/lib/csv_row_model/internal/model/header.rb +25 -0
  38. data/lib/csv_row_model/model/file_model.rb +0 -1
  39. data/lib/csv_row_model/model.rb +5 -12
  40. data/lib/csv_row_model/validators/{boolean_format.rb → boolean_format_validator.rb} +1 -1
  41. data/lib/csv_row_model/validators/date_format_validator.rb +7 -0
  42. data/lib/csv_row_model/validators/date_time_format_validator.rb +7 -0
  43. data/lib/csv_row_model/validators/{default_change.rb → default_change_validator.rb} +0 -0
  44. data/lib/csv_row_model/validators/float_format_validator.rb +7 -0
  45. data/lib/csv_row_model/validators/integer_format_validator.rb +15 -0
  46. data/lib/csv_row_model/version.rb +1 -1
  47. data/lib/csv_row_model.rb +16 -37
  48. metadata +35 -33
  49. data/lib/csv_row_model/engine.rb +0 -5
  50. data/lib/csv_row_model/export/attributes.rb +0 -46
  51. data/lib/csv_row_model/export/cell.rb +0 -24
  52. data/lib/csv_row_model/export/dynamic_column_cell.rb +0 -29
  53. data/lib/csv_row_model/export/dynamic_columns.rb +0 -46
  54. data/lib/csv_row_model/import/attributes.rb +0 -66
  55. data/lib/csv_row_model/import/dynamic_column_cell.rb +0 -37
  56. data/lib/csv_row_model/import/dynamic_columns.rb +0 -59
  57. data/lib/csv_row_model/import/file/callbacks.rb +0 -17
  58. data/lib/csv_row_model/import/file/validations.rb +0 -43
  59. data/lib/csv_row_model/model/comparison.rb +0 -15
  60. data/lib/csv_row_model/model/dynamic_column_cell.rb +0 -44
  61. data/lib/csv_row_model/validators/date_format.rb +0 -9
  62. data/lib/csv_row_model/validators/date_time_format.rb +0 -9
  63. data/lib/csv_row_model/validators/float_format.rb +0 -9
  64. data/lib/csv_row_model/validators/integer_format.rb +0 -9
  65. data/lib/csv_row_model/validators/number_validator.rb +0 -16
  66. data/lib/csv_row_model/validators/validate_attributes.rb +0 -27
@@ -4,15 +4,7 @@ module CsvRowModel
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  included do
7
- attr_reader :context
8
-
9
- # @return [Model] return the parent, if this instance is a child
10
- attr_reader :parent
11
-
12
- # @return [DateTime] return when self has been intialized
13
- attr_reader :initialized_at
14
-
15
- validate_attributes :parent
7
+ attr_reader :context, :parent, :initialized_at
16
8
  end
17
9
 
18
10
  # @param [Hash] options
@@ -25,14 +17,12 @@ module CsvRowModel
25
17
  end
26
18
 
27
19
  # Safe to override.
28
- #
29
20
  # @return [Boolean] returns true, if this instance should be skipped
30
21
  def skip?
31
22
  !valid?
32
23
  end
33
24
 
34
25
  # Safe to override.
35
- #
36
26
  # @return [Boolean] returns true, if the entire csv file should stop reading
37
27
  def abort?
38
28
  false
@@ -1,7 +1,13 @@
1
+ require 'inherited_class_var'
2
+ require 'csv_row_model/concerns/hidden_module'
3
+
1
4
  module CsvRowModel
2
5
  module Model
3
6
  module Children
4
7
  extend ActiveSupport::Concern
8
+ include InheritedClassVar
9
+ include HiddenModule
10
+
5
11
  included do
6
12
  inherited_class_hash :has_many_relationships
7
13
  end
@@ -55,7 +61,7 @@ module CsvRowModel
55
61
 
56
62
  merge_has_many_relationships(relation_name => row_model_class)
57
63
 
58
- define_method(relation_name) do
64
+ define_proxy_method(relation_name) do
59
65
  #
60
66
  # equal to: @relation_name ||= []
61
67
  #
@@ -1,40 +1,29 @@
1
- require 'csv_row_model/model/dynamic_column_cell'
1
+ require 'csv_row_model/internal/model/dynamic_column_header'
2
2
 
3
3
  module CsvRowModel
4
4
  module Model
5
5
  module DynamicColumns
6
6
  extend ActiveSupport::Concern
7
+ include InheritedClassVar
8
+
7
9
  included do
8
10
  inherited_class_hash :dynamic_columns
9
11
  end
10
12
 
11
- # See Model::Columns#attributes
12
- def attributes
13
- super.merge!(attributes_from_method_names(self.class.dynamic_column_names))
14
- end
15
-
16
13
  class_methods do
17
14
  def dynamic_columns?
18
15
  dynamic_columns.present?
19
16
  end
20
17
 
21
- def is_dynamic_column?(column_name)
22
- dynamic_columns.keys.include?(column_name)
23
- end
24
-
25
- # Safe to override. Method applied to each dynamic_column attribute
26
- #
27
- # @param cells [Array] Array of values
28
- # @param column_name [Symbol] Dynamic column name
29
- def format_dynamic_column_cells(cells, column_name, column_index, context)
30
- cells
18
+ # @return [Integer] index of dynamic_column of all columns
19
+ def dynamic_column_index(column_name)
20
+ offset = dynamic_column_names.index(column_name)
21
+ offset ? columns.size + offset : nil
31
22
  end
32
23
 
33
- # Safe to override
34
- #
35
- # @return [String] formatted header
36
- def format_dynamic_column_header(header_model, column_name, dynamic_column_index, index_of_column, context)
37
- header_model
24
+ # @return [Array<Symbol>] column names for the row model
25
+ def dynamic_column_names
26
+ dynamic_columns.keys
38
27
  end
39
28
 
40
29
  # See Model::Columns::headers
@@ -43,35 +32,27 @@ module CsvRowModel
43
32
  end
44
33
 
45
34
  def dynamic_column_headers(context={})
46
- context = OpenStruct.new(context)
47
-
48
- dynamic_columns.map do |column_name, options|
49
- Array(context.public_send(column_name)).map.with_index do |header_model, index_of_column|
50
- header_proc = options[:header] ||
51
- ->(header_model) { format_dynamic_column_header(header_model, column_name, dynamic_column_index(column_name), index_of_column, context) }
52
- instance_exec(header_model, &header_proc)
53
- end
54
- end.flatten
35
+ dynamic_column_names.map { |column_name| DynamicColumnHeader.new(column_name, self, context).value }.flatten
55
36
  end
56
37
 
57
- # @return [Integer] index of dynamic_column of all columns
58
- def dynamic_column_index(column_name)
59
- offset = dynamic_column_names.index(column_name)
60
- offset ? columns.size + offset : nil
61
- end
62
-
63
- # @return [Array<Symbol>] column names for the row model
64
- def dynamic_column_names
65
- dynamic_columns.keys
38
+ # Safe to override. Method applied to each dynamic_column attribute
39
+ #
40
+ # @param cells [Array] Array of values
41
+ # @param column_name [Symbol] Dynamic column name
42
+ def format_dynamic_column_cells(cells, column_name, column_index, context)
43
+ cells
66
44
  end
67
45
 
68
- def singular_dynamic_attribute_method_name(column_name)
69
- column_name.to_s.singularize
46
+ # Safe to override
47
+ #
48
+ # @return [String] formatted header
49
+ def format_dynamic_column_header(header_model, column_name, dynamic_column_index, context)
50
+ header_model
70
51
  end
71
52
 
72
53
  protected
73
54
 
74
- VALID_OPTIONS_KEYS = %i[header].freeze
55
+ VALID_OPTIONS_KEYS = %i[header header_models_context_key].freeze
75
56
 
76
57
  # define a dynamic_column, must be after all normal columns
77
58
  #
@@ -7,14 +7,8 @@ module CsvRowModel
7
7
  # and everything else is return as is.
8
8
  def to_rows
9
9
  rows_template.map.with_index do |row, index|
10
- [].tap do |result|
11
- row.each do |cell|
12
- if header? cell
13
- result << self.class.format_header(cell, index, context)
14
- else
15
- result << cell.to_s
16
- end
17
- end
10
+ row.map do |cell|
11
+ self.class.row_names.include?(cell) ? self.class.format_header(cell, index, context) : cell.to_s
18
12
  end
19
13
  end
20
14
  end
@@ -31,13 +25,6 @@ module CsvRowModel
31
25
  class_methods do
32
26
  def setup(csv, context, with_headers: true); end
33
27
  end
34
-
35
- private
36
-
37
- def header?(cell)
38
- self.class.is_row_name? cell
39
- end
40
-
41
28
  end
42
29
  end
43
30
  end
@@ -1,6 +1,6 @@
1
- require 'csv_row_model/export/base'
2
- require 'csv_row_model/export/dynamic_columns'
3
- require 'csv_row_model/export/attributes'
1
+ require 'csv_row_model/concerns/export/base'
2
+ require 'csv_row_model/concerns/export/dynamic_columns'
3
+ require 'csv_row_model/concerns/export/attributes'
4
4
 
5
5
  module CsvRowModel
6
6
  # Include this to with {Model} to have a RowModel for exporting to CSVs.
@@ -1,40 +1,39 @@
1
- require 'csv_row_model/import/file/callbacks'
2
- require 'csv_row_model/import/file/validations'
1
+ require 'csv_row_model/internal/import/csv'
3
2
 
4
3
  module CsvRowModel
5
4
  module Import
6
5
  # Represents a csv file and handles parsing to return `Import`
7
6
  class File
8
- include Callbacks
9
- include Validations
10
-
11
- # @return [Csv]
12
- attr_reader :csv
13
- # @return [Input] model class returned for importing
14
- attr_reader :row_model_class
15
-
16
- # Current index of the row model (not the same as number of rows)
17
- # @return [Integer] returns -1 = start of file, 0 to infinity = index of row_model, nil = end of file, no row_model
18
- attr_reader :index
19
- # @return [Input] the current row model set by {#next}
20
- attr_reader :current_row_model
21
- # @return [Input] the previous row model set by {#next}
22
- attr_reader :previous_row_model
23
- # @return [Hash] context passed to the {Import}
24
- attr_reader :context
7
+ extend ActiveModel::Callbacks
8
+ include ActiveWarnings
9
+
10
+ attr_reader :csv, :row_model_class
11
+ attr_reader :index # -1 = start of file, 0 to infinity = index of row_model, nil = end of file, no row_model
12
+ attr_reader :current_row_model, :previous_row_model, :context
25
13
 
26
14
  delegate :size, :end_of_file?, :line_number, to: :csv
27
15
 
16
+ define_model_callbacks :each_iteration
17
+ define_model_callbacks :next
18
+ define_model_callbacks :abort, :skip, only: :before
19
+
20
+ validate { errors.messages.merge!(csv.errors.messages) unless csv.valid? }
21
+ warnings do
22
+ validate :headers_invalid_row
23
+ end
24
+
28
25
  # @param [String] file_path path of csv file
29
26
  # @param [Import] row_model_class model class returned for importing
30
27
  # @param context [Hash] context passed to the {Import}
31
28
  def initialize(file_path, row_model_class, context={})
32
- @csv, @row_model_class, @context = Csv.new(file_path), row_model_class, context.to_h.symbolize_keys
29
+ @csv = Csv.new(file_path)
30
+ @row_model_class = row_model_class
31
+ @context = context.to_h.symbolize_keys
33
32
  reset
34
33
  end
35
34
 
36
- def header
37
- h = csv.header
35
+ def headers
36
+ h = csv.headers
38
37
  h.class == Array ? h : []
39
38
  end
40
39
 
@@ -75,6 +74,47 @@ module CsvRowModel
75
74
  end
76
75
  end
77
76
  end
77
+
78
+ # @return [Boolean] returns true, if the file should abort reading
79
+ def abort?
80
+ !valid? || !!current_row_model.try(:abort?)
81
+ end
82
+
83
+ # @return [Boolean] returns true, if the file should skip `current_row_model`
84
+ def skip?
85
+ !!current_row_model.try(:skip?)
86
+ end
87
+
88
+ protected
89
+ def _abort?
90
+ abort = abort?
91
+ run_callbacks(:abort) if abort
92
+ abort
93
+ end
94
+
95
+ def _skip?
96
+ skip = skip?
97
+ run_callbacks(:skip) if skip
98
+ skip
99
+ end
100
+
101
+ #
102
+ # Validations
103
+ #
104
+ def headers_invalid_row
105
+ errors.add(:csv, "has header with #{csv.headers.message}") if csv.headers.class < Exception
106
+ end
107
+
108
+ def headers_count
109
+ return if headers_invalid_row || !csv.valid?
110
+ return if row_model_class.dynamic_columns? || row_model_class.try(:row_names) # dynamic_column or FileModel
111
+
112
+ size_until_blank = ((headers || []).map { |h| h.try(:strip) }.rindex(&:present?) || -1) + 1
113
+ column_names = row_model_class.column_names
114
+
115
+ return if size_until_blank == column_names.size
116
+ errors.add(:headers, "count does not match. Given headers (#{size_until_blank}). Expected headers (#{column_names.size}): #{column_names.join(", ")}.")
117
+ end
78
118
  end
79
119
  end
80
120
  end
@@ -19,11 +19,10 @@ module CsvRowModel
19
19
  match ? match[1] : nil
20
20
  end
21
21
 
22
- # @return [Array] header_matchs matchers for the row model
23
22
  def header_matchers(context)
24
23
  @header_matchers ||= begin
25
- columns.map.with_index do |(name, options), index|
26
- if formatted_header = self.format_header(name, index, context)
24
+ row_names.map.with_index do |row_name, index|
25
+ if formatted_header = self.format_header(row_name, index, context)
27
26
  Regexp.new("^#{formatted_header}$", Regexp::IGNORECASE)
28
27
  end
29
28
  end.compact
@@ -49,7 +48,7 @@ module CsvRowModel
49
48
  end
50
49
  end
51
50
 
52
- new(source_row, source_header: csv.header, context: context, previous: file.previous_row_model)
51
+ new(source_row, source_headers: csv.headers, context: context, previous: file.previous_row_model)
53
52
  end
54
53
  end
55
54
  end
@@ -1,20 +1,16 @@
1
- require 'csv_row_model/import/base'
2
- require 'csv_row_model/import/csv_string_model'
3
- require 'csv_row_model/import/attributes'
4
- require 'csv_row_model/import/dynamic_columns'
5
- require 'csv_row_model/import/represents'
1
+ require 'csv_row_model/concerns/import/base'
2
+ require 'csv_row_model/concerns/import/attributes'
3
+ require 'csv_row_model/concerns/import/dynamic_columns'
4
+ require 'csv_row_model/concerns/import/represents'
6
5
 
7
6
  module CsvRowModel
8
7
  # Include this to with {Model} to have a RowModel for importing csvs.
9
8
  module Import
10
9
  extend ActiveSupport::Concern
11
10
 
12
- include Concerns::Inspect
13
-
14
11
  include Base
15
- include CsvStringModel
16
12
  include Attributes
17
- include DynamicColumns
18
13
  include Represents
14
+ include DynamicColumns
19
15
  end
20
16
  end
@@ -0,0 +1,22 @@
1
+ require 'csv_row_model/internal/concerns/column_shared'
2
+
3
+ module CsvRowModel
4
+ class AttributeBase
5
+ include ColumnShared
6
+
7
+ attr_reader :column_name, :row_model
8
+
9
+ def initialize(column_name, row_model)
10
+ @column_name = column_name
11
+ @row_model = row_model
12
+ end
13
+
14
+ def formatted_value
15
+ @formatted_value ||= row_model_class.format_cell(source_value, column_name, column_index, row_model.context)
16
+ end
17
+
18
+ def row_model_class
19
+ row_model.class
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ module CsvRowModel
2
+ module ColumnShared
3
+ #
4
+ # row_model
5
+ #
6
+ def context
7
+ row_model.context
8
+ end
9
+
10
+ #
11
+ # row_model_class
12
+ #
13
+ def column_index
14
+ row_model_class.index(column_name)
15
+ end
16
+
17
+ def options
18
+ row_model_class.columns[column_name]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,28 @@
1
+ require 'csv_row_model/internal/concerns/column_shared'
2
+
3
+ module CsvRowModel
4
+ module DynamicColumnShared
5
+ include ColumnShared
6
+ #
7
+ # row_model_class
8
+ #
9
+ def column_index
10
+ @dynamic_column_index ||= row_model_class.dynamic_column_index(column_name)
11
+ end
12
+
13
+ def options
14
+ row_model_class.dynamic_columns[column_name]
15
+ end
16
+
17
+ #
18
+ # header models
19
+ #
20
+ def header_models
21
+ Array(context.public_send(header_models_context_key))
22
+ end
23
+
24
+ def header_models_context_key
25
+ options[:header_models_context_key] || column_name
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,42 @@
1
+ require 'csv_row_model/internal/attribute_base'
2
+ require 'csv_row_model/internal/concerns/dynamic_column_shared'
3
+
4
+ module CsvRowModel
5
+ class DynamicColumnAttributeBase < AttributeBase
6
+ include DynamicColumnShared
7
+
8
+ def value
9
+ @value ||= row_model_class.format_dynamic_column_cells(unformatted_value, column_name, column_index, row_model.context)
10
+ end
11
+
12
+ def formatted_cells
13
+ source_cells.map.with_index.map do |cell, index|
14
+ row_model_class.format_cell(cell, column_name, column_index + index, row_model.context)
15
+ end
16
+ end
17
+
18
+ protected
19
+
20
+ def process_cell_method_name
21
+ self.class.process_cell_method_name(column_name)
22
+ end
23
+
24
+ # Calls the process_cell to return the value of a Attribute given the args
25
+ def call_process_cell(*args)
26
+ row_model.public_send(process_cell_method_name, *args)
27
+ end
28
+
29
+ class << self
30
+ def process_cell_method_name(column_name)
31
+ column_name.to_s.singularize.to_sym
32
+ end
33
+
34
+ # define a method to process each cell of the attribute method
35
+ # process_cell = one cell
36
+ # attribute_method = many cells = [process_cell(), process_cell()...]
37
+ def define_process_cell(row_model_class, column_name, &block)
38
+ row_model_class.define_proxy_method(process_cell_method_name(column_name), &block)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,15 @@
1
+ require 'csv_row_model/internal/attribute_base'
2
+
3
+ module CsvRowModel
4
+ module Export
5
+ class Attribute < CsvRowModel::AttributeBase
6
+ def value
7
+ formatted_value
8
+ end
9
+
10
+ def source_value
11
+ row_model.public_send(column_name)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ require 'csv_row_model/internal/dynamic_column_attribute_base'
2
+
3
+ module CsvRowModel
4
+ module Export
5
+ class DynamicColumnAttribute < CsvRowModel::DynamicColumnAttributeBase
6
+ def unformatted_value
7
+ formatted_cells
8
+ end
9
+
10
+ def source_cells
11
+ header_models.map { |header_model| call_process_cell(header_model) }
12
+ end
13
+
14
+ class << self
15
+ def define_process_cell(row_model_class, column_name)
16
+ super { |header_model| header_model }
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,15 +1,14 @@
1
- require 'csv_row_model/validators/boolean_format'
1
+ require 'csv_row_model/internal/attribute_base'
2
2
 
3
3
  module CsvRowModel
4
4
  module Import
5
- class Cell
6
- attr_reader :column_name, :source_value, :csv_string_model_errors, :row_model
5
+ class Attribute < CsvRowModel::AttributeBase
6
+ attr_reader :source_value, :csv_string_model_errors
7
7
 
8
8
  def initialize(column_name, source_value, csv_string_model_errors, row_model)
9
- @column_name = column_name
10
9
  @source_value = source_value
11
10
  @csv_string_model_errors = csv_string_model_errors
12
- @row_model = row_model
11
+ super(column_name, row_model)
13
12
  end
14
13
 
15
14
  def value
@@ -19,10 +18,6 @@ module CsvRowModel
19
18
  end
20
19
  end
21
20
 
22
- def formatted_value
23
- @formatted_value ||= row_model.class.format_cell(source_value, column_name, row_model.class.index(column_name), row_model.context)
24
- end
25
-
26
21
  def parsed_value
27
22
  @parsed_value ||= begin
28
23
  value = formatted_value
@@ -45,10 +40,6 @@ module CsvRowModel
45
40
  [formatted_value, default_value] if default?
46
41
  end
47
42
 
48
- def options
49
- row_model.class.columns[column_name]
50
- end
51
-
52
43
  protected
53
44
 
54
45
  # Mapping of column type classes to a parsing lambda. These are applied after {Import.format_cell}.
@@ -11,7 +11,7 @@ module CsvRowModel
11
11
 
12
12
  include ActiveModel::Validations
13
13
 
14
- validate { begin; _ruby_csv; rescue => e; errors.add(:ruby_csv, e.message) end }
14
+ validate { begin; _ruby_csv; rescue => e; errors.add(:csv, e.message) end }
15
15
 
16
16
  def initialize(file_path)
17
17
  @file_path = file_path
@@ -24,18 +24,16 @@ module CsvRowModel
24
24
  @size ||= `wc -l #{file_path}`.split[0].to_i + 1
25
25
  end
26
26
 
27
- # If the current position is at the header, skip it and return it. Otherwise, only return false.
27
+ # If the current position is at the headers, skip it and return it. Otherwise, only return false.
28
28
  # @return [Boolean, Array] returns false, if header is already skipped, otherwise returns the header
29
- def skip_header
30
- start_of_file? ? (@header = read_row) : false
29
+ def skip_headers
30
+ start_of_file? ? (@headers = read_row) : false
31
31
  end
32
32
 
33
33
  # Returns the header __without__ changing the position of the CSV
34
34
  # @return [Array, nil] the header
35
- def header
36
- return unless valid?
37
- return @header if @header
38
- @header = next_row
35
+ def headers
36
+ @headers ||= next_row
39
37
  end
40
38
 
41
39
  # Resets the file to the start of file
@@ -43,7 +41,7 @@ module CsvRowModel
43
41
  return false unless valid?
44
42
 
45
43
  @line_number = 0
46
- @current_row = @next_row = @skipped_rows = @next_skipped_rows = nil
44
+ @headers = @current_row = @next_row = @skipped_rows = @next_skipped_rows = nil
47
45
 
48
46
  @ruby_csv.try(:close)
49
47
  @ruby_csv = _ruby_csv
@@ -83,13 +81,13 @@ module CsvRowModel
83
81
  CSV.open(file_path)
84
82
  end
85
83
 
86
- def _read_row(ruby_csv=@ruby_csv)
84
+ def _read_row
87
85
  return unless valid?
88
- ruby_csv.readline
86
+ @ruby_csv.readline.tap { |row| @headers ||= row }
89
87
  rescue Exception => e
90
- changed = e.exception(e.message.gsub(/line \d+\./, "line #{line_number + 1}.")) # line numbers are usually off
88
+ changed = e.exception(e.message.gsub(/line \d+\.?/, "line #{line_number + 1}.")) # line numbers are usually off
91
89
  changed.set_backtrace(e.backtrace)
92
- return changed
90
+ changed
93
91
  end
94
92
  end
95
93
  end
@@ -0,0 +1,33 @@
1
+ require 'csv_row_model/internal/dynamic_column_attribute_base'
2
+
3
+ module CsvRowModel
4
+ module Import
5
+ class DynamicColumnAttribute < CsvRowModel::DynamicColumnAttributeBase
6
+ attr_reader :source_headers, :source_cells
7
+
8
+ def initialize(column_name, source_headers, source_cells, row_model)
9
+ @source_headers = source_headers
10
+ @source_cells = source_cells
11
+ super(column_name, row_model)
12
+ end
13
+
14
+ def unformatted_value
15
+ formatted_cells.zip(formatted_headers).map do |formatted_cell, source_headers|
16
+ call_process_cell(formatted_cell, source_headers)
17
+ end
18
+ end
19
+
20
+ def formatted_headers
21
+ source_headers.map do |source_headers|
22
+ row_model_class.format_dynamic_column_header(source_headers, column_name, column_index, row_model.context)
23
+ end
24
+ end
25
+
26
+ class << self
27
+ def define_process_cell(row_model_class, column_name)
28
+ super { |formatted_cell, source_headers| formatted_cell }
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,7 +1,7 @@
1
1
  module CsvRowModel
2
2
  module Import
3
3
  class Representation
4
- include Concerns::CheckOptions
4
+ include CheckOptions
5
5
  VALID_OPTIONS = %i[memoize empty_value dependencies].freeze
6
6
 
7
7
  attr_reader :name, :options, :row_model
@@ -52,7 +52,7 @@ module CsvRowModel
52
52
  end
53
53
 
54
54
  def define_lambda_method(row_model_class, representation_name, &block)
55
- row_model_class.send(:define_method, lambda_name(representation_name), &block)
55
+ row_model_class.define_proxy_method(lambda_name(representation_name), &block)
56
56
  end
57
57
  end
58
58
  end