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.
- checksums.yaml +4 -4
- data/.rspec +2 -1
- data/README.md +22 -9
- data/lib/csv_row_model/concerns/attributes_base.rb +86 -0
- data/lib/csv_row_model/concerns/check_options.rb +7 -9
- data/lib/csv_row_model/concerns/dynamic_columns_base.rb +47 -0
- data/lib/csv_row_model/concerns/export/attributes.rb +26 -0
- data/lib/csv_row_model/{export → concerns/export}/base.rb +1 -1
- data/lib/csv_row_model/concerns/export/dynamic_columns.rb +33 -0
- data/lib/csv_row_model/concerns/hidden_module.rb +15 -0
- data/lib/csv_row_model/concerns/import/attributes.rb +52 -0
- data/lib/csv_row_model/{import → concerns/import}/base.rb +10 -18
- data/lib/csv_row_model/{import → concerns/import}/csv_string_model.rb +2 -3
- data/lib/csv_row_model/concerns/import/dynamic_columns.rb +50 -0
- data/lib/csv_row_model/{import → concerns/import}/represents.rb +2 -2
- data/lib/csv_row_model/concerns/inspect.rb +5 -6
- data/lib/csv_row_model/{model/columns.rb → concerns/model/attributes.rb} +8 -31
- data/lib/csv_row_model/{model → concerns/model}/base.rb +1 -11
- data/lib/csv_row_model/{model → concerns/model}/children.rb +7 -1
- data/lib/csv_row_model/{model → concerns/model}/dynamic_columns.rb +23 -42
- data/lib/csv_row_model/export/file_model.rb +2 -15
- data/lib/csv_row_model/export.rb +3 -3
- data/lib/csv_row_model/import/file.rb +62 -22
- data/lib/csv_row_model/import/file_model.rb +3 -4
- data/lib/csv_row_model/import.rb +5 -9
- data/lib/csv_row_model/internal/attribute_base.rb +22 -0
- data/lib/csv_row_model/internal/concerns/column_shared.rb +21 -0
- data/lib/csv_row_model/internal/concerns/dynamic_column_shared.rb +28 -0
- data/lib/csv_row_model/internal/dynamic_column_attribute_base.rb +42 -0
- data/lib/csv_row_model/internal/export/attribute.rb +15 -0
- data/lib/csv_row_model/internal/export/dynamic_column_attribute.rb +21 -0
- data/lib/csv_row_model/{import/cell.rb → internal/import/attribute.rb} +4 -13
- data/lib/csv_row_model/{import → internal/import}/csv.rb +11 -13
- data/lib/csv_row_model/internal/import/dynamic_column_attribute.rb +33 -0
- data/lib/csv_row_model/{import → internal/import}/representation.rb +2 -2
- data/lib/csv_row_model/internal/model/dynamic_column_header.rb +22 -0
- data/lib/csv_row_model/internal/model/header.rb +25 -0
- data/lib/csv_row_model/model/file_model.rb +0 -1
- data/lib/csv_row_model/model.rb +5 -12
- data/lib/csv_row_model/validators/{boolean_format.rb → boolean_format_validator.rb} +1 -1
- data/lib/csv_row_model/validators/date_format_validator.rb +7 -0
- data/lib/csv_row_model/validators/date_time_format_validator.rb +7 -0
- data/lib/csv_row_model/validators/{default_change.rb → default_change_validator.rb} +0 -0
- data/lib/csv_row_model/validators/float_format_validator.rb +7 -0
- data/lib/csv_row_model/validators/integer_format_validator.rb +15 -0
- data/lib/csv_row_model/version.rb +1 -1
- data/lib/csv_row_model.rb +16 -37
- metadata +35 -33
- data/lib/csv_row_model/engine.rb +0 -5
- data/lib/csv_row_model/export/attributes.rb +0 -46
- data/lib/csv_row_model/export/cell.rb +0 -24
- data/lib/csv_row_model/export/dynamic_column_cell.rb +0 -29
- data/lib/csv_row_model/export/dynamic_columns.rb +0 -46
- data/lib/csv_row_model/import/attributes.rb +0 -66
- data/lib/csv_row_model/import/dynamic_column_cell.rb +0 -37
- data/lib/csv_row_model/import/dynamic_columns.rb +0 -59
- data/lib/csv_row_model/import/file/callbacks.rb +0 -17
- data/lib/csv_row_model/import/file/validations.rb +0 -43
- data/lib/csv_row_model/model/comparison.rb +0 -15
- data/lib/csv_row_model/model/dynamic_column_cell.rb +0 -44
- data/lib/csv_row_model/validators/date_format.rb +0 -9
- data/lib/csv_row_model/validators/date_time_format.rb +0 -9
- data/lib/csv_row_model/validators/float_format.rb +0 -9
- data/lib/csv_row_model/validators/integer_format.rb +0 -9
- data/lib/csv_row_model/validators/number_validator.rb +0 -16
- 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
|
-
|
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/
|
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
|
-
|
22
|
-
|
23
|
-
|
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
|
-
#
|
34
|
-
|
35
|
-
|
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
|
-
|
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
|
-
#
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
69
|
-
|
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
|
-
|
11
|
-
|
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
|
data/lib/csv_row_model/export.rb
CHANGED
@@ -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/
|
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
|
-
|
9
|
-
include
|
10
|
-
|
11
|
-
|
12
|
-
attr_reader :
|
13
|
-
|
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
|
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
|
37
|
-
h = csv.
|
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
|
-
|
26
|
-
if formatted_header = self.format_header(
|
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,
|
51
|
+
new(source_row, source_headers: csv.headers, context: context, previous: file.previous_row_model)
|
53
52
|
end
|
54
53
|
end
|
55
54
|
end
|
data/lib/csv_row_model/import.rb
CHANGED
@@ -1,20 +1,16 @@
|
|
1
|
-
require 'csv_row_model/import/base'
|
2
|
-
require 'csv_row_model/import/
|
3
|
-
require 'csv_row_model/import/
|
4
|
-
require 'csv_row_model/import/
|
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/
|
1
|
+
require 'csv_row_model/internal/attribute_base'
|
2
2
|
|
3
3
|
module CsvRowModel
|
4
4
|
module Import
|
5
|
-
class
|
6
|
-
attr_reader :
|
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
|
-
|
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(:
|
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
|
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
|
30
|
-
start_of_file? ? (@
|
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
|
36
|
-
|
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
|
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
|
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
|
-
|
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
|
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.
|
55
|
+
row_model_class.define_proxy_method(lambda_name(representation_name), &block)
|
56
56
|
end
|
57
57
|
end
|
58
58
|
end
|