csv_row_model 1.0.0.beta1 → 1.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
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