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