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