csv_row_model 1.0.0.beta1 → 1.0.0.beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -1
  3. data/README.md +22 -9
  4. data/lib/csv_row_model/concerns/attributes_base.rb +86 -0
  5. data/lib/csv_row_model/concerns/check_options.rb +7 -9
  6. data/lib/csv_row_model/concerns/dynamic_columns_base.rb +47 -0
  7. data/lib/csv_row_model/concerns/export/attributes.rb +26 -0
  8. data/lib/csv_row_model/{export → concerns/export}/base.rb +1 -1
  9. data/lib/csv_row_model/concerns/export/dynamic_columns.rb +33 -0
  10. data/lib/csv_row_model/concerns/hidden_module.rb +15 -0
  11. data/lib/csv_row_model/concerns/import/attributes.rb +52 -0
  12. data/lib/csv_row_model/{import → concerns/import}/base.rb +10 -18
  13. data/lib/csv_row_model/{import → concerns/import}/csv_string_model.rb +2 -3
  14. data/lib/csv_row_model/concerns/import/dynamic_columns.rb +50 -0
  15. data/lib/csv_row_model/{import → concerns/import}/represents.rb +2 -2
  16. data/lib/csv_row_model/concerns/inspect.rb +5 -6
  17. data/lib/csv_row_model/{model/columns.rb → concerns/model/attributes.rb} +8 -31
  18. data/lib/csv_row_model/{model → concerns/model}/base.rb +1 -11
  19. data/lib/csv_row_model/{model → concerns/model}/children.rb +7 -1
  20. data/lib/csv_row_model/{model → concerns/model}/dynamic_columns.rb +23 -42
  21. data/lib/csv_row_model/export/file_model.rb +2 -15
  22. data/lib/csv_row_model/export.rb +3 -3
  23. data/lib/csv_row_model/import/file.rb +62 -22
  24. data/lib/csv_row_model/import/file_model.rb +3 -4
  25. data/lib/csv_row_model/import.rb +5 -9
  26. data/lib/csv_row_model/internal/attribute_base.rb +22 -0
  27. data/lib/csv_row_model/internal/concerns/column_shared.rb +21 -0
  28. data/lib/csv_row_model/internal/concerns/dynamic_column_shared.rb +28 -0
  29. data/lib/csv_row_model/internal/dynamic_column_attribute_base.rb +42 -0
  30. data/lib/csv_row_model/internal/export/attribute.rb +15 -0
  31. data/lib/csv_row_model/internal/export/dynamic_column_attribute.rb +21 -0
  32. data/lib/csv_row_model/{import/cell.rb → internal/import/attribute.rb} +4 -13
  33. data/lib/csv_row_model/{import → internal/import}/csv.rb +11 -13
  34. data/lib/csv_row_model/internal/import/dynamic_column_attribute.rb +33 -0
  35. data/lib/csv_row_model/{import → internal/import}/representation.rb +2 -2
  36. data/lib/csv_row_model/internal/model/dynamic_column_header.rb +22 -0
  37. data/lib/csv_row_model/internal/model/header.rb +25 -0
  38. data/lib/csv_row_model/model/file_model.rb +0 -1
  39. data/lib/csv_row_model/model.rb +5 -12
  40. data/lib/csv_row_model/validators/{boolean_format.rb → boolean_format_validator.rb} +1 -1
  41. data/lib/csv_row_model/validators/date_format_validator.rb +7 -0
  42. data/lib/csv_row_model/validators/date_time_format_validator.rb +7 -0
  43. data/lib/csv_row_model/validators/{default_change.rb → default_change_validator.rb} +0 -0
  44. data/lib/csv_row_model/validators/float_format_validator.rb +7 -0
  45. data/lib/csv_row_model/validators/integer_format_validator.rb +15 -0
  46. data/lib/csv_row_model/version.rb +1 -1
  47. data/lib/csv_row_model.rb +16 -37
  48. metadata +35 -33
  49. data/lib/csv_row_model/engine.rb +0 -5
  50. data/lib/csv_row_model/export/attributes.rb +0 -46
  51. data/lib/csv_row_model/export/cell.rb +0 -24
  52. data/lib/csv_row_model/export/dynamic_column_cell.rb +0 -29
  53. data/lib/csv_row_model/export/dynamic_columns.rb +0 -46
  54. data/lib/csv_row_model/import/attributes.rb +0 -66
  55. data/lib/csv_row_model/import/dynamic_column_cell.rb +0 -37
  56. data/lib/csv_row_model/import/dynamic_columns.rb +0 -59
  57. data/lib/csv_row_model/import/file/callbacks.rb +0 -17
  58. data/lib/csv_row_model/import/file/validations.rb +0 -43
  59. data/lib/csv_row_model/model/comparison.rb +0 -15
  60. data/lib/csv_row_model/model/dynamic_column_cell.rb +0 -44
  61. data/lib/csv_row_model/validators/date_format.rb +0 -9
  62. data/lib/csv_row_model/validators/date_time_format.rb +0 -9
  63. data/lib/csv_row_model/validators/float_format.rb +0 -9
  64. data/lib/csv_row_model/validators/integer_format.rb +0 -9
  65. data/lib/csv_row_model/validators/number_validator.rb +0 -16
  66. data/lib/csv_row_model/validators/validate_attributes.rb +0 -27
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 620b2a84648e1723419994992518ecd9e77609d3
4
- data.tar.gz: 5b331bc69fb075b9ca84bf53db592c0e6db15dde
3
+ metadata.gz: b9eb25b87297dafc35c155017125d709b0606449
4
+ data.tar.gz: ba28f582a1319e2217350a6ad910ef7fa427c386
5
5
  SHA512:
