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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +162 -162
  3. data/csv_row_model.gemspec +2 -1
  4. data/lib/csv_row_model/concerns/{invalid_options.rb → check_options.rb} +4 -6
  5. data/lib/csv_row_model/export/attributes.rb +11 -20
  6. data/lib/csv_row_model/export/base.rb +3 -3
  7. data/lib/csv_row_model/export/cell.rb +24 -0
  8. data/lib/csv_row_model/export/dynamic_column_cell.rb +29 -0
  9. data/lib/csv_row_model/export/dynamic_columns.rb +11 -13
  10. data/lib/csv_row_model/export/file.rb +7 -7
  11. data/lib/csv_row_model/export/file_model.rb +2 -2
  12. data/lib/csv_row_model/export.rb +0 -3
  13. data/lib/csv_row_model/import/attributes.rb +18 -81
  14. data/lib/csv_row_model/import/base.rb +26 -69
  15. data/lib/csv_row_model/import/cell.rb +77 -0
  16. data/lib/csv_row_model/import/csv.rb +23 -60
  17. data/lib/csv_row_model/import/csv_string_model.rb +65 -0
  18. data/lib/csv_row_model/import/dynamic_column_cell.rb +37 -0
  19. data/lib/csv_row_model/import/dynamic_columns.rb +20 -37
  20. data/lib/csv_row_model/import/file/validations.rb +5 -1
  21. data/lib/csv_row_model/import/file.rb +8 -3
  22. data/lib/csv_row_model/import/file_model.rb +5 -4
  23. data/lib/csv_row_model/import/representation.rb +60 -0
  24. data/lib/csv_row_model/import/represents.rb +85 -0
  25. data/lib/csv_row_model/import.rb +4 -3
  26. data/lib/csv_row_model/model/base.rb +5 -15
  27. data/lib/csv_row_model/model/children.rb +2 -1
  28. data/lib/csv_row_model/model/columns.rb +19 -16
  29. data/lib/csv_row_model/model/comparison.rb +1 -1
  30. data/lib/csv_row_model/model/dynamic_column_cell.rb +44 -0
  31. data/lib/csv_row_model/model/dynamic_columns.rb +26 -11
  32. data/lib/csv_row_model/model.rb +4 -3
  33. data/lib/csv_row_model/version.rb +1 -1
  34. data/lib/csv_row_model.rb +3 -1
  35. metadata +29 -10
  36. data/lib/csv_row_model/concerns/inherited_class_var.rb +0 -121
  37. data/lib/csv_row_model/import/presenter.rb +0 -153
  38. 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
- # @return [Array] dynamic_column headers
11
- def dynamic_source_headers
12
- self.class.dynamic_source_headers source_header
13
- end
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 [Object] the column's attribute before override
28
- def original_attribute(column_name)
29
- return super if self.class.column_names.include? column_name
30
- return unless self.class.dynamic_column_names.include? column_name
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
- @original_attributes[column_name] = self.class.format_dynamic_column_cells(values, column_name)
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
- # Safe to override. Method applied to each dynamic_column attribute
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
- define_method(singular_dynamic_attribute_method_name(column_name)) { |value, source_header| value }
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 ActiveModel::Validations
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 :header, :size, :skipped_rows, :end_of_file?, to: :csv
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(csv, source_header, context={}, previous=nil)
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: source_header, context: context, previous: previous)
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
@@ -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/model/comparison'
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
- def initialize(source=nil, options={})
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
- attributes_from_column_names self.class.column_names
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 attributes_from_column_names(column_names)
25
- array_to_block_hash(column_names) { |column_name| public_send(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
- columns.map { |name, options| options[:header] || format_header(name, context) }
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, (options(column_name) || {}).merge(options))
103
+ column(column_name, options)
101
104
  end
102
105
  end
103
106
  end
@@ -4,7 +4,7 @@ module CsvRowModel
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  def eql?(other)
7
- other.attributes == attributes
7
+ other.try(:attributes) == attributes
8
8
  end
9
9
 
10
10
  def hash
@@ -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(attributes_from_column_names(self.class.dynamic_column_names))
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
- dynamic_column_names.map do |column_name|
30
- Array(OpenStruct.new(context).public_send(column_name)).each do |header_model|
31
- header_proc = dynamic_column_options(column_name)[:header] || ->(header_model) { header_model }
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 dynamic_index(column_name)
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?
@@ -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 Concerns::InheritedClassVar
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