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.
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