csv_row_model 1.0.0.beta1 → 1.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
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
  #