csv_row_model 0.1.1 → 0.2.1
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/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.
|