6
- metadata.gz: 5e10d1f2a7fd7115e632fadee715a852d9aa05bf5f1d69d04c79fc1503d598118bb75dfbb1707af4cf5afa6692c2338bd1d1e93125b39d1175445f34197dacad
7
- data.tar.gz: 78994103e5eab1c176f5a2e9c7306073071a7da9adfc94344393e16cdd32fff6023c3f1e8e018b2f6ed8178f2720bbfc7149abba232a3b4414393ae6bc6bb8cf
6
+ metadata.gz: 52392629db0ded13d4dc3ede022c1f98520d21aa59ee10dca1f98e9011118597b04b424fd1354dd7d79470b8f0c809292494bf4e447611c321cb69955e804c44
7
+ data.tar.gz: 6b8d3f701bc4005dc8508cc64ee601255c50d587b81fa80ccc0579a57db88d3eb18589e95b65802375ffedaf80e940f22b78625028d6ebda1b649007aa9f693c
data/.rspec CHANGED
@@ -1,2 +1,3 @@
1
1
  --format documentation
2
- --color
2
+ --color
3
+ --order rand
data/README.md CHANGED
@@ -50,10 +50,10 @@ end
50
50
  import_file = CsvRowModel::Import::File.new(file_path, ProjectImportRowModel)
51
51
  row_model = import_file.next
52
52
 
53
- row_model.header # => ["id", "name"]
53
+ row_model.headers # => ["id", "name"]
54
54
 
55
55
  row_model.source_row # => ["1", "Some Project Name"]
56
- row_model.mapped_row # => { id: "1", name: "Some Project Name" }, this is `source_row` mapped to `column_names`
56
+ row_model.source_attributes # => { id: "1", name: "Some Project Name" }, this is `source_row` mapped to `column_names`
57
57
  row_model.attributes # => { id: "1", name: "Some Project Name" }, this is final attribute values mapped to `column_names`
58
58
 
59
59
  row_model.id # => 1
@@ -123,7 +123,7 @@ To generate a attribute value, the following pseudocode is executed:
123
123
  ```ruby
124
124
  def original_attribute(column_name)
125
125
  # 1. Get the raw CSV string value for the column
126
- value = mapped_row[column_name]
126
+ value = source_attributes[column_name]
127
127
 
128
128
  # 2. Clean or format each cell
129
129
  value = self.class.format_cell(cell, column_name, column_index, context)
@@ -280,6 +280,18 @@ import_file.each { |project_import_model| puts "does not yield here" }
280
280
  import_file.next # does not skip or abort
281
281
  ```
