csv_row_model 0.1.1 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +79 -1
- data/lib/csv_row_model/concerns/inherited_class_var.rb +2 -2
- data/lib/csv_row_model/export.rb +28 -13
- data/lib/csv_row_model/export/dynamic_columns.rb +42 -0
- data/lib/csv_row_model/export/file.rb +1 -1
- data/lib/csv_row_model/export/file_model.rb +1 -1
- data/lib/csv_row_model/import.rb +16 -7
- data/lib/csv_row_model/import/attributes.rb +9 -5
- data/lib/csv_row_model/import/dynamic_columns.rb +71 -0
- data/lib/csv_row_model/import/file.rb +1 -1
- data/lib/csv_row_model/import/file_model.rb +2 -2
- data/lib/csv_row_model/import/presenter.rb +9 -3
- data/lib/csv_row_model/model.rb +4 -2
- data/lib/csv_row_model/model/columns.rb +16 -11
- data/lib/csv_row_model/model/dynamic_columns.rb +74 -0
- data/lib/csv_row_model/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1875456398a0d03c7704e622ef2fb992c628d436
|
4
|
+
data.tar.gz: 8da58e2819b99eb9be2caa92e319ec505fa7f6b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be79b067b838f8541dc176f82ba86dab9d4719354d8c500ed93f50bafc14e0eeeb9564e528c85fa619116d1b2274de0c217d7ab4ae81d2e6a9549f3eea3ead6c
|
7
|
+
data.tar.gz: 09c668f2d08663000b284c91d1b1c77fea8109fc84b392143035fb39f9cca3f9ed6707084a8d0b3308d133384dfa7fc3fc517dc39d928b77f03d727599b2fccc
|
data/README.md
CHANGED
@@ -124,7 +124,7 @@ def original_attribute(column_name)
|
|
124
124
|
value = mapped_row[column_name]
|
125
125
|
|
126
126
|
# 2. Clean or format each cell
|
127
|
-
value = self.class.format_cell(
|
127
|
+
value = self.class.format_cell(cell, column_name, column_index)
|
128
128
|
|
129
129
|
if value.present?
|
130
130
|
# 3a. Parse the cell value (which does nothing if no parsing is specified)
|
@@ -428,4 +428,82 @@ class ImportFile < CsvRowModel::Import::File
|
|
428
428
|
...
|
429
429
|
end
|
430
430
|
end
|
431
|
+
```
|
432
|
+
|
433
|
+
## Dynamic columns
|
434
|
+
Dynamic columns are columns that can expand to many columns. Currently, we can only one dynamic column after all other standard columns.
|
435
|
+
The following:
|
436
|
+
|
437
|
+
```ruby
|
438
|
+
class DynamicColumnModel
|
439
|
+
include CsvRowModel::Model
|
440
|
+
|
441
|
+
column :first_name
|
442
|
+
column :last_name
|
443
|
+
dynamic_column :skills
|
444
|
+
end
|
445
|
+
```
|
446
|
+
|
447
|
+
represents this table:
|
448
|
+
|
449
|
+
| first_name | last_name | skill1 | skill2 |
|
450
|
+
| ---------- |----------- | ------ | ------ |
|
451
|
+
| John | Doe | No | Yes |
|
452
|
+
| Mario | Super | Yes | No |
|
453
|
+
| Mike | Jackson | Yes | Yes |
|
454
|
+
|
455
|
+
|
456
|
+
### Export
|
457
|
+
Dynamic column attributes are arrays, but each item in the array is defined via singular attribute method like
|
458
|
+
normal columns:
|
459
|
+
|
460
|
+
```ruby
|
461
|
+
class DynamicColumnExportModel < DynamicColumnModel
|
462
|
+
include CsvRowModel::Export
|
463
|
+
|
464
|
+
def skill(skill_name)
|
465
|
+
# below is an override, this is the default implementation: skill_name # => "skill1", then "skill2"
|
466
|
+
source_model.skills.include?(skill_name) ? "Yes" : "No"
|
467
|
+
end
|
468
|
+
|
469
|
+
class << self
|
470
|
+
# this is an override with the default implementation
|
471
|
+
def skill_header(skill_name)
|
472
|
+
skill_name
|
473
|
+
end
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
# the `skills` context is mapped to generate an array
|
478
|
+
export_file = CsvRowModel::Export::File.new(DynamicColumnExportModel, { skills: Skill.all })
|
479
|
+
export_file.generate do |csv|
|
480
|
+
User.all.each { |user| csv << user }
|
481
|
+
end
|
482
|
+
```
|
483
|
+
|
484
|
+
### Import
|
485
|
+
Like Export above, each item of the array is defined via singular attribute method like
|
486
|
+
normal columns:
|
487
|
+
|
488
|
+
```ruby
|
489
|
+
class DynamicColumnImportModel < DynamicColumnModel
|
490
|
+
include CsvRowModel::Import
|
491
|
+
|
492
|
+
# this is an override with the default implementation (override highly recommended)
|
493
|
+
def skill(value, skill_name)
|
494
|
+
value
|
495
|
+
end
|
496
|
+
|
497
|
+
class << self
|
498
|
+
# Clean/format every dynamic_column attribute array
|
499
|
+
#
|
500
|
+
# this is an override with the default implementation
|
501
|
+
def format_dynamic_column_cells(cells, column_name)
|
502
|
+
cells
|
503
|
+
end
|
504
|
+
end
|
505
|
+
end
|
506
|
+
row_model = CsvRowModel::Import::File.new(file_path, DynamicColumnImportModel).next
|
507
|
+
row_model.attributes # => { first_name: "John", last_name: "Doe", skills: ['No', 'Yes'] }
|
508
|
+
row_model.skills # => ['No', 'Yes']
|
431
509
|
```
|
@@ -14,7 +14,7 @@ module CsvRowModel
|
|
14
14
|
|
15
15
|
# @param included_module [Module] module to search for
|
16
16
|
# @return [Array<Module>] inherited_ancestors of included_module (including self)
|
17
|
-
def inherited_ancestors(included_module=
|
17
|
+
def inherited_ancestors(included_module=inherited_class_module)
|
18
18
|
included_model_index = ancestors.index(included_module)
|
19
19
|
included_model_index == 0 ? [included_module] : ancestors[0..(included_model_index - 1)]
|
20
20
|
end
|
@@ -43,7 +43,7 @@ module CsvRowModel
|
|
43
43
|
# @param variable_name [Symbol] class variable name (recommend :@_variable_name)
|
44
44
|
# @param default_value [Object] default value of the class variable
|
45
45
|
# @param merge_method [Symbol] method to merge values of the class variable
|
46
|
-
# @return [Object] a class variable merged across ancestors until
|
46
|
+
# @return [Object] a class variable merged across ancestors until inherited_class_module
|
47
47
|
def inherited_class_var(variable_name, default_value, merge_method)
|
48
48
|
class_cache(variable_name) do
|
49
49
|
value = default_value
|
data/lib/csv_row_model/export.rb
CHANGED
@@ -1,21 +1,15 @@
|
|
1
|
+
require 'csv_row_model/export/dynamic_columns'
|
2
|
+
|
1
3
|
module CsvRowModel
|
2
4
|
# Include this to with {Model} to have a RowModel for exporting to CSVs.
|
3
5
|
module Export
|
4
6
|
extend ActiveSupport::Concern
|
5
7
|
|
6
8
|
included do
|
9
|
+
include DynamicColumns
|
7
10
|
attr_reader :source_model, :context
|
8
11
|
|
9
|
-
self.column_names.each
|
10
|
-
|
11
|
-
# Safe to override
|
12
|
-
#
|
13
|
-
#
|
14
|
-
# @return [String] a string of public_send(column_name) of the CSV model
|
15
|
-
define_method(column_name) do
|
16
|
-
source_model.public_send(column_name)
|
17
|
-
end
|
18
|
-
end
|
12
|
+
self.column_names.each { |*args| define_attribute_method(*args) }
|
19
13
|
|
20
14
|
validates :source_model, presence: true
|
21
15
|
end
|
@@ -24,7 +18,7 @@ module CsvRowModel
|
|
24
18
|
# @param [Hash] context
|
25
19
|
def initialize(source_model, context={})
|
26
20
|
@source_model = source_model
|
27
|
-
@context
|
21
|
+
@context = OpenStruct.new(context)
|
28
22
|
end
|
29
23
|
|
30
24
|
def to_rows
|
@@ -37,8 +31,29 @@ module CsvRowModel
|
|
37
31
|
end
|
38
32
|
|
39
33
|
class_methods do
|
40
|
-
def setup(csv, with_headers: true)
|
41
|
-
csv << headers if with_headers
|
34
|
+
def setup(csv, context={}, with_headers: true)
|
35
|
+
csv << headers(context) if with_headers
|
36
|
+
end
|
37
|
+
|
38
|
+
# See {Model#column}
|
39
|
+
def column(column_name, options={})
|
40
|
+
super
|
41
|
+
define_attribute_method(column_name)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Define default attribute method for a column
|
45
|
+
# @param column_name [Symbol] the cell's column_name
|
46
|
+
def define_attribute_method(column_name)
|
47
|
+
define_method(column_name) do
|
48
|
+
self.class.format_cell(source_model.public_send(column_name), column_name, self.class.index(column_name))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Safe to override. Method applied to each cell by default
|
53
|
+
#
|
54
|
+
# @param cell [Object] the cell's value
|
55
|
+
def format_cell(cell, column_name, column_index)
|
56
|
+
cell
|
42
57
|
end
|
43
58
|
end
|
44
59
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module CsvRowModel
|
2
|
+
module Export
|
3
|
+
module DynamicColumns
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
self.dynamic_column_names.each { |*args| define_dynamic_attribute_method(*args) }
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [Array] an array of public_send(column_name) of the CSV model
|
11
|
+
def to_row
|
12
|
+
super.flatten
|
13
|
+
end
|
14
|
+
|
15
|
+
class_methods do
|
16
|
+
protected
|
17
|
+
|
18
|
+
# See {Model::DynamicColumns#dynamic_column}
|
19
|
+
def dynamic_column(column_name, options={})
|
20
|
+
super
|
21
|
+
define_dynamic_attribute_method(column_name)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Define default attribute method for a dynamic_column
|
25
|
+
# @param column_name [Symbol] the cell's column_name
|
26
|
+
def define_dynamic_attribute_method(column_name)
|
27
|
+
define_method(column_name) do
|
28
|
+
context.public_send(column_name).map do |header_model|
|
29
|
+
self.class.format_cell(
|
30
|
+
public_send(self.class.singular_dynamic_attribute_method_name(column_name), header_model),
|
31
|
+
column_name,
|
32
|
+
self.class.dynamic_index(column_name)
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
define_method(singular_dynamic_attribute_method_name(column_name)) { |header_model| header_model }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -34,7 +34,7 @@ module CsvRowModel
|
|
34
34
|
@file = Tempfile.new([export_model_class.name, ".csv"])
|
35
35
|
CSV.open(file.path, "wb") do |csv|
|
36
36
|
@csv = csv
|
37
|
-
export_model_class.setup(csv, with_headers: with_headers)
|
37
|
+
export_model_class.setup(csv, context, with_headers: with_headers)
|
38
38
|
yield self
|
39
39
|
end
|
40
40
|
ensure
|
data/lib/csv_row_model/import.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'csv_row_model/import/attributes'
|
2
2
|
require 'csv_row_model/import/presenter'
|
3
|
+
require 'csv_row_model/import/dynamic_columns'
|
3
4
|
|
4
5
|
module CsvRowModel
|
5
6
|
# Include this to with {Model} to have a RowModel for importing csvs.
|
@@ -9,6 +10,7 @@ module CsvRowModel
|
|
9
10
|
included do
|
10
11
|
include Concerns::Inspect
|
11
12
|
include Attributes
|
13
|
+
include DynamicColumns
|
12
14
|
|
13
15
|
attr_reader :attr_reader, :source_header, :source_row, :context, :index, :previous
|
14
16
|
|
@@ -54,12 +56,15 @@ module CsvRowModel
|
|
54
56
|
@csv_string_model ||= begin
|
55
57
|
if source_row
|
56
58
|
column_names = self.class.column_names
|
57
|
-
hash = column_names.zip(
|
58
|
-
|
59
|
-
|
59
|
+
hash = column_names.zip(
|
60
|
+
column_names.map.with_index do |column_name, index|
|
61
|
+
self.class.format_cell(source_row[index], column_name, index)
|
62
|
+
end
|
63
|
+
).to_h
|
60
64
|
else
|
61
65
|
hash = {}
|
62
66
|
end
|
67
|
+
|
63
68
|
self.class.csv_string_model_class.new(hash)
|
64
69
|
end
|
65
70
|
end
|
@@ -88,7 +93,7 @@ module CsvRowModel
|
|
88
93
|
end
|
89
94
|
|
90
95
|
if using_warnings?
|
91
|
-
csv_string_model.using_warnings
|
96
|
+
csv_string_model.using_warnings(&proc)
|
92
97
|
else
|
93
98
|
proc.call
|
94
99
|
end
|
@@ -105,13 +110,17 @@ module CsvRowModel
|
|
105
110
|
# @param [Hash] context extra data you want to work with the model
|
106
111
|
# @param [Import] prevuous the previous row model
|
107
112
|
# @return [Import] the next model instance from the csv
|
108
|
-
def next(csv, context={}, previous=nil)
|
113
|
+
def next(csv, source_header, context={}, previous=nil)
|
109
114
|
csv.skip_header
|
110
115
|
row_model = nil
|
111
116
|
|
112
117
|
loop do # loop until the next parent or end_of_file? (need to read children rows)
|
113
118
|
csv.read_row
|
114
|
-
row_model ||= new(csv.current_row,
|
119
|
+
row_model ||= new(csv.current_row,
|
120
|
+
index: csv.index,
|
121
|
+
source_header: source_header,
|
122
|
+
context: context,
|
123
|
+
previous: previous)
|
115
124
|
|
116
125
|
return row_model if csv.end_of_file?
|
117
126
|
|
@@ -132,7 +141,7 @@ module CsvRowModel
|
|
132
141
|
|
133
142
|
# Call to define the presenter
|
134
143
|
def presenter(&block)
|
135
|
-
presenter_class.class_eval
|
144
|
+
presenter_class.class_eval(&block)
|
136
145
|
end
|
137
146
|
end
|
138
147
|
end
|
@@ -26,10 +26,7 @@ module CsvRowModel
|
|
26
26
|
|
27
27
|
# @return [Object] the column's attribute before override
|
28
28
|
def original_attribute(column_name)
|
29
|
-
@original_attributes
|
30
|
-
@default_changes ||= {}
|
31
|
-
|
32
|
-
return @original_attributes[column_name] if @original_attributes.has_key? column_name
|
29
|
+
return @original_attributes[column_name] if original_attribute_memoized? column_name
|
33
30
|
|
34
31
|
csv_string_model.valid?
|
35
32
|
return nil unless csv_string_model.errors[column_name].blank?
|
@@ -51,6 +48,13 @@ module CsvRowModel
|
|
51
48
|
@default_changes
|
52
49
|
end
|
53
50
|
|
51
|
+
protected
|
52
|
+
def original_attribute_memoized?(column_name)
|
53
|
+
@original_attributes ||= {}
|
54
|
+
@default_changes ||= {}
|
55
|
+
@original_attributes.has_key? column_name
|
56
|
+
end
|
57
|
+
|
54
58
|
class_methods do
|
55
59
|
# Safe to override. Method applied to each cell by default
|
56
60
|
#
|
@@ -103,4 +107,4 @@ module CsvRowModel
|
|
103
107
|
end
|
104
108
|
end
|
105
109
|
end
|
106
|
-
end
|
110
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module CsvRowModel
|
2
|
+
module Import
|
3
|
+
module DynamicColumns
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
self.dynamic_column_names.each { |*args| define_dynamic_attribute_method(*args) }
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [Array] dynamic_column headers
|
11
|
+
def dynamic_source_headers
|
12
|
+
source_header[self.class.columns.size..-1]
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Array] dynamic_column row data
|
16
|
+
def dynamic_source_row
|
17
|
+
source_row[self.class.columns.size..-1]
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Hash] a map of `column_name => original_attribute(column_name)`
|
21
|
+
def original_attributes
|
22
|
+
super
|
23
|
+
self.class.dynamic_column_names.each { |column_name| original_attribute(column_name) }
|
24
|
+
@original_attributes
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [Object] the column's attribute before override
|
28
|
+
def original_attribute(column_name)
|
29
|
+
return super if self.class.column_names.include? column_name
|
30
|
+
return unless self.class.dynamic_column_names.include? column_name
|
31
|
+
return @original_attributes[column_name] if original_attribute_memoized? column_name
|
32
|
+
|
33
|
+
values = dynamic_source_headers.map.with_index do |source_header, index|
|
34
|
+
value = self.class.format_cell(
|
35
|
+
dynamic_source_row[index],
|
36
|
+
source_header,
|
37
|
+
self.class.dynamic_index(column_name)
|
38
|
+
)
|
39
|
+
public_send(self.class.singular_dynamic_attribute_method_name(column_name), value, source_header)
|
40
|
+
end
|
41
|
+
|
42
|
+
@original_attributes[column_name] = self.class.format_dynamic_column_cells(values, column_name)
|
43
|
+
end
|
44
|
+
|
45
|
+
class_methods do
|
46
|
+
# Safe to override. Method applied to each dynamic_column attribute
|
47
|
+
#
|
48
|
+
# @param cells [Array] Array of values
|
49
|
+
# @param column_name [Symbol] Dynamic column name
|
50
|
+
def format_dynamic_column_cells(cells, column_name)
|
51
|
+
cells
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
# See {Model#dynamic_column}
|
57
|
+
def dynamic_column(column_name, options={})
|
58
|
+
super
|
59
|
+
define_dynamic_attribute_method(column_name)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Define default attribute method for a column
|
63
|
+
# @param column_name [Symbol] the cell's column_name
|
64
|
+
def define_dynamic_attribute_method(column_name)
|
65
|
+
define_method(column_name) { original_attribute(column_name) }
|
66
|
+
define_method(singular_dynamic_attribute_method_name(column_name)) { |value, source_header| value }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -47,7 +47,7 @@ module CsvRowModel
|
|
47
47
|
run_callbacks :next do
|
48
48
|
context = context.to_h.reverse_merge(self.context)
|
49
49
|
@previous_row_model = current_row_model
|
50
|
-
@current_row_model = row_model_class.next(csv, context, previous_row_model)
|
50
|
+
@current_row_model = row_model_class.next(csv, header, context, previous_row_model)
|
51
51
|
@index += 1
|
52
52
|
@current_row_model = @index = nil if end_of_file?
|
53
53
|
end
|
@@ -28,7 +28,7 @@ module CsvRowModel
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
def next(csv, context={}, previous=nil)
|
31
|
+
def next(csv, source_header, context={}, previous=nil)
|
32
32
|
return csv.read_row unless csv.next_row
|
33
33
|
|
34
34
|
source_row = Array.new(header_matchers.size)
|
@@ -44,7 +44,7 @@ module CsvRowModel
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
new(source_row, context: context, previous: previous)
|
47
|
+
new(source_row, source_header: source_header, context: context, previous: previous)
|
48
48
|
end
|
49
49
|
end
|
50
50
|
end
|
@@ -63,10 +63,16 @@ module CsvRowModel
|
|
63
63
|
errors.messages.reverse_merge!(row_model.errors.messages)
|
64
64
|
end
|
65
65
|
|
66
|
+
# @param [Array] Array of column_names to check
|
67
|
+
# @return [Boolean] if column_names are valid
|
68
|
+
def row_model_valid?(*column_names)
|
69
|
+
row_model.valid? || (row_model.errors.keys & column_names).empty?
|
70
|
+
end
|
71
|
+
|
66
72
|
# @param [Symbol] attribute_name the attribute to check
|
67
73
|
# @return [Boolean] if the dependencies are valid
|
68
74
|
def valid_dependencies?(attribute_name)
|
69
|
-
|
75
|
+
row_model_valid?(*self.class.options(attribute_name)[:dependencies])
|
70
76
|
end
|
71
77
|
|
72
78
|
# equal to: @method_name ||= yield
|
@@ -78,7 +84,7 @@ module CsvRowModel
|
|
78
84
|
end
|
79
85
|
|
80
86
|
class << self
|
81
|
-
def
|
87
|
+
def inherited_class_module
|
82
88
|
Presenter
|
83
89
|
end
|
84
90
|
|
@@ -163,4 +169,4 @@ module CsvRowModel
|
|
163
169
|
end
|
164
170
|
end
|
165
171
|
end
|
166
|
-
end
|
172
|
+
end
|
data/lib/csv_row_model/model.rb
CHANGED
@@ -2,6 +2,7 @@ require 'csv_row_model/model/csv_string_model'
|
|
2
2
|
|
3
3
|
require 'csv_row_model/model/columns'
|
4
4
|
require 'csv_row_model/model/children'
|
5
|
+
require 'csv_row_model/model/dynamic_columns'
|
5
6
|
|
6
7
|
module CsvRowModel
|
7
8
|
# Base module for representing a RowModel---a model that represents row(s).
|
@@ -16,6 +17,7 @@ module CsvRowModel
|
|
16
17
|
|
17
18
|
include Columns
|
18
19
|
include Children
|
20
|
+
include DynamicColumns
|
19
21
|
|
20
22
|
# @return [Model] return the parent, if this instance is a child
|
21
23
|
attr_reader :parent
|
@@ -57,10 +59,10 @@ module CsvRowModel
|
|
57
59
|
protected
|
58
60
|
# Called to add validations to the csv_string_model_class
|
59
61
|
def csv_string_model(&block)
|
60
|
-
csv_string_model_class.class_eval
|
62
|
+
csv_string_model_class.class_eval(&block)
|
61
63
|
end
|
62
64
|
|
63
|
-
def
|
65
|
+
def inherited_class_module
|
64
66
|
Model
|
65
67
|
end
|
66
68
|
end
|
@@ -5,15 +5,24 @@ module CsvRowModel
|
|
5
5
|
|
6
6
|
# @return [Hash] a map of `column_name => public_send(column_name)`
|
7
7
|
def attributes
|
8
|
-
self.class.column_names
|
9
|
-
.zip(self.class.column_names.map { |column_name| public_send(column_name) })
|
10
|
-
.to_h
|
8
|
+
attributes_from_column_names self.class.column_names
|
11
9
|
end
|
12
10
|
|
13
11
|
def to_json
|
14
12
|
attributes.to_json
|
15
13
|
end
|
16
14
|
|
15
|
+
def headers
|
16
|
+
self.class.headers(context)
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
def attributes_from_column_names(column_names)
|
21
|
+
column_names
|
22
|
+
.zip(column_names.map { |column_name| public_send(column_name) })
|
23
|
+
.to_h
|
24
|
+
end
|
25
|
+
|
17
26
|
class_methods do
|
18
27
|
# @return [Array<Symbol>] column names for the row model
|
19
28
|
def column_names
|
@@ -43,14 +52,10 @@ module CsvRowModel
|
|
43
52
|
column_name.is_a?(Symbol) && index(column_name)
|
44
53
|
end
|
45
54
|
|
46
|
-
|
55
|
+
# @param [Hash, OpenStruct] context name of column to check
|
47
56
|
# @return [Array] column headers for the row model
|
48
|
-
def headers
|
49
|
-
@headers ||=
|
50
|
-
columns.map do |name, options|
|
51
|
-
options[:header] || format_header(name)
|
52
|
-
end
|
53
|
-
end
|
57
|
+
def headers(context={})
|
58
|
+
@headers ||= columns.map { |name, options| options[:header] || format_header(name) }
|
54
59
|
end
|
55
60
|
|
56
61
|
# Safe to override
|
@@ -93,4 +98,4 @@ module CsvRowModel
|
|
93
98
|
end
|
94
99
|
end
|
95
100
|
end
|
96
|
-
end
|
101
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module CsvRowModel
|
2
|
+
module Model
|
3
|
+
module DynamicColumns
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
# See Model::Columns#attributes
|
7
|
+
def attributes
|
8
|
+
super.merge(attributes_from_column_names(self.class.dynamic_column_names))
|
9
|
+
end
|
10
|
+
|
11
|
+
class_methods do
|
12
|
+
# See Model::Columns::headers
|
13
|
+
def headers(context={})
|
14
|
+
@headers ||= super + dynamic_column_names.map do |column_name|
|
15
|
+
OpenStruct.new(context).public_send(column_name).each do |header_model|
|
16
|
+
public_send(header_method_name(column_name), header_model)
|
17
|
+
end
|
18
|
+
end.flatten
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Integer] index of dynamic_column of all columns
|
22
|
+
def dynamic_index(column_name)
|
23
|
+
offset = dynamic_column_names.index(column_name)
|
24
|
+
offset ? columns.size + offset : nil
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [Array<Symbol>] column names for the row model
|
28
|
+
def dynamic_column_names
|
29
|
+
dynamic_columns.keys
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Hash] column names mapped to their options
|
33
|
+
def dynamic_columns
|
34
|
+
inherited_class_var(:@_dynamic_columns, {}, :merge)
|
35
|
+
end
|
36
|
+
|
37
|
+
def header_method_name(column_name)
|
38
|
+
"#{column_name.to_s.singularize}_header"
|
39
|
+
end
|
40
|
+
def singular_dynamic_attribute_method_name(column_name)
|
41
|
+
column_name.to_s.singularize
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
def merge_dynamic_columns(column_hash)
|
47
|
+
@_dynamic_columns ||= {}
|
48
|
+
deep_clear_class_cache(:@_dynamic_columns)
|
49
|
+
@_dynamic_columns.merge!(column_hash)
|
50
|
+
end
|
51
|
+
|
52
|
+
VALID_OPTIONS_KEYS = [].freeze
|
53
|
+
|
54
|
+
# define a dynamic_column, must be after all normal columns
|
55
|
+
#
|
56
|
+
# options to be implemented later
|
57
|
+
#
|
58
|
+
# @param column_name [Symbol] column_name
|
59
|
+
def dynamic_column(column_name, options={})
|
60
|
+
extra_keys = options.keys - VALID_OPTIONS_KEYS
|
61
|
+
raise ArgumentError.new("invalid options #{extra_keys}") unless extra_keys.empty?
|
62
|
+
|
63
|
+
define_header_method(column_name)
|
64
|
+
|
65
|
+
merge_dynamic_columns(column_name.to_sym => options)
|
66
|
+
end
|
67
|
+
|
68
|
+
def define_header_method(column_name)
|
69
|
+
define_singleton_method(header_method_name(column_name)) { |header_model| header_model }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: csv_row_model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Steve Chung
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-10-
|
11
|
+
date: 2015-10-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -64,12 +64,14 @@ files:
|
|
64
64
|
- lib/csv_row_model/concerns/inspect.rb
|
65
65
|
- lib/csv_row_model/engine.rb
|
66
66
|
- lib/csv_row_model/export.rb
|
67
|
+
- lib/csv_row_model/export/dynamic_columns.rb
|
67
68
|
- lib/csv_row_model/export/file.rb
|
68
69
|
- lib/csv_row_model/export/file_model.rb
|
69
70
|
- lib/csv_row_model/import.rb
|
70
71
|
- lib/csv_row_model/import/attributes.rb
|
71
72
|
- lib/csv_row_model/import/csv.rb
|
72
73
|
- lib/csv_row_model/import/csv/row.rb
|
74
|
+
- lib/csv_row_model/import/dynamic_columns.rb
|
73
75
|
- lib/csv_row_model/import/file.rb
|
74
76
|
- lib/csv_row_model/import/file/callbacks.rb
|
75
77
|
- lib/csv_row_model/import/file/validations.rb
|
@@ -79,6 +81,7 @@ files:
|
|
79
81
|
- lib/csv_row_model/model/children.rb
|
80
82
|
- lib/csv_row_model/model/columns.rb
|
81
83
|
- lib/csv_row_model/model/csv_string_model.rb
|
84
|
+
- lib/csv_row_model/model/dynamic_columns.rb
|
82
85
|
- lib/csv_row_model/model/file_model.rb
|
83
86
|
- lib/csv_row_model/validators/boolean_format.rb
|
84
87
|
- lib/csv_row_model/validators/date_format.rb
|
@@ -108,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
108
111
|
version: '0'
|
109
112
|
requirements: []
|
110
113
|
rubyforge_project:
|
111
|
-
rubygems_version: 2.4.
|
114
|
+
rubygems_version: 2.4.6
|
112
115
|
signing_key:
|
113
116
|
specification_version: 4
|
114
117
|
summary: Import and export your custom CSVs with a intuitive shared Ruby interface.
|