csv_row_model 0.4.1 → 1.0.0.beta1
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/README.md +162 -162
- data/csv_row_model.gemspec +2 -1
- data/lib/csv_row_model/concerns/{invalid_options.rb → check_options.rb} +4 -6
- data/lib/csv_row_model/export/attributes.rb +11 -20
- data/lib/csv_row_model/export/base.rb +3 -3
- data/lib/csv_row_model/export/cell.rb +24 -0
- data/lib/csv_row_model/export/dynamic_column_cell.rb +29 -0
- data/lib/csv_row_model/export/dynamic_columns.rb +11 -13
- data/lib/csv_row_model/export/file.rb +7 -7
- data/lib/csv_row_model/export/file_model.rb +2 -2
- data/lib/csv_row_model/export.rb +0 -3
- data/lib/csv_row_model/import/attributes.rb +18 -81
- data/lib/csv_row_model/import/base.rb +26 -69
- data/lib/csv_row_model/import/cell.rb +77 -0
- data/lib/csv_row_model/import/csv.rb +23 -60
- data/lib/csv_row_model/import/csv_string_model.rb +65 -0
- data/lib/csv_row_model/import/dynamic_column_cell.rb +37 -0
- data/lib/csv_row_model/import/dynamic_columns.rb +20 -37
- data/lib/csv_row_model/import/file/validations.rb +5 -1
- data/lib/csv_row_model/import/file.rb +8 -3
- data/lib/csv_row_model/import/file_model.rb +5 -4
- data/lib/csv_row_model/import/representation.rb +60 -0
- data/lib/csv_row_model/import/represents.rb +85 -0
- data/lib/csv_row_model/import.rb +4 -3
- data/lib/csv_row_model/model/base.rb +5 -15
- data/lib/csv_row_model/model/children.rb +2 -1
- data/lib/csv_row_model/model/columns.rb +19 -16
- data/lib/csv_row_model/model/comparison.rb +1 -1
- data/lib/csv_row_model/model/dynamic_column_cell.rb +44 -0
- data/lib/csv_row_model/model/dynamic_columns.rb +26 -11
- data/lib/csv_row_model/model.rb +4 -3
- data/lib/csv_row_model/version.rb +1 -1
- data/lib/csv_row_model.rb +3 -1
- metadata +29 -10
- data/lib/csv_row_model/concerns/inherited_class_var.rb +0 -121
- data/lib/csv_row_model/import/presenter.rb +0 -153
- data/lib/csv_row_model/model/csv_string_model.rb +0 -7
@@ -0,0 +1,37 @@
|
|
1
|
+
module CsvRowModel
|
2
|
+
module Import
|
3
|
+
class DynamicColumnCell < CsvRowModel::Model::DynamicColumnCell
|
4
|
+
attr_reader :source_headers, :source_cells
|
5
|
+
|
6
|
+
def initialize(column_name, source_headers, source_cells, row_model)
|
7
|
+
@source_headers = source_headers
|
8
|
+
@source_cells = source_cells
|
9
|
+
super(column_name, row_model)
|
10
|
+
end
|
11
|
+
|
12
|
+
def unformatted_value
|
13
|
+
formatted_cells.zip(formatted_headers).map do |formatted_cell, source_header|
|
14
|
+
call_process_cell(formatted_cell, source_header)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def formatted_cells
|
19
|
+
source_cells.map.with_index do |source_cell, index|
|
20
|
+
row_model.class.format_cell(source_cell, column_name, dynamic_column_index + index, row_model.context)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def formatted_headers
|
25
|
+
source_headers.map.with_index do |source_header, index|
|
26
|
+
row_model.class.format_dynamic_column_header(source_header, column_name, dynamic_column_index, index, row_model.context)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class << self
|
31
|
+
def define_process_cell(row_model_class, column_name)
|
32
|
+
super { |formatted_cell, source_header| formatted_cell }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'csv_row_model/import/dynamic_column_cell'
|
2
|
+
|
1
3
|
module CsvRowModel
|
2
4
|
module Import
|
3
5
|
module DynamicColumns
|
@@ -7,55 +9,36 @@ module CsvRowModel
|
|
7
9
|
self.dynamic_column_names.each { |*args| define_dynamic_attribute_method(*args) }
|
8
10
|
end
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
# @return [Array] dynamic_column row data
|
16
|
-
def dynamic_source_row
|
17
|
-
self.class.dynamic_columns? ? source_row[self.class.columns.size..-1] : []
|
12
|
+
def cell_objects
|
13
|
+
@dynamic_column_cell_objects ||= super.merge(array_to_block_hash(self.class.dynamic_column_names) do |column_name|
|
14
|
+
DynamicColumnCell.new(column_name, dynamic_column_source_headers, dynamic_column_source_cells, self)
|
15
|
+
end)
|
18
16
|
end
|
19
17
|
|
20
18
|
# @return [Hash] a map of `column_name => original_attribute(column_name)`
|
21
19
|
def original_attributes
|
22
|
-
super
|
23
|
-
self.class.dynamic_column_names.each { |column_name| original_attribute(column_name) }
|
24
|
-
@original_attributes
|
20
|
+
super.merge!(array_to_block_hash(self.class.dynamic_column_names) { |column_name| original_attribute(column_name) })
|
25
21
|
end
|
26
22
|
|
27
|
-
# @return [
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
return @original_attributes[column_name] if original_attribute_memoized? column_name
|
32
|
-
|
33
|
-
values = dynamic_source_headers.map.with_index do |source_header, index|
|
34
|
-
value = self.class.format_cell(
|
35
|
-
dynamic_source_row[index],
|
36
|
-
source_header,
|
37
|
-
self.class.dynamic_index(column_name),
|
38
|
-
context
|
39
|
-
)
|
40
|
-
public_send(self.class.singular_dynamic_attribute_method_name(column_name), value, source_header)
|
41
|
-
end
|
23
|
+
# @return [Array] dynamic_column headers
|
24
|
+
def dynamic_column_source_headers
|
25
|
+
self.class.dynamic_column_source_headers source_header
|
26
|
+
end
|
42
27
|
|
43
|
-
|
28
|
+
# @return [Array] dynamic_column row data
|
29
|
+
def dynamic_column_source_cells
|
30
|
+
self.class.dynamic_column_source_cells source_row
|
44
31
|
end
|
45
32
|
|
46
33
|
class_methods do
|
47
|
-
|
48
|
-
#
|
49
|
-
# @param cells [Array] Array of values
|
50
|
-
# @param column_name [Symbol] Dynamic column name
|
51
|
-
def format_dynamic_column_cells(cells, column_name)
|
52
|
-
cells
|
53
|
-
end
|
54
|
-
# @return [Array] dynamic_column headers
|
55
|
-
def dynamic_source_headers(source_header)
|
34
|
+
def dynamic_column_source_headers(source_header)
|
56
35
|
dynamic_columns? ? source_header[columns.size..-1] : []
|
57
36
|
end
|
58
37
|
|
38
|
+
def dynamic_column_source_cells(source_row)
|
39
|
+
dynamic_columns? ? source_row[columns.size..-1] : []
|
40
|
+
end
|
41
|
+
|
59
42
|
protected
|
60
43
|
|
61
44
|
# See {Model#dynamic_column}
|
@@ -68,7 +51,7 @@ module CsvRowModel
|
|
68
51
|
# @param column_name [Symbol] the cell's column_name
|
69
52
|
def define_dynamic_attribute_method(column_name)
|
70
53
|
define_method(column_name) { original_attribute(column_name) }
|
71
|
-
|
54
|
+
DynamicColumnCell.define_process_cell(self, column_name)
|
72
55
|
end
|
73
56
|
end
|
74
57
|
end
|
@@ -4,11 +4,15 @@ module CsvRowModel
|
|
4
4
|
module Validations
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
|
-
include
|
7
|
+
include ActiveWarnings
|
8
8
|
include Validators::ValidateAttributes
|
9
9
|
|
10
10
|
included do
|
11
11
|
validate_attributes :csv
|
12
|
+
|
13
|
+
warnings do
|
14
|
+
validate { errors.add(:csv, "has header with #{csv.header.message}") unless csv.header.class == Array }
|
15
|
+
end
|
12
16
|
end
|
13
17
|
|
14
18
|
# @return [Boolean] returns true, if the file should abort reading
|
@@ -13,7 +13,7 @@ module CsvRowModel
|
|
13
13
|
# @return [Input] model class returned for importing
|
14
14
|
attr_reader :row_model_class
|
15
15
|
|
16
|
-
# Current index of the row model
|
16
|
+
# Current index of the row model (not the same as number of rows)
|
17
17
|
# @return [Integer] returns -1 = start of file, 0 to infinity = index of row_model, nil = end of file, no row_model
|
18
18
|
attr_reader :index
|
19
19
|
# @return [Input] the current row model set by {#next}
|
@@ -23,7 +23,7 @@ module CsvRowModel
|
|
23
23
|
# @return [Hash] context passed to the {Import}
|
24
24
|
attr_reader :context
|
25
25
|
|
26
|
-
delegate :
|
26
|
+
delegate :size, :end_of_file?, :line_number, to: :csv
|
27
27
|
|
28
28
|
# @param [String] file_path path of csv file
|
29
29
|
# @param [Import] row_model_class model class returned for importing
|
@@ -33,6 +33,11 @@ module CsvRowModel
|
|
33
33
|
reset
|
34
34
|
end
|
35
35
|
|
36
|
+
def header
|
37
|
+
h = csv.header
|
38
|
+
h.class == Array ? h : []
|
39
|
+
end
|
40
|
+
|
36
41
|
# Resets the file back to the top
|
37
42
|
def reset
|
38
43
|
csv.reset
|
@@ -47,8 +52,8 @@ module CsvRowModel
|
|
47
52
|
run_callbacks :next do
|
48
53
|
context = context.to_h.reverse_merge(self.context)
|
49
54
|
@previous_row_model = current_row_model
|
50
|
-
@current_row_model = row_model_class.next(csv, header, context, previous_row_model)
|
51
55
|
@index += 1
|
56
|
+
@current_row_model = row_model_class.next(self, context)
|
52
57
|
@current_row_model = @index = nil if end_of_file?
|
53
58
|
end
|
54
59
|
|
@@ -22,15 +22,16 @@ module CsvRowModel
|
|
22
22
|
# @return [Array] header_matchs matchers for the row model
|
23
23
|
def header_matchers(context)
|
24
24
|
@header_matchers ||= begin
|
25
|
-
columns.map do |name, options|
|
26
|
-
if formatted_header = self.format_header(name, context)
|
25
|
+
columns.map.with_index do |(name, options), index|
|
26
|
+
if formatted_header = self.format_header(name, index, context)
|
27
27
|
Regexp.new("^#{formatted_header}$", Regexp::IGNORECASE)
|
28
28
|
end
|
29
29
|
end.compact
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
-
def next(
|
33
|
+
def next(file, context={})
|
34
|
+
csv = file.csv
|
34
35
|
return csv.read_row unless csv.next_row
|
35
36
|
|
36
37
|
source_row = Array.new(header_matchers(context).size)
|
@@ -48,7 +49,7 @@ module CsvRowModel
|
|
48
49
|
end
|
49
50
|
end
|
50
51
|
|
51
|
-
new(source_row, source_header:
|
52
|
+
new(source_row, source_header: csv.header, context: context, previous: file.previous_row_model)
|
52
53
|
end
|
53
54
|
end
|
54
55
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module CsvRowModel
|
2
|
+
module Import
|
3
|
+
class Representation
|
4
|
+
include Concerns::CheckOptions
|
5
|
+
VALID_OPTIONS = %i[memoize empty_value dependencies].freeze
|
6
|
+
|
7
|
+
attr_reader :name, :options, :row_model
|
8
|
+
|
9
|
+
def initialize(name, options, row_model)
|
10
|
+
@name = name
|
11
|
+
@options = options
|
12
|
+
@row_model = row_model
|
13
|
+
end
|
14
|
+
|
15
|
+
def value
|
16
|
+
memoize? ? memoized_value : dependencies_value
|
17
|
+
end
|
18
|
+
|
19
|
+
def memoized_value
|
20
|
+
@memoized_value ||= dependencies_value
|
21
|
+
end
|
22
|
+
|
23
|
+
def memoize?
|
24
|
+
options.has_key?(:memoize) ? !!options[:memoize] : true
|
25
|
+
end
|
26
|
+
|
27
|
+
def dependencies_value
|
28
|
+
valid_dependencies? ? lambda_value : empty_value
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Boolean] if the dependencies are valid
|
32
|
+
def valid_dependencies?
|
33
|
+
dependencies.each { |attribute_name| return false if row_model.public_send(attribute_name).blank? }
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
def empty_value
|
38
|
+
options[:empty_value]
|
39
|
+
end
|
40
|
+
|
41
|
+
def lambda_value
|
42
|
+
row_model.public_send(self.class.lambda_name(name))
|
43
|
+
end
|
44
|
+
|
45
|
+
def dependencies
|
46
|
+
Array(options[:dependencies])
|
47
|
+
end
|
48
|
+
|
49
|
+
class << self
|
50
|
+
def lambda_name(representation_name)
|
51
|
+
:"__#{representation_name}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def define_lambda_method(row_model_class, representation_name, &block)
|
55
|
+
row_model_class.send(:define_method, lambda_name(representation_name), &block)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'csv_row_model/import/representation'
|
2
|
+
|
3
|
+
module CsvRowModel
|
4
|
+
module Import
|
5
|
+
module Represents
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
inherited_class_hash :representations
|
10
|
+
end
|
11
|
+
|
12
|
+
def representation_objects
|
13
|
+
@representation_objects ||= array_to_block_hash(self.class.representation_names) do |representation_name|
|
14
|
+
Representation.new(representation_name, self.class.representations[representation_name], self)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def representation_value(representation_name)
|
19
|
+
representation_objects[representation_name].try(:value)
|
20
|
+
end
|
21
|
+
|
22
|
+
def representations
|
23
|
+
attributes_from_method_names(self.class.representation_names)
|
24
|
+
end
|
25
|
+
|
26
|
+
def all_attributes
|
27
|
+
attributes.merge!(representations)
|
28
|
+
end
|
29
|
+
|
30
|
+
def valid?(*args)
|
31
|
+
super
|
32
|
+
filter_errors
|
33
|
+
errors.empty?
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
# remove each dependent attribute from errors if it's representation dependencies are in the errors
|
39
|
+
def filter_errors
|
40
|
+
self.class.representation_names.each do |representation_name|
|
41
|
+
next unless errors.messages.slice(*representation_objects[representation_name].dependencies).present?
|
42
|
+
errors.delete representation_name
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class_methods do
|
47
|
+
# @return [Array<Symbol>] names of all representations
|
48
|
+
def representation_names
|
49
|
+
representations.keys
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
# Defines a representation for singular resources
|
55
|
+
#
|
56
|
+
# @param [Symbol] representation_name name of representation to add
|
57
|
+
# @param [Proc] block to define the attribute
|
58
|
+
# @param options [Hash]
|
59
|
+
# @option options [Hash] :memoize whether to memoize the attribute (default: true)
|
60
|
+
# @option options [Hash] :dependencies the dependencies with other attributes/representations (default: [])
|
61
|
+
def represents_one(*args, &block)
|
62
|
+
define_representation_method(*args, &block)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Defines a representation for multiple resources
|
66
|
+
#
|
67
|
+
# @param [Symbol] representation_name name of representation to add
|
68
|
+
# @param [Proc] block to define the attribute
|
69
|
+
# @param options [Hash]
|
70
|
+
# @option options [Hash] :memoize whether to memoize the attribute (default: true)
|
71
|
+
# @option options [Hash] :dependencies the dependencies with other attributes/representations (default: [])
|
72
|
+
def represents_many(representation_name, options={}, &block)
|
73
|
+
define_representation_method(representation_name, options.merge(empty_value: []), &block)
|
74
|
+
end
|
75
|
+
|
76
|
+
def define_representation_method(representation_name, options={}, &block)
|
77
|
+
Representation.check_options(options)
|
78
|
+
merge_representations(representation_name.to_sym => options)
|
79
|
+
define_method(representation_name) { representation_value(representation_name) }
|
80
|
+
Representation.define_lambda_method(self, representation_name, &block)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/csv_row_model/import.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
require 'csv_row_model/import/base'
|
2
|
+
require 'csv_row_model/import/csv_string_model'
|
2
3
|
require 'csv_row_model/import/attributes'
|
3
4
|
require 'csv_row_model/import/dynamic_columns'
|
4
|
-
require 'csv_row_model/
|
5
|
+
require 'csv_row_model/import/represents'
|
5
6
|
|
6
7
|
module CsvRowModel
|
7
8
|
# Include this to with {Model} to have a RowModel for importing csvs.
|
@@ -11,9 +12,9 @@ module CsvRowModel
|
|
11
12
|
include Concerns::Inspect
|
12
13
|
|
13
14
|
include Base
|
15
|
+
include CsvStringModel
|
14
16
|
include Attributes
|
15
17
|
include DynamicColumns
|
16
|
-
|
17
|
-
include Model::Comparison # can't be added on Model module because Model does not have attributes implemented
|
18
|
+
include Represents
|
18
19
|
end
|
19
20
|
end
|
@@ -4,6 +4,8 @@ module CsvRowModel
|
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
6
|
included do
|
7
|
+
attr_reader :context
|
8
|
+
|
7
9
|
# @return [Model] return the parent, if this instance is a child
|
8
10
|
attr_reader :parent
|
9
11
|
|
@@ -13,12 +15,13 @@ module CsvRowModel
|
|
13
15
|
validate_attributes :parent
|
14
16
|
end
|
15
17
|
|
16
|
-
# @param [NilClass] source not used here, see {Input}
|
17
18
|
# @param [Hash] options
|
18
19
|
# @option options [String] :parent if the instance is a child, pass the parent
|
19
|
-
|
20
|
+
# @option options [Hash] :context extra data you want to work with the model
|
21
|
+
def initialize(options={})
|
20
22
|
@initialized_at = DateTime.now
|
21
23
|
@parent = options[:parent]
|
24
|
+
@context = OpenStruct.new(options[:context] || {})
|
22
25
|
end
|
23
26
|
|
24
27
|
# Safe to override.
|
@@ -34,19 +37,6 @@ module CsvRowModel
|
|
34
37
|
def abort?
|
35
38
|
false
|
36
39
|
end
|
37
|
-
|
38
|
-
class_methods do
|
39
|
-
# @return [Class] the Class with validations of the csv_string_model
|
40
|
-
def csv_string_model_class
|
41
|
-
@csv_string_model_class ||= inherited_custom_class(:csv_string_model_class, CsvStringModel)
|
42
|
-
end
|
43
|
-
|
44
|
-
protected
|
45
|
-
# Called to add validations to the csv_string_model_class
|
46
|
-
def csv_string_model(&block)
|
47
|
-
csv_string_model_class.class_eval(&block)
|
48
|
-
end
|
49
|
-
end
|
50
40
|
end
|
51
41
|
end
|
52
42
|
end
|
@@ -15,6 +15,7 @@ module CsvRowModel
|
|
15
15
|
#
|
16
16
|
# @return [Model] return the child if it is valid, otherwise returns nil
|
17
17
|
def append_child(source, options={})
|
18
|
+
return nil unless source
|
18
19
|
self.class.has_many_relationships.each do |relation_name, child_class|
|
19
20
|
child_row_model = child_class.new(source, options.reverse_merge(parent: self))
|
20
21
|
if child_row_model.valid?
|
@@ -58,7 +59,7 @@ module CsvRowModel
|
|
58
59
|
#
|
59
60
|
# equal to: @relation_name ||= []
|
60
61
|
#
|
61
|
-
variable_name = "@#{relation_name}"
|
62
|
+
variable_name = :"@#{relation_name}"
|
62
63
|
instance_variable_get(variable_name) || instance_variable_set(variable_name, [])
|
63
64
|
end
|
64
65
|
end
|
@@ -8,7 +8,7 @@ module CsvRowModel
|
|
8
8
|
|
9
9
|
# @return [Hash] a map of `column_name => public_send(column_name)`
|
10
10
|
def attributes
|
11
|
-
|
11
|
+
attributes_from_method_names self.class.column_names
|
12
12
|
end
|
13
13
|
|
14
14
|
def to_json
|
@@ -21,15 +21,12 @@ module CsvRowModel
|
|
21
21
|
|
22
22
|
protected
|
23
23
|
|
24
|
-
def
|
25
|
-
array_to_block_hash(column_names) { |column_name|
|
24
|
+
def attributes_from_method_names(column_names)
|
25
|
+
array_to_block_hash(column_names) { |column_name| try(column_name) }
|
26
26
|
end
|
27
27
|
|
28
28
|
def array_to_block_hash(array, &block)
|
29
|
-
array
|
30
|
-
.zip(
|
31
|
-
array.map { |column_name| block.call(column_name) }
|
32
|
-
).to_h
|
29
|
+
array.zip(array.map { |column_name| block.call(column_name) }).to_h
|
33
30
|
end
|
34
31
|
|
35
32
|
class_methods do
|
@@ -38,12 +35,6 @@ module CsvRowModel
|
|
38
35
|
columns.keys
|
39
36
|
end
|
40
37
|
|
41
|
-
# @param [Symbol] column_name name of column to find option
|
42
|
-
# @return [Hash] options for the column_name
|
43
|
-
def options(column_name)
|
44
|
-
columns[column_name]
|
45
|
-
end
|
46
|
-
|
47
38
|
# @param [Symbol] column_name name of column to find index
|
48
39
|
# @return [Integer] index of the column_name
|
49
40
|
def index(column_name)
|
@@ -59,16 +50,28 @@ module CsvRowModel
|
|
59
50
|
# @param [Hash, OpenStruct] context name of column to check
|
60
51
|
# @return [Array] column headers for the row model
|
61
52
|
def headers(context={})
|
62
|
-
|
53
|
+
context = OpenStruct.new(context)
|
54
|
+
columns.map.with_index do |(column_name, options), index|
|
55
|
+
options[:header] || format_header(column_name, index, context)
|
56
|
+
end
|
63
57
|
end
|
64
58
|
|
65
59
|
# Safe to override
|
66
60
|
#
|
67
61
|
# @return [String] formatted header
|
68
|
-
def format_header(column_name, context
|
62
|
+
def format_header(column_name, column_index, context)
|
69
63
|
column_name
|
70
64
|
end
|
71
65
|
|
66
|
+
# Safe to override. Method applied to each cell by default
|
67
|
+
#
|
68
|
+
# @param cell [String] the cell's string
|
69
|
+
# @param column_name [Symbol] the cell's column_name
|
70
|
+
# @param column_index [Integer] the column_name's index
|
71
|
+
def format_cell(cell, column_name, column_index, context)
|
72
|
+
cell
|
73
|
+
end
|
74
|
+
|
72
75
|
protected
|
73
76
|
|
74
77
|
VALID_OPTIONS_KEYS = %i[type parse validate_type default header header_matchs].freeze
|
@@ -97,7 +100,7 @@ module CsvRowModel
|
|
97
100
|
|
98
101
|
def merge_options(column_name, options={})
|
99
102
|
column_name = column_name.to_sym
|
100
|
-
column(column_name,
|
103
|
+
column(column_name, options)
|
101
104
|
end
|
102
105
|
end
|
103
106
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module CsvRowModel
|
2
|
+
module Model
|
3
|
+
class DynamicColumnCell
|
4
|
+
attr_reader :column_name, :row_model
|
5
|
+
|
6
|
+
def initialize(column_name, row_model)
|
7
|
+
@column_name = column_name
|
8
|
+
@row_model = row_model
|
9
|
+
end
|
10
|
+
|
11
|
+
def value
|
12
|
+
@value ||= row_model.class.format_dynamic_column_cells(unformatted_value, column_name, dynamic_column_index, row_model.context)
|
13
|
+
end
|
14
|
+
|
15
|
+
def dynamic_column_index
|
16
|
+
@dynamic_column_index ||= row_model.class.dynamic_column_index(column_name)
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def process_cell_method_name
|
22
|
+
self.class.process_cell_method_name(column_name)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Calls the process_cell to return the value of a Cell given the args
|
26
|
+
def call_process_cell(*args)
|
27
|
+
row_model.public_send(process_cell_method_name, *args)
|
28
|
+
end
|
29
|
+
|
30
|
+
class << self
|
31
|
+
def process_cell_method_name(column_name)
|
32
|
+
column_name.to_s.singularize.to_sym
|
33
|
+
end
|
34
|
+
|
35
|
+
# define a method to process each cell of the attribute method
|
36
|
+
# process_cell = one cell
|
37
|
+
# attribute_method = many cells = [process_cell(), process_cell()...]
|
38
|
+
def define_process_cell(row_model_class, column_name, &block)
|
39
|
+
row_model_class.send(:define_method, process_cell_method_name(column_name), &block)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'csv_row_model/model/dynamic_column_cell'
|
2
|
+
|
1
3
|
module CsvRowModel
|
2
4
|
module Model
|
3
5
|
module DynamicColumns
|
@@ -8,7 +10,7 @@ module CsvRowModel
|
|
8
10
|
|
9
11
|
# See Model::Columns#attributes
|
10
12
|
def attributes
|
11
|
-
super.merge(
|
13
|
+
super.merge!(attributes_from_method_names(self.class.dynamic_column_names))
|
12
14
|
end
|
13
15
|
|
14
16
|
class_methods do
|
@@ -20,30 +22,44 @@ module CsvRowModel
|
|
20
22
|
dynamic_columns.keys.include?(column_name)
|
21
23
|
end
|
22
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
|
31
|
+
end
|
32
|
+
|
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
|
38
|
+
end
|
39
|
+
|
23
40
|
# See Model::Columns::headers
|
24
41
|
def headers(context={})
|
25
42
|
super + dynamic_column_headers(context)
|
26
43
|
end
|
27
44
|
|
28
45
|
def dynamic_column_headers(context={})
|
29
|
-
|
30
|
-
|
31
|
-
|
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) }
|
32
52
|
instance_exec(header_model, &header_proc)
|
33
53
|
end
|
34
54
|
end.flatten
|
35
55
|
end
|
36
56
|
|
37
57
|
# @return [Integer] index of dynamic_column of all columns
|
38
|
-
def
|
58
|
+
def dynamic_column_index(column_name)
|
39
59
|
offset = dynamic_column_names.index(column_name)
|
40
60
|
offset ? columns.size + offset : nil
|
41
61
|
end
|
42
62
|
|
43
|
-
def dynamic_column_options(column_name)
|
44
|
-
dynamic_columns[column_name]
|
45
|
-
end
|
46
|
-
|
47
63
|
# @return [Array<Symbol>] column names for the row model
|
48
64
|
def dynamic_column_names
|
49
65
|
dynamic_columns.keys
|
@@ -59,9 +75,8 @@ module CsvRowModel
|
|
59
75
|
|
60
76
|
# define a dynamic_column, must be after all normal columns
|
61
77
|
#
|
62
|
-
# options to be implemented later
|
63
|
-
#
|
64
78
|
# @param column_name [Symbol] column_name
|
79
|
+
# @option options [String] :header human friendly string of the column name, by default format_header(column_name)
|
65
80
|
def dynamic_column(column_name, options={})
|
66
81
|
extra_keys = options.keys - VALID_OPTIONS_KEYS
|
67
82
|
raise ArgumentError.new("invalid options #{extra_keys}") unless extra_keys.empty?
|
data/lib/csv_row_model/model.rb
CHANGED
@@ -1,16 +1,15 @@
|
|
1
|
-
require 'csv_row_model/model/csv_string_model'
|
2
|
-
|
3
1
|
require 'csv_row_model/model/base'
|
4
2
|
require 'csv_row_model/model/columns'
|
5
3
|
require 'csv_row_model/model/children'
|
6
4
|
require 'csv_row_model/model/dynamic_columns'
|
5
|
+
require 'csv_row_model/model/comparison'
|
7
6
|
|
8
7
|
module CsvRowModel
|
9
8
|
# Base module for representing a RowModel---a model that represents row(s).
|
10
9
|
module Model
|
11
10
|
extend ActiveSupport::Concern
|
12
11
|
|
13
|
-
include
|
12
|
+
include InheritedClassVar
|
14
13
|
|
15
14
|
include ActiveWarnings
|
16
15
|
include Validators::ValidateAttributes
|
@@ -20,5 +19,7 @@ module CsvRowModel
|
|
20
19
|
include Columns
|
21
20
|
include Children
|
22
21
|
include DynamicColumns
|
22
|
+
|
23
|
+
include Comparison
|
23
24
|
end
|
24
25
|
end
|