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.
- checksums.yaml +4 -4
- data/.rspec +2 -1
- data/README.md +22 -9
- data/lib/csv_row_model/concerns/attributes_base.rb +86 -0
- data/lib/csv_row_model/concerns/check_options.rb +7 -9
- data/lib/csv_row_model/concerns/dynamic_columns_base.rb +47 -0
- data/lib/csv_row_model/concerns/export/attributes.rb +26 -0
- data/lib/csv_row_model/{export → concerns/export}/base.rb +1 -1
- data/lib/csv_row_model/concerns/export/dynamic_columns.rb +33 -0
- data/lib/csv_row_model/concerns/hidden_module.rb +15 -0
- data/lib/csv_row_model/concerns/import/attributes.rb +52 -0
- data/lib/csv_row_model/{import → concerns/import}/base.rb +10 -18
- data/lib/csv_row_model/{import → concerns/import}/csv_string_model.rb +2 -3
- data/lib/csv_row_model/concerns/import/dynamic_columns.rb +50 -0
- data/lib/csv_row_model/{import → concerns/import}/represents.rb +2 -2
- data/lib/csv_row_model/concerns/inspect.rb +5 -6
- data/lib/csv_row_model/{model/columns.rb → concerns/model/attributes.rb} +8 -31
- data/lib/csv_row_model/{model → concerns/model}/base.rb +1 -11
- data/lib/csv_row_model/{model → concerns/model}/children.rb +7 -1
- data/lib/csv_row_model/{model → concerns/model}/dynamic_columns.rb +23 -42
- data/lib/csv_row_model/export/file_model.rb +2 -15
- data/lib/csv_row_model/export.rb +3 -3
- data/lib/csv_row_model/import/file.rb +62 -22
- data/lib/csv_row_model/import/file_model.rb +3 -4
- data/lib/csv_row_model/import.rb +5 -9
- data/lib/csv_row_model/internal/attribute_base.rb +22 -0
- data/lib/csv_row_model/internal/concerns/column_shared.rb +21 -0
- data/lib/csv_row_model/internal/concerns/dynamic_column_shared.rb +28 -0
- data/lib/csv_row_model/internal/dynamic_column_attribute_base.rb +42 -0
- data/lib/csv_row_model/internal/export/attribute.rb +15 -0
- data/lib/csv_row_model/internal/export/dynamic_column_attribute.rb +21 -0
- data/lib/csv_row_model/{import/cell.rb → internal/import/attribute.rb} +4 -13
- data/lib/csv_row_model/{import → internal/import}/csv.rb +11 -13
- data/lib/csv_row_model/internal/import/dynamic_column_attribute.rb +33 -0
- data/lib/csv_row_model/{import → internal/import}/representation.rb +2 -2
- data/lib/csv_row_model/internal/model/dynamic_column_header.rb +22 -0
- data/lib/csv_row_model/internal/model/header.rb +25 -0
- data/lib/csv_row_model/model/file_model.rb +0 -1
- data/lib/csv_row_model/model.rb +5 -12
- data/lib/csv_row_model/validators/{boolean_format.rb → boolean_format_validator.rb} +1 -1
- data/lib/csv_row_model/validators/date_format_validator.rb +7 -0
- data/lib/csv_row_model/validators/date_time_format_validator.rb +7 -0
- data/lib/csv_row_model/validators/{default_change.rb → default_change_validator.rb} +0 -0
- data/lib/csv_row_model/validators/float_format_validator.rb +7 -0
- data/lib/csv_row_model/validators/integer_format_validator.rb +15 -0
- data/lib/csv_row_model/version.rb +1 -1
- data/lib/csv_row_model.rb +16 -37
- metadata +35 -33
- data/lib/csv_row_model/engine.rb +0 -5
- data/lib/csv_row_model/export/attributes.rb +0 -46
- data/lib/csv_row_model/export/cell.rb +0 -24
- data/lib/csv_row_model/export/dynamic_column_cell.rb +0 -29
- data/lib/csv_row_model/export/dynamic_columns.rb +0 -46
- data/lib/csv_row_model/import/attributes.rb +0 -66
- data/lib/csv_row_model/import/dynamic_column_cell.rb +0 -37
- data/lib/csv_row_model/import/dynamic_columns.rb +0 -59
- data/lib/csv_row_model/import/file/callbacks.rb +0 -17
- data/lib/csv_row_model/import/file/validations.rb +0 -43
- data/lib/csv_row_model/model/comparison.rb +0 -15
- data/lib/csv_row_model/model/dynamic_column_cell.rb +0 -44
- data/lib/csv_row_model/validators/date_format.rb +0 -9
- data/lib/csv_row_model/validators/date_time_format.rb +0 -9
- data/lib/csv_row_model/validators/float_format.rb +0 -9
- data/lib/csv_row_model/validators/integer_format.rb +0 -9
- data/lib/csv_row_model/validators/number_validator.rb +0 -16
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b9eb25b87297dafc35c155017125d709b0606449
|
4
|
+
data.tar.gz: ba28f582a1319e2217350a6ad910ef7fa427c386
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 52392629db0ded13d4dc3ede022c1f98520d21aa59ee10dca1f98e9011118597b04b424fd1354dd7d79470b8f0c809292494bf4e447611c321cb69955e804c44
|
7
|
+
data.tar.gz: 6b8d3f701bc4005dc8508cc64ee601255c50d587b81fa80ccc0579a57db88d3eb18589e95b65802375ffedaf80e940f22b78625028d6ebda1b649007aa9f693c
|
data/.rspec
CHANGED
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.
|
53
|
+
row_model.headers # => ["id", "name"]
|
54
54
|
|
55
55
|
row_model.source_row # => ["1", "Some Project Name"]
|
56
|
-
row_model.
|
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 =
|
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
|
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
|
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,
|
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
|
-
#
|
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
|
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
|
3
|
-
|
4
|
-
extend ActiveSupport::Concern
|
2
|
+
module CheckOptions
|
3
|
+
extend ActiveSupport::Concern
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
@@ -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 :
|
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] :
|
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, @
|
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.
|
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
|
-
|
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
|
-
|
21
|
-
formatted_hash = array_to_block_hash(self.class.column_names) { |column_name|
|
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
|
-
|
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
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
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
|
-
|
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
|
54
|
+
VALID_OPTIONS_KEYS = %i[type parse validate_type default header].freeze
|
78
55
|
|
79
56
|
# Adds column to the row model
|
80
57
|
#
|