282
282
 
283
+ ### File Validations
284
+ You can also have file validations, while will make the entire import process abort. Currently, there is one provided validation.
285
+
286
+ ```ruby
287
+ class ImportFile < CsvRowModel::Import::File
288
+ validate :headers_invalid_row # checks if header is valid CSV syntax
289
+ validate :headers_count # calls #headers_invalid_row, then check the count. will ignore tailing empty headers
290
+ end
291
+ ```
292
+
293
+ Can't be used for [Dynamic Columns](#dynamic-columns) or [File Model](#file-model)s.
294
+
283
295
  ### Import Callbacks
284
296
  `CsvRowModel::Import::File` can be subclassed to access
285
297
  [`ActiveModel::Callbacks`](http://api.rubyonrails.org/classes/ActiveModel/Callbacks.html).
@@ -350,7 +362,7 @@ row_model.valid? # => false
350
362
  row_model.errors.full_messages # => ["Id must be greater than 0"]
351
363
  ```
352
364
 
353
- Note that `CsvStringModel` validations are calculated after [Format Cell](#format-cell).
365
+ Note that `CsvStringModel` validations are calculated after [Format Attribute](#format-cell).
354
366
 
355
367
  ### Represents
356
368
  A CSV is often a representation of database model(s), much like how JSON parameters represents models in requests.
@@ -429,7 +441,7 @@ row_model = import_file.next
429
441
  row_model.projects # => [<ProjectImportRowModel>, ...]
430
442
  ```
431
443
 
432
- ## Dynamic columns
444
+ ## Dynamic Columns
433
445
  Dynamic columns are columns that can expand to many columns. Currently, we can only one dynamic column after all other standard columns.
434
446
  The following:
435
447
 
@@ -440,7 +452,7 @@ class DynamicColumnModel
440
452
  column :first_name
441
453
  column :last_name
442
454
  # header is optional, below is the default_implementation
443
- dynamic_column :skills, header: ->(skill_name) { skill_name }
455
+ dynamic_column :skills, header: ->(skill_name) { skill_name }, header_models_context_key: :skills
444
456
  end
445
457
  ```
446
458
 
@@ -453,7 +465,7 @@ represents this table:
453
465
  | Mike | Jackson | Yes | Yes |
454
466
 
455
467
 
456
- The `format_dynamic_column_header(header_model, column_name, dynamic_column_index, index_of_column, context)` can
468
+ The `format_dynamic_column_header(header_model, column_name, dynamic_column_index, context)` can
457
469
  be used to defined like `format_header`. Defined in both import and export due to headers being used for both.
458
470
 
459
471
  ### Export
@@ -470,7 +482,8 @@ class DynamicColumnExportModel < DynamicColumnModel
470
482
  end
471
483
  end
472
484
 
473
- # the `skills` context is mapped to generate an array
485
+ # `skills` in the context is used as the header, which is used in `def skill(skill_name)` above
486
+ # to change this context key, use the :header_models_context_key option
474
487
  export_file = CsvRowModel::Export::File.new(DynamicColumnExportModel, { skills: Skill.all })
475
488
  export_file.generate do |csv|
476
489
  User.all.each { |user| csv << user }
@@ -504,7 +517,7 @@ row_model.attributes # => { first_name: "John", last_name: "Doe", skills: ['No',
504
517
  row_model.skills # => ['No', 'Yes']
505
518
  ```
506
519
 
507
- ## File Model (Mapping)
520
+ ## File Model
508
521
 
509
522
  If you have to deal with a mapping on a csv you can use FileModel, isn't complete a this time and many cases isn't covered but can be helpful
510
523
 
@@ -0,0 +1,86 @@
1
+ require 'csv_row_model/concerns/model/attributes'
2
+ require 'csv_row_model/concerns/hidden_module'
3
+
4
+ # Shared between Import and Export, see test fixture for basic setup
5
+ module CsvRowModel
6
+ module AttributesBase
7
+ extend ActiveSupport::Concern
8
+ include Model::Attributes
9
+ include HiddenModule
10
+
11
+ # @return [Hash] a map of `column_name => public_send(column_name)`
12
+ def attributes
13
+ attributes_from_method_names self.class.column_names
14
+ end
15
+
16
+ # Export:
17
+ # source_value - row_model.column1 --> source_model.column1
18
+ # formatted_value - format_cell(source_value)
19
+ # vale - formatted_value
20
+ #
21
+ # Import:
22
+ # source_value - form source_row
23
+ # formatted_value - format_cell(source_value)
24
+ # value - calculated_value from a bunch of stuff
25
+ ATTRIBUTE_METHODS = {
26
+ original_attributes: :value, # a map of `column_name => original_attribute(column_name)`
27
+ formatted_attributes: :formatted_value, # a map of `column_name => format_cell(column_name, ...)`
28
+ source_attributes: :source_value # a map of `column_name => source (source_row[index_of_column_name] or row_model.public_send(column_name)) `
29
+ }.freeze
30
+ ATTRIBUTE_METHODS.each do |method_name, attribute_method|
31
+ define_method(method_name) do
32
+ column_names_to_attribute_value(self.class.column_names, attribute_method)
33
+ end
34
+ end
35
+
36
+ # @return [Object] the column's attribute (the csv_row_model default value to be used for import/export)
37
+ def original_attribute(column_name)
38
+ attribute_objects[column_name].try(:value)
39
+ end
40
+
41
+ def to_json
42
+ attributes.to_json
43
+ end
44
+
45
+ def eql?(other)
46
+ other.try(:attributes) == attributes
47
+ end
48
+
49
+ def hash
50
+ attributes.hash
51
+ end
52
+
53
+ protected
54
+ def attributes_from_method_names(column_names)
55
+ array_to_block_hash(column_names) { |column_name| try(column_name) }
56
+ end
57
+
58
+ def column_names_to_attribute_value(column_names, attribute_method)
59
+ array_to_block_hash(column_names) { |column_name| attribute_objects[column_name].public_send(attribute_method)}
60
+ end
61
+
62
+ def array_to_block_hash(array, &block)
63
+ array.zip(array.map(&block)).to_h
64
+ end
65
+
66
+ class_methods do
67
+ protected
68
+ # See {Model#column}
69
+ def column(column_name, options={})
70
+ super
71
+ define_attribute_method(column_name)
72
+ end
73
+
74
+ # Define default attribute method for a column
75
+ # @param column_name [Symbol] the cell's column_name
76
+ def define_attribute_method(column_name, &block)
77
+ return if method_defined? column_name
78
+ define_proxy_method(column_name, &block)
79
+ end
80
+
81
+ def ensure_attribute_method
82
+ self.column_names.each { |*args| define_attribute_method(*args) }
83
+ end
84
+ end
85
+ end
86
+ end
@@ -1,14 +1,12 @@
1
1
  module CsvRowModel
2
- module Concerns
3
- module CheckOptions
4
- extend ActiveSupport::Concern
2
+ module CheckOptions
3
+ extend ActiveSupport::Concern
5
4
 
6
- class_methods do
7
- def check_options(options)
8
- invalid_options = options.keys - self::VALID_OPTIONS
9
- raise ArgumentError.new("Invalid option(s): #{invalid_options}") if invalid_options.present?
10
- true
11
- end
5
+ class_methods do
6
+ def check_options(options)
7
+ invalid_options = options.keys - self::VALID_OPTIONS
8
+ raise ArgumentError.new("Invalid option(s): #{invalid_options}") if invalid_options.present?
9
+ true
12
10
  end
13
11
  end
14
12
  end
@@ -0,0 +1,47 @@
1
+ require 'csv_row_model/concerns/model/dynamic_columns'
2
+
3
+ # Shared between Import and Export, see test fixture for basic setup
4
+ module CsvRowModel
5
+ module DynamicColumnsBase
6
+ extend ActiveSupport::Concern
7
+ include Model::DynamicColumns
8
+
9
+ def attribute_objects
10
+ @attribute_objects ||= super.merge(dynamic_column_attribute_objects)
11
+ end
12
+
13
+ def attributes
14
+ super.merge!(attributes_from_method_names(self.class.dynamic_column_names))
15
+ end
16
+
17
+ ATTRIBUTE_METHODS = {
18
+ original_attributes: :value, # a map of `column_name => original_attribute(column_name)`
19
+ formatted_attributes: :formatted_cells, # a map of `column_name => format_cell(column_name, ...)`
20
+ }.freeze
21
+ ATTRIBUTE_METHODS.each do |method_name, attribute_method|
22
+ define_method(method_name) do
23
+ super().merge! column_names_to_attribute_value(self.class.dynamic_column_names, attribute_method)
24
+ end
25
+ end
26
+
27
+ class_methods do
28
+ protected
29
+ # See {Model::DynamicColumns#dynamic_column}
30
+ def dynamic_column(column_name, options={})
31
+ super
32
+ define_dynamic_attribute_method(column_name)
33
+ end
34
+
35
+ # Define default attribute method for a dynamic_column
36
+ # @param column_name [Symbol] the cell's column_name
37
+ def define_dynamic_attribute_method(column_name)
38
+ define_proxy_method(column_name) { original_attribute(column_name) }
39
+ dynamic_attribute_class.define_process_cell(self, column_name)
40
+ end
41
+
42
+ def ensure_define_dynamic_attribute_method
43
+ dynamic_column_names.each { |*args| define_dynamic_attribute_method(*args) }
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,26 @@
1
+ require 'csv_row_model/concerns/attributes_base'
2
+ require 'csv_row_model/internal/export/attribute'
3
+
4
+ module CsvRowModel
5
+ module Export
6
+ module Attributes
7
+ extend ActiveSupport::Concern
8
+ include AttributesBase
9
+
10
+ included do
11
+ ensure_attribute_method
12
+ end
13
+
14
+ def attribute_objects
15
+ @attribute_objects ||= array_to_block_hash(self.class.column_names) { |column_name| Attribute.new(column_name, self) }
16
+ end
17
+
18
+ class_methods do
19
+ protected
20
+ def define_attribute_method(column_name)
21
+ super { source_model.public_send(column_name) }
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -21,7 +21,7 @@ module CsvRowModel
21
21
 
22
22
  # @return [Array] an array of public_send(column_name) of the CSV model
23
23
  def to_row
24
- formatted_attributes.values
24
+ original_attributes.values
25
25
  end
26
26
 
27
27
  class_methods do
@@ -0,0 +1,33 @@
1
+ require 'csv_row_model/concerns/dynamic_columns_base'
2
+ require 'csv_row_model/concerns/export/attributes'
3
+ require 'csv_row_model/internal/export/dynamic_column_attribute'
4
+
5
+ module CsvRowModel
6
+ module Export
7
+ module DynamicColumns
8
+ extend ActiveSupport::Concern
9
+ include DynamicColumnsBase
10
+
11
+ included do
12
+ ensure_define_dynamic_attribute_method
13
+ end
14
+
15
+ def dynamic_column_attribute_objects
16
+ @dynamic_column_attribute_objects ||= array_to_block_hash(self.class.dynamic_column_names) do |column_name|
17
+ self.class.dynamic_attribute_class.new(column_name, self)
18
+ end
19
+ end
20
+
21
+ # @return [Array] an array of public_send(column_name) of the CSV model
22
+ def to_row
23
+ super.flatten
24
+ end
25
+
26
+ class_methods do
27
+ def dynamic_attribute_class
28
+ DynamicColumnAttribute
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,15 @@
1
+ module CsvRowModel
2
+ module HiddenModule
3
+ extend ActiveSupport::Concern
4
+
5
+ class_methods do
6
+ def hidden_module
7
+ @hidden_module ||= Module.new.tap { |mod| include mod }
8
+ end
9
+
10
+ def define_proxy_method(*args, &block)
11
+ hidden_module.send(:define_method, *args, &block)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,52 @@
1
+ require 'csv_row_model/concerns/attributes_base'
2
+ require 'csv_row_model/concerns/import/csv_string_model'
3
+ require 'csv_row_model/internal/import/attribute'
4
+
5
+ module CsvRowModel
6
+ module Import
7
+ module Attributes
8
+ extend ActiveSupport::Concern
9
+ include AttributesBase
10
+ include CsvStringModel
11
+
12
+ included do
13
+ ensure_attribute_method
14
+ end
15
+
16
+ def attribute_objects
17
+ @attribute_objects ||= begin
18
+ csv_string_model.valid?
19
+ _attribute_objects(csv_string_model.errors)
20
+ end
21
+ end
22
+
23
+ # return [Hash] a map changes from {.column}'s default option': `column_name -> [value_before_default, default_set]`
24
+ def default_changes
25
+ column_names_to_attribute_value(self.class.column_names, :default_change).delete_if {|k, v| v.blank? }
26
+ end
27
+
28
+ protected
29
+ # to prevent circular dependency with csv_string_model
30
+ def _attribute_objects(csv_string_model_errors={})
31
+ index = -1
32
+ array_to_block_hash(self.class.column_names) do |column_name|
33
+ Attribute.new(column_name, source_row[index += 1], csv_string_model_errors[column_name], self)
34
+ end
35
+ end
36
+
37
+ class_methods do
38
+ protected
39
+ def merge_options(column_name, options={})
40
+ original_options = columns[column_name]
41
+ csv_string_model_class.add_type_validation(column_name, columns[column_name]) unless original_options[:validate_type]
42
+ super
43
+ end
44
+
45
+ def define_attribute_method(column_name)
46
+ return if super { original_attribute(column_name) }.nil?
47
+ csv_string_model_class.add_type_validation(column_name, columns[column_name])
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -1,19 +1,22 @@
1
+ require 'csv_row_model/concerns/inspect'
2
+
1
3
  module CsvRowModel
2
4
  module Import
3
5
  module Base
4
6
  extend ActiveSupport::Concern
7
+ include Inspect
8
+ INSPECT_METHODS = %i[source_attributes initialized_at parent context previous].freeze
5
9
 
6
10
  included do
7
- attr_reader :source_header, :source_row, :line_number, :index, :previous
8
-
9
- validate { errors.add(:csv, "has #{@csv_exception.message}") if @csv_exception }
11
+ attr_reader :source_headers, :source_row, :line_number, :index, :previous
12
+ validate { errors.add(:csv, "has #{@csv_exception.message}") if @csv_exception }
10
13
  end
11
14
 
12
15
  # @param [Array] source_row_or_exception the csv row
13
16
  # @param options [Hash]
14
17
  # @option options [Integer] :index 1st row_model is 0, 2nd is 1, 3rd is 2, etc.
15
18
  # @option options [Integer] :line_number line_number in the CSV file
16
- # @option options [Array] :source_header the csv header row
19
+ # @option options [Array] :source_headers the csv header row
17
20
  # @option options [CsvRowModel::Import] :previous the previous row model
18
21
  # @option options [CsvRowModel::Import] :parent if the instance is a child, pass the parent
19
22
  def initialize(source_row_or_exception=[], options={})
@@ -21,19 +24,13 @@ module CsvRowModel
21
24
  @csv_exception = source_row if source_row.kind_of? Exception
22
25
  @source_row = [] if source_row_or_exception.class != Array
23
26
 
24
- @line_number, @index, @source_header = options[:line_number], options[:index], options[:source_header]
27
+ @line_number, @index, @source_headers = options[:line_number], options[:index], options[:source_headers]
25
28
 
26
29
  @previous = options[:previous].try(:dup)
27
30
  previous.try(:free_previous)
28
31
  super(options)
29
32
  end
30
33
 
31
- # @return [Hash] a map of `column_name => source_row[index_of_column_name]`
32
- def mapped_row
33
- return {} unless source_row
34
- @mapped_row ||= self.class.column_names.zip(source_row).to_h
35
- end
36
-
37
34
  # Free `previous` from memory to avoid making a linked list
38
35
  def free_previous
39
36
  attributes
@@ -64,7 +61,7 @@ module CsvRowModel
64
61
  # @return [Import] the next model instance from the csv
65
62
  def next(file, context={})
66
63
  csv = file.csv
67
- csv.skip_header
64
+ csv.skip_headers
68
65
  row_model = nil
69
66
 
70
67
  loop do # loop until the next parent or end_of_file? (need to read children rows)
@@ -72,7 +69,7 @@ module CsvRowModel
72
69
  row_model ||= new(csv.current_row,
73
70
  line_number: csv.line_number,
74
71
  index: file.index,
75
- source_header: csv.header,
72
+ source_headers: csv.headers,
76
73
  context: context,
77
74
  previous: file.previous_row_model)
78
75
 
@@ -82,11 +79,6 @@ module CsvRowModel
82
79
  return row_model if next_row_is_parent
83
80
  end
84
81
  end
85
-
86
- protected
87
- def inspect_methods
88
- @inspect_methods ||= %i[mapped_row initialized_at parent context previous].freeze
89
- end
90
82
  end
91
83
  end
92
84
  end
@@ -13,12 +13,11 @@ module CsvRowModel
13
13
  end
14
14
  end
15
15
 
16
- # @return [Import::CsvStringModel::Model] a model with validations related to csv_string_model (values are from format_cell)
17
16
  # @return [Import::CsvStringModel::Model] a model with validations related to csv_string_model (values are from format_cell)
18
17
  def csv_string_model
19
18
  @csv_string_model ||= begin
20
- cell_objects = _cell_objects
21
- formatted_hash = array_to_block_hash(self.class.column_names) { |column_name| cell_objects[column_name].formatted_value }
19
+ attribute_objects = _attribute_objects
20
+ formatted_hash = array_to_block_hash(self.class.column_names) { |column_name| attribute_objects[column_name].formatted_value }
22
21
  self.class.csv_string_model_class.new(formatted_hash)
23
22
  end
24
23
  end
@@ -0,0 +1,50 @@
1
+ require 'csv_row_model/concerns/dynamic_columns_base'
2
+ require 'csv_row_model/internal/import/dynamic_column_attribute'
3
+
4
+ module CsvRowModel
5
+ module Import
6
+ module DynamicColumns
7
+ extend ActiveSupport::Concern
8
+ include DynamicColumnsBase
9
+
10
+ included do
11
+ ensure_define_dynamic_attribute_method
12
+ end
13
+
14
+ def dynamic_column_attribute_objects
15
+ @dynamic_column_attribute_objects ||= array_to_block_hash(self.class.dynamic_column_names) do |column_name|
16
+ self.class.dynamic_attribute_class.new(column_name, dynamic_column_source_headers, dynamic_column_source_cells, self)
17
+ end
18
+ end
19
+
20
+ # @return [Array] an array of format_dynamic_column_header(...)
21
+ def formatted_dynamic_column_headers
22
+ dynamic_column_attribute_objects.values.first.try(:formatted_headers) || []
23
+ end
24
+
25
+ # @return [Array] dynamic_column headers
26
+ def dynamic_column_source_headers
27
+ self.class.dynamic_column_source_headers source_headers
28
+ end
29
+
30
+ # @return [Array] dynamic_column row data
31
+ def dynamic_column_source_cells
32
+ self.class.dynamic_column_source_cells source_row
33
+ end
34
+
35
+ class_methods do
36
+ def dynamic_column_source_headers(source_headers)
37
+ dynamic_columns? ? source_headers[columns.size..-1] : []
38
+ end
39
+
40
+ def dynamic_column_source_cells(source_row)
41
+ dynamic_columns? ? source_row[columns.size..-1] : []
42
+ end
43
+
44
+ def dynamic_attribute_class
45
+ DynamicColumnAttribute
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,4 +1,4 @@
1
- require 'csv_row_model/import/representation'
1
+ require 'csv_row_model/internal/import/representation'
2
2
 
3
3
  module CsvRowModel
4
4
  module Import
@@ -76,7 +76,7 @@ module CsvRowModel
76
76
  def define_representation_method(representation_name, options={}, &block)
77
77
  Representation.check_options(options)
78
78
  merge_representations(representation_name.to_sym => options)
79
- define_method(representation_name) { representation_value(representation_name) }
79
+ define_proxy_method(representation_name) { representation_value(representation_name) }
80
80
  Representation.define_lambda_method(self, representation_name, &block)
81
81
  end
82
82
  end
@@ -1,10 +1,9 @@
1
1
  module CsvRowModel
2
- module Concerns
3
- module Inspect
4
- def inspect
5
- s = self.class.send(:inspect_methods).map { |method| "#{method}=#{public_send(method).inspect}" }.join(", ")
6
- "#<#{self.class.name}:#{object_id} #{s}>"
7
- end
2
+ module Inspect
3
+ def inspect
4
+ s = self.class::INSPECT_METHODS.map { |method| "#{method}=#{public_send(method).inspect}" }.join(", ")
5
+ address = ('%x' % (object_id << 1)).rjust(14, "0")
6
+ "#<#{self.class.name}:0x#{address} #{s}>"
8
7
  end
9
8
  end
10
9
  end
@@ -1,34 +1,20 @@
1
+ require 'inherited_class_var'
2
+ require 'csv_row_model/internal/model/header'
3
+
1
4
  module CsvRowModel
2
5
  module Model
3
- module Columns
6
+ module Attributes
4
7
  extend ActiveSupport::Concern
8
+ include InheritedClassVar
9
+
5
10
  included do
6
11
  inherited_class_hash :columns
7
12
  end
8
13
 
9
- # @return [Hash] a map of `column_name => public_send(column_name)`
10
- def attributes
11
- attributes_from_method_names self.class.column_names
12
- end
13
-
14
- def to_json
15
- attributes.to_json
16
- end
17
-
18
14
  def headers
19
15
  self.class.headers(context)
20
16
  end
21
17
 
22
- protected
23
-
24
- def attributes_from_method_names(column_names)
25
- array_to_block_hash(column_names) { |column_name| try(column_name) }
26
- end
27
-
28
- def array_to_block_hash(array, &block)
29
- array.zip(array.map { |column_name| block.call(column_name) }).to_h
30
- end
31
-
32
18
  class_methods do
33
19
  # @return [Array<Symbol>] column names for the row model
34
20
  def column_names
@@ -41,19 +27,10 @@ module CsvRowModel
41
27
  column_names.index column_name
42
28
  end
43
29
 
44
- # @param [Symbol] column_name name of column to check
45
- # @return [Boolean] true if it's a column name
46
- def is_column_name? column_name
47
- column_name.is_a?(Symbol) && index(column_name)
48
- end
49
-
50
30
  # @param [Hash, OpenStruct] context name of column to check
51
31
  # @return [Array] column headers for the row model
52
32
  def headers(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
33
+ column_names.map { |column_name| Header.new(column_name, self, context).value }
57
34
  end
58
35
 
59
36
  # Safe to override
@@ -74,7 +51,7 @@ module CsvRowModel
74
51
 
75
52
  protected
76
53
 
77
- VALID_OPTIONS_KEYS = %i[type parse validate_type default header header_matchs].freeze
54
+ VALID_OPTIONS_KEYS = %i[type parse validate_type default header].freeze
78
55
 
79
56
  # Adds column to the row model
80
57
  #