csv_row_model 0.4.1 → 1.0.0.beta1
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 +162 -162
- data/csv_row_model.gemspec +2 -1
- data/lib/csv_row_model/concerns/{invalid_options.rb → check_options.rb} +4 -6
- data/lib/csv_row_model/export/attributes.rb +11 -20
- data/lib/csv_row_model/export/base.rb +3 -3
- data/lib/csv_row_model/export/cell.rb +24 -0
- data/lib/csv_row_model/export/dynamic_column_cell.rb +29 -0
- data/lib/csv_row_model/export/dynamic_columns.rb +11 -13
- data/lib/csv_row_model/export/file.rb +7 -7
- data/lib/csv_row_model/export/file_model.rb +2 -2
- data/lib/csv_row_model/export.rb +0 -3
- data/lib/csv_row_model/import/attributes.rb +18 -81
- data/lib/csv_row_model/import/base.rb +26 -69
- data/lib/csv_row_model/import/cell.rb +77 -0
- data/lib/csv_row_model/import/csv.rb +23 -60
- data/lib/csv_row_model/import/csv_string_model.rb +65 -0
- data/lib/csv_row_model/import/dynamic_column_cell.rb +37 -0
- data/lib/csv_row_model/import/dynamic_columns.rb +20 -37
- data/lib/csv_row_model/import/file/validations.rb +5 -1
- data/lib/csv_row_model/import/file.rb +8 -3
- data/lib/csv_row_model/import/file_model.rb +5 -4
- data/lib/csv_row_model/import/representation.rb +60 -0
- data/lib/csv_row_model/import/represents.rb +85 -0
- data/lib/csv_row_model/import.rb +4 -3
- data/lib/csv_row_model/model/base.rb +5 -15
- data/lib/csv_row_model/model/children.rb +2 -1
- data/lib/csv_row_model/model/columns.rb +19 -16
- data/lib/csv_row_model/model/comparison.rb +1 -1
- data/lib/csv_row_model/model/dynamic_column_cell.rb +44 -0
- data/lib/csv_row_model/model/dynamic_columns.rb +26 -11
- data/lib/csv_row_model/model.rb +4 -3
- data/lib/csv_row_model/version.rb +1 -1
- data/lib/csv_row_model.rb +3 -1
- metadata +29 -10
- data/lib/csv_row_model/concerns/inherited_class_var.rb +0 -121
- data/lib/csv_row_model/import/presenter.rb +0 -153
- data/lib/csv_row_model/model/csv_string_model.rb +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 620b2a84648e1723419994992518ecd9e77609d3
|
4
|
+
data.tar.gz: 5b331bc69fb075b9ca84bf53db592c0e6db15dde
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5e10d1f2a7fd7115e632fadee715a852d9aa05bf5f1d69d04c79fc1503d598118bb75dfbb1707af4cf5afa6692c2338bd1d1e93125b39d1175445f34197dacad
|
7
|
+
data.tar.gz: 78994103e5eab1c176f5a2e9c7306073071a7da9adfc94344393e16cdd32fff6023c3f1e8e018b2f6ed8178f2720bbfc7149abba232a3b4414393ae6bc6bb8cf
|
data/README.md
CHANGED
@@ -29,7 +29,7 @@ class ProjectExportRowModel < ProjectRowModel
|
|
29
29
|
end
|
30
30
|
|
31
31
|
export_file = CsvRowModel::Export::File.new(ProjectExportRowModel)
|
32
|
-
export_file.generate { |csv| csv << project }
|
32
|
+
export_file.generate { |csv| csv << project } # `project` is the `source_model` in `ProjectExportRowModel`
|
33
33
|
export_file.file # => <Tempfile>
|
34
34
|
export_file.to_s # => export_file.file.read
|
35
35
|
```
|
@@ -86,10 +86,10 @@ To generate a header value, the following pseudocode is executed:
|
|
86
86
|
```ruby
|
87
87
|
def header(column_name)
|
88
88
|
# 1. Header Option
|
89
|
-
header =
|
89
|
+
header = options_for(column_name)[:header]
|
90
90
|
|
91
91
|
# 2. format_header
|
92
|
-
header || format_header(column_name)
|
92
|
+
header || format_header(column_name, column_index, context)
|
93
93
|
end
|
94
94
|
```
|
95
95
|
|
@@ -108,7 +108,7 @@ Override the `format_header` method to format column header names:
|
|
108
108
|
class ProjectExportRowModel < ProjectRowModel
|
109
109
|
include CsvRowModel::Export
|
110
110
|
class << self
|
111
|
-
def format_header(column_name)
|
111
|
+
def format_header(column_name, column_index, context)
|
112
112
|
column_name.to_s.titleize
|
113
113
|
end
|
114
114
|
end
|
@@ -137,9 +137,8 @@ def original_attribute(column_name)
|
|
137
137
|
end
|
138
138
|
end
|
139
139
|
|
140
|
-
def original_attributes;
|
141
|
-
|
142
|
-
def id; original_attribute[:id] end
|
140
|
+
def original_attributes; { id: original_attribute(:id) } end
|
141
|
+
def id; original_attribute(:id) end
|
143
142
|
```
|
144
143
|
|
145
144
|
#### Format Cell
|
@@ -148,7 +147,7 @@ Override the `format_cell` method to clean/format every cell:
|
|
148
147
|
class ProjectImportRowModel < ProjectRowModel
|
149
148
|
include CsvRowModel::Import
|
150
149
|
class << self
|
151
|
-
def format_cell(cell, column_name, column_index, context
|
150
|
+
def format_cell(cell, column_name, column_index, context)
|
152
151
|
cell = cell.strip
|
153
152
|
cell.blank? ? nil : cell
|
154
153
|
end
|
@@ -173,7 +172,7 @@ class ProjectImportRowModel
|
|
173
172
|
end
|
174
173
|
```
|
175
174
|
|
176
|
-
There are validators for different types: `Boolean`, `Date`, `DateTime`, `Float`, `Integer`. See [
|
175
|
+
There are validators for different types: `Boolean`, `Date`, `DateTime`, `Float`, `Integer`. See [Type Format](#type-format) for more.
|
177
176
|
|
178
177
|
#### Default
|
179
178
|
Sets the default value of the cell:
|
@@ -193,157 +192,37 @@ row_model.name # => "John Doe"
|
|
193
192
|
row_model.default_changes # => { id: ["", 1], name: ["", "John Doe"] }
|
194
193
|
```
|
195
194
|
|
196
|
-
`DefaultChangeValidator` is provided to allows to add warnings when defaults are set. See [
|
195
|
+
`DefaultChangeValidator` is provided to allows to add warnings when defaults are set. See [Default Changes](#default-changes) for more.
|
197
196
|
|
198
|
-
|
197
|
+
### Validations
|
199
198
|
|
200
|
-
|
199
|
+
[`ActiveModel::Validations`](http://api.rubyonrails.org/classes/ActiveModel/Validations.html) and [`ActiveWarnings`](https://github.com/s12chung/active_warnings)
|
200
|
+
are included for errors and warnings.
|
201
201
|
|
202
|
-
|
203
|
-
|
204
|
-
```ruby
|
205
|
-
class UserImportRowModel
|
206
|
-
include CsvRowModel::Model
|
207
|
-
include CsvRowModel::Import
|
208
|
-
|
209
|
-
column :id, type: Integer
|
210
|
-
column :name
|
211
|
-
column :email
|
212
|
-
|
213
|
-
# uses ProjectImportRowModel#valid? to detect the child row
|
214
|
-
has_many :projects, ProjectImportRowModel
|
215
|
-
end
|
216
|
-
|
217
|
-
import_file = CsvRowModel::Import::File.new(file_path, UserImportRowModel)
|
218
|
-
row_model = import_file.next
|
219
|
-
row_model.projects # => [<ProjectImportRowModel>, ...]
|
220
|
-
```
|
221
|
-
|
222
|
-
### Layers
|
223
|
-
For complex `RowModel`s there are different layers you can work with:
|
224
|
-
```ruby
|
225
|
-
import_file = CsvRowModel::Import::File.new(file_path, ProjectImportRowModel)
|
226
|
-
row_model = import_file.next
|
227
|
-
|
228
|
-
# the three layers:
|
229
|
-
# 1. csv_string_model - represents the row BEFORE parsing (attributes are always strings)
|
230
|
-
row_model.csv_string_model
|
231
|
-
|
232
|
-
# 2. RowModel - represents the row AFTER parsing
|
233
|
-
row_model
|
234
|
-
|
235
|
-
# 3. Presenter - an abstraction of a row
|
236
|
-
row_model.presenter
|
237
|
-
```
|
238
|
-
|
239
|
-
#### CsvStringModel
|
240
|
-
The `CsvStringModel` represents a row before parsing to add parsing validations.
|
202
|
+
There are layers to validations.
|
241
203
|
|
242
204
|
```ruby
|
243
205
|
class ProjectImportRowModel
|
244
206
|
include CsvRowModel::Model
|
245
207
|
include CsvRowModel::Import
|
246
|
-
|
247
|
-
#
|
248
|
-
|
249
|
-
|
250
|
-
#
|
251
|
-
|
252
|
-
|
253
|
-
csv_string_model do
|
254
|
-
# define your csv_string_model here
|
255
|
-
|
256
|
-
# this is applied BEFORE the parsed CSV on csv_string_model
|
257
|
-
validates :id, presense: true
|
258
|
-
|
259
|
-
def random_method; "Hihi" end
|
208
|
+
|
209
|
+
# Errors - by default, an Error will make the row skip
|
210
|
+
validates :id, numericality: { greater_than: 0 } # ActiveModel::Validations
|
211
|
+
|
212
|
+
# Warnings - a message you want the user to see, but will not make the row skip
|
213
|
+
warnings do # ActiveWarnings, see: https://github.com/s12chung/active_warnings
|
214
|
+
validates :some_custom_string, presence: true
|
260
215
|
end
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
csv_string_model.random_method => "Hihi"
|
267
|
-
csv_string_model.valid? => false
|
268
|
-
csv_string_model.errors.full_messages # => ["Id can't be blank'"]
|
269
|
-
|
270
|
-
# Errors are propagated for simplicity
|
271
|
-
row_model.valid? # => false
|
272
|
-
row_model.errors.full_messages # => ["Id can't be blank'"]
|
273
|
-
|
274
|
-
# Applied to the parsed Integer
|
275
|
-
row_model = ProjectRowModel.new(["-1"])
|
276
|
-
row_model.valid? # => false
|
277
|
-
row_model.errors.full_messages # => ["Id must be greater than 0"]
|
278
|
-
```
|
279
|
-
|
280
|
-
Note that `CsvStringModel` validations are calculated after [Format Cell](#format-cell).
|
281
|
-
|
282
|
-
#### Presenter
|
283
|
-
For complex rows, you can wrap your `RowModel` with a presenter:
|
284
|
-
|
285
|
-
```ruby
|
286
|
-
class ProjectImportRowModel < ProjectRowModel
|
287
|
-
include CsvRowModel::Import
|
288
|
-
|
289
|
-
presenter do
|
290
|
-
# define your presenter here
|
291
|
-
|
292
|
-
# this is shorthand for the psuedo_code:
|
293
|
-
# def project
|
294
|
-
# return if row_model.id.blank? || row_model.name.blank?
|
295
|
-
#
|
296
|
-
# # turn off memoziation with `memoize: false` option
|
297
|
-
# @project ||= __the_code_inside_the_block__
|
298
|
-
# end
|
299
|
-
#
|
300
|
-
# and the psuedo_code:
|
301
|
-
# def valid?
|
302
|
-
# super # calls ActiveModel::Errors code
|
303
|
-
# errors.delete(:project) if row_model.id.invalid? || row_model.name.invalid?
|
304
|
-
# errors.empty?
|
305
|
-
# end
|
306
|
-
attribute :project, dependencies: [:id, :name] do
|
307
|
-
project = Project.where(id: row_model.id).first
|
308
|
-
|
309
|
-
# project not found, invalid.
|
310
|
-
return unless project
|
311
|
-
|
312
|
-
project.name = row_model.name
|
313
|
-
project
|
314
|
-
end
|
216
|
+
|
217
|
+
# This is for validation of the strings before parsing. See: https://github.com/FinalCAD/csv_row_model#csvstringmodel
|
218
|
+
csv_string_model do
|
219
|
+
validates :id, presence: true
|
220
|
+
# can do warnings too
|
315
221
|
end
|
316
222
|
end
|
317
|
-
|
318
|
-
# Importing is the same
|
319
|
-
import_file = CsvRowModel::Import::File.new(file_path, ProjectImportRowModel)
|
320
|
-
row_model = import_file.next
|
321
|
-
presenter = row_model.presenter
|
322
|
-
|
323
|
-
presenter.row_model # gets the row model underneath
|
324
|
-
presenter.project.name == presenter.row_model.name # => "Some Project Name"
|
325
223
|
```
|
326
224
|
|
327
|
-
|
328
|
-
|
329
|
-
Also, the `attribute` defines a dynamic `#project` method that:
|
330
|
-
|
331
|
-
1. Memoizes by default, turn off with `memoize: false` option
|
332
|
-
2. All errors of `row_model` are propagated to the presenter when calling `presenter.valid?`
|
333
|
-
3. Handles dependencies:
|
334
|
-
- When any of the dependencies are `blank?`, the attribute block is not called and the attribute returns `nil`.
|
335
|
-
- When any of the dependencies are `invalid?`, `presenter.errors` for dependencies are cleaned. For the example above, if `row_model.id/name` are `invalid?`, then
|
336
|
-
the `:project` key is removed from the errors, so: `presenter.errors.keys # => [:id, :name]`
|
337
|
-
|
338
|
-
## Import Validations
|
339
|
-
|
340
|
-
Use [`ActiveModel::Validations`](http://api.rubyonrails.org/classes/ActiveModel/Validations.html) the `RowModel`'s [Layers](#layers).
|
341
|
-
Please read [Layers](#layers) for more information.
|
342
|
-
|
343
|
-
Included is [`ActiveWarnings`](https://github.com/s12chung/active_warnings) on `Model` and `Presenter` for warnings.
|
344
|
-
|
345
|
-
|
346
|
-
### Type Format
|
225
|
+
#### Type Format
|
347
226
|
Notice that there are validators given for different types: `Boolean`, `Date`, `DateTime`, `Float`, `Integer`:
|
348
227
|
|
349
228
|
```ruby
|
@@ -364,8 +243,8 @@ row_model.valid? # => false
|
|
364
243
|
row_model.errors.full_messages # => ["Id is not a Integer format"]
|
365
244
|
```
|
366
245
|
|
367
|
-
|
368
|
-
[Default Changes](#default)
|
246
|
+
#### Default Changes
|
247
|
+
A custom validator for [Default Changes](#default).
|
369
248
|
|
370
249
|
```ruby
|
371
250
|
class ProjectImportRowModel
|
@@ -373,17 +252,13 @@ class ProjectImportRowModel
|
|
373
252
|
include CsvRowModel::Input
|
374
253
|
|
375
254
|
column :id, default: 1
|
376
|
-
|
377
|
-
warnings do
|
378
|
-
validates :id, default_change: true
|
379
|
-
end
|
255
|
+
validates :id, default_change: true
|
380
256
|
end
|
381
257
|
|
382
258
|
row_model = ProjectImportRowModel.new([""])
|
383
259
|
|
384
|
-
row_model.
|
385
|
-
row_model.
|
386
|
-
row_model.warnings.full_messages # => ["Id changed by default"]
|
260
|
+
row_model.valid? # => false
|
261
|
+
row_model.errors.full_messages # => ["Id changed by default"]
|
387
262
|
row_model.default_changes # => { id: ["", 1] }
|
388
263
|
```
|
389
264
|
|
@@ -396,13 +271,13 @@ abort logic:
|
|
396
271
|
class ProjectImportRowModel
|
397
272
|
# always skip
|
398
273
|
def skip?
|
399
|
-
true # original implementation: !valid?
|
274
|
+
true # original implementation: !valid?
|
400
275
|
end
|
401
276
|
end
|
402
277
|
|
403
|
-
CsvRowModel::Import::File.new(file_path, ProjectImportRowModel)
|
404
|
-
|
405
|
-
|
278
|
+
import_file = CsvRowModel::Import::File.new(file_path, ProjectImportRowModel)
|
279
|
+
import_file.each { |project_import_model| puts "does not yield here" }
|
280
|
+
import_file.next # does not skip or abort
|
406
281
|
```
|
407
282
|
|
408
283
|
### Import Callbacks
|
@@ -432,6 +307,128 @@ class ImportFile < CsvRowModel::Import::File
|
|
432
307
|
end
|
433
308
|
```
|
434
309
|
|
310
|
+
## Advanced Import
|
311
|
+
|
312
|
+
### CsvStringModel
|
313
|
+
The `CsvStringModel` represents a row BEFORE parsing to add validations.
|
314
|
+
|
315
|
+
```ruby
|
316
|
+
class ProjectImportRowModel
|
317
|
+
include CsvRowModel::Model
|
318
|
+
include CsvRowModel::Import
|
319
|
+
|
320
|
+
# Note the type definition here for parsing
|
321
|
+
column :id, type: Integer
|
322
|
+
|
323
|
+
# this is applied to the parsed CSV on the model
|
324
|
+
validates :id, numericality: { greater_than: 0 }
|
325
|
+
|
326
|
+
csv_string_model do
|
327
|
+
# define your csv_string_model here
|
328
|
+
|
329
|
+
# this is applied BEFORE the parsed CSV on csv_string_model
|
330
|
+
validates :id, presence: true
|
331
|
+
|
332
|
+
def random_method; "Hihi" end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
# Applied to the String
|
337
|
+
ProjectImportRowModel.new([""])
|
338
|
+
csv_string_model = row_model.csv_string_model
|
339
|
+
csv_string_model.random_method => "Hihi"
|
340
|
+
csv_string_model.valid? => false
|
341
|
+
csv_string_model.errors.full_messages # => ["Id can't be blank'"]
|
342
|
+
|
343
|
+
# Errors are propagated for simplicity
|
344
|
+
row_model.valid? # => false
|
345
|
+
row_model.errors.full_messages # => ["Id can't be blank'"]
|
346
|
+
|
347
|
+
# Applied to the parsed Integer
|
348
|
+
row_model = ProjectRowModel.new(["-1"])
|
349
|
+
row_model.valid? # => false
|
350
|
+
row_model.errors.full_messages # => ["Id must be greater than 0"]
|
351
|
+
```
|
352
|
+
|
353
|
+
Note that `CsvStringModel` validations are calculated after [Format Cell](#format-cell).
|
354
|
+
|
355
|
+
### Represents
|
356
|
+
A CSV is often a representation of database model(s), much like how JSON parameters represents models in requests.
|
357
|
+
However, CSVs schemas are **flat** and **static** and JSON parameters are **tree structured** and **dynamic** (but often static).
|
358
|
+
Because CSVs are flat, `RowModel`s are also flat, but they can represent various models. The `represents` interface attempts to simplify this for importing.
|
359
|
+
|
360
|
+
```ruby
|
361
|
+
class ProjectImportRowModel < ProjectRowModel
|
362
|
+
include CsvRowModel::Import
|
363
|
+
|
364
|
+
# this is shorthand for the psuedo_code:
|
365
|
+
# def project
|
366
|
+
# return if id.blank? || name.blank?
|
367
|
+
#
|
368
|
+
# # turn off memoziation with `memoize: false` option
|
369
|
+
# @project ||= __the_code_inside_the_block__
|
370
|
+
# end
|
371
|
+
#
|
372
|
+
# and the psuedo_code:
|
373
|
+
# def valid?
|
374
|
+
# super # calls ActiveModel::Errors code
|
375
|
+
# errors.delete(:project) if id.invalid? || name.invalid?
|
376
|
+
# errors.empty?
|
377
|
+
# end
|
378
|
+
represents_one :project, dependencies: [:id, :name] do
|
379
|
+
project = Project.where(id: id).first
|
380
|
+
|
381
|
+
# project not found, invalid.
|
382
|
+
return unless project
|
383
|
+
|
384
|
+
project.name = name
|
385
|
+
project
|
386
|
+
end
|
387
|
+
|
388
|
+
# same as above, but: returns [] if name.blank?
|
389
|
+
represents_many :projects, dependencies: [:name] do
|
390
|
+
Project.where(name: name)
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
# Importing is the same
|
395
|
+
import_file = CsvRowModel::Import::File.new(file_path, ProjectImportRowModel)
|
396
|
+
row_model = import_file.next
|
397
|
+
row_model.project.name # => "Some Project Name"
|
398
|
+
```
|
399
|
+
|
400
|
+
The `represents_one` method defines a dynamic `#project` method that:
|
401
|
+
|
402
|
+
1. Memoizes by default, turn off with `memoize: false` option
|
403
|
+
2. Handles dependencies:
|
404
|
+
- When any of the dependencies are `blank?`, the attribute block is not called and the representation returns `nil`.
|
405
|
+
- When any of the dependencies are `invalid?`, `row_model.errors` for dependencies are cleaned. For the example above, if `id/name` are `invalid?`, then
|
406
|
+
the `:project` key is removed from the errors, so: `row_model.errors.keys # => [:id, :name]` (applies to warnings as well)
|
407
|
+
|
408
|
+
`represents_many` is also available, except it returns `[]` when any of the dependencies are `blank?`.
|
409
|
+
|
410
|
+
### Children
|
411
|
+
|
412
|
+
Child `RowModel` relationships can also be defined:
|
413
|
+
|
414
|
+
```ruby
|
415
|
+
class UserImportRowModel
|
416
|
+
include CsvRowModel::Model
|
417
|
+
include CsvRowModel::Import
|
418
|
+
|
419
|
+
column :id, type: Integer
|
420
|
+
column :name
|
421
|
+
column :email
|
422
|
+
|
423
|
+
# uses ProjectImportRowModel#valid? to detect the child row
|
424
|
+
has_many :projects, ProjectImportRowModel
|
425
|
+
end
|
426
|
+
|
427
|
+
import_file = CsvRowModel::Import::File.new(file_path, UserImportRowModel)
|
428
|
+
row_model = import_file.next
|
429
|
+
row_model.projects # => [<ProjectImportRowModel>, ...]
|
430
|
+
```
|
431
|
+
|
435
432
|
## Dynamic columns
|
436
433
|
Dynamic columns are columns that can expand to many columns. Currently, we can only one dynamic column after all other standard columns.
|
437
434
|
The following:
|
@@ -456,6 +453,9 @@ represents this table:
|
|
456
453
|
| Mike | Jackson | Yes | Yes |
|
457
454
|
|
458
455
|
|
456
|
+
The `format_dynamic_column_header(header_model, column_name, dynamic_column_index, index_of_column, context)` can
|
457
|
+
be used to defined like `format_header`. Defined in both import and export due to headers being used for both.
|
458
|
+
|
459
459
|
### Export
|
460
460
|
Dynamic column attributes are arrays, but each item in the array is defined via singular attribute method like
|
461
461
|
normal columns:
|
@@ -494,7 +494,7 @@ class DynamicColumnImportModel < DynamicColumnModel
|
|
494
494
|
# Clean/format every dynamic_column attribute array
|
495
495
|
#
|
496
496
|
# this is an override with the default implementation
|
497
|
-
def format_dynamic_column_cells(cells, column_name)
|
497
|
+
def format_dynamic_column_cells(cells, column_name, column_index, context)
|
498
498
|
cells
|
499
499
|
end
|
500
500
|
end
|
@@ -518,7 +518,7 @@ class FileRowModel
|
|
518
518
|
row :string1
|
519
519
|
row :string2, header: 'String 2'
|
520
520
|
|
521
|
-
def self.format_header(column_name, context
|
521
|
+
def self.format_header(column_name, column_index, context)
|
522
522
|
":: - #{column_name} - ::"
|
523
523
|
end
|
524
524
|
end
|
data/csv_row_model.gemspec
CHANGED
@@ -19,5 +19,6 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_dependency "activemodel", "~> 4.2"
|
22
|
-
spec.add_dependency "active_warnings", "
|
22
|
+
spec.add_dependency "active_warnings", "~> 0.1.2"
|
23
|
+
spec.add_dependency "inherited_class_var", ">= 0.2.2"
|
23
24
|
end
|
@@ -1,15 +1,13 @@
|
|
1
1
|
module CsvRowModel
|
2
2
|
module Concerns
|
3
|
-
module
|
3
|
+
module CheckOptions
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
6
|
class_methods do
|
7
|
-
|
8
|
-
|
9
|
-
invalid_options = options.keys - default_options.keys
|
7
|
+
def check_options(options)
|
8
|
+
invalid_options = options.keys - self::VALID_OPTIONS
|
10
9
|
raise ArgumentError.new("Invalid option(s): #{invalid_options}") if invalid_options.present?
|
11
|
-
|
12
|
-
options.reverse_merge(default_options)
|
10
|
+
true
|
13
11
|
end
|
14
12
|
end
|
15
13
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'csv_row_model/export/cell'
|
2
|
+
|
1
3
|
module CsvRowModel
|
2
4
|
module Export
|
3
5
|
module Attributes
|
@@ -7,35 +9,24 @@ module CsvRowModel
|
|
7
9
|
self.column_names.each { |*args| define_attribute_method(*args) }
|
8
10
|
end
|
9
11
|
|
10
|
-
|
12
|
+
def cell_objects
|
13
|
+
@cell_objects ||= array_to_block_hash(self.class.column_names) { |column_name| Cell.new(column_name, self) }
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [Hash] a map of `column_name => formatted_attributes`
|
11
17
|
def formatted_attributes
|
12
|
-
|
18
|
+
array_to_block_hash(self.class.column_names) { |column_name| formatted_attribute(column_name) }
|
13
19
|
end
|
14
20
|
|
15
21
|
def formatted_attribute(column_name)
|
16
|
-
|
17
|
-
|
18
|
-
self.class.format_cell(
|
19
|
-
public_send(column_name),
|
20
|
-
column_name,
|
21
|
-
self.class.index(column_name),
|
22
|
-
context
|
23
|
-
)
|
22
|
+
cell_objects[column_name].try(:value)
|
24
23
|
end
|
25
24
|
|
26
|
-
|
27
|
-
|
28
|
-
array_to_block_hash(column_names) { |column_name| formatted_attribute(column_name) }
|
25
|
+
def source_attribute(column_name)
|
26
|
+
cell_objects[column_name].try(:source_value)
|
29
27
|
end
|
30
28
|
|
31
29
|
class_methods do
|
32
|
-
# Safe to override. Method applied to each cell by default
|
33
|
-
#
|
34
|
-
# @param cell [Object] the cell's value
|
35
|
-
def format_cell(cell, column_name, column_index, context={})
|
36
|
-
cell
|
37
|
-
end
|
38
|
-
|
39
30
|
protected
|
40
31
|
# See {Model#column}
|
41
32
|
def column(column_name, options={})
|
@@ -4,15 +4,15 @@ module CsvRowModel
|
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
6
|
included do
|
7
|
-
attr_reader :source_model
|
7
|
+
attr_reader :source_model
|
8
8
|
validates :source_model, presence: true
|
9
9
|
end
|
10
10
|
|
11
11
|
# @param [Model] source_model object to export to CSV
|
12
12
|
# @param [Hash] context
|
13
|
-
def initialize(source_model, context={})
|
13
|
+
def initialize(source_model=nil, context={})
|
14
14
|
@source_model = source_model
|
15
|
-
|
15
|
+
super(context: context)
|
16
16
|
end
|
17
17
|
|
18
18
|
def to_rows
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module CsvRowModel
|
2
|
+
module Export
|
3
|
+
class Cell
|
4
|
+
attr_reader :column_name, :row_model
|
5
|
+
|
6
|
+
def initialize(column_name, row_model)
|
7
|
+
@column_name = column_name
|
8
|
+
@row_model = row_model
|
9
|
+
end
|
10
|
+
|
11
|
+
def value
|
12
|
+
formatted_value
|
13
|
+
end
|
14
|
+
|
15
|
+
def formatted_value
|
16
|
+
@formatted_value ||= row_model.class.format_cell(source_value, column_name, row_model.class.index(column_name), row_model.context)
|
17
|
+
end
|
18
|
+
|
19
|
+
def source_value
|
20
|
+
row_model.public_send(column_name)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module CsvRowModel
|
2
|
+
module Export
|
3
|
+
class DynamicColumnCell < CsvRowModel::Model::DynamicColumnCell
|
4
|
+
def unformatted_value
|
5
|
+
formatted_cells
|
6
|
+
end
|
7
|
+
|
8
|
+
def formatted_cells
|
9
|
+
cells.map.with_index.map do |cell, index|
|
10
|
+
row_model.class.format_cell(cell, column_name, dynamic_column_index + index, row_model.context)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def cells
|
15
|
+
header_models.map { |header_model| call_process_cell(header_model) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def header_models
|
19
|
+
row_model.context.public_send(column_name)
|
20
|
+
end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def define_process_cell(row_model_class, column_name)
|
24
|
+
super { |header_model| header_model }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'csv_row_model/export/dynamic_column_cell'
|
2
|
+
|
1
3
|
module CsvRowModel
|
2
4
|
module Export
|
3
5
|
module DynamicColumns
|
@@ -7,6 +9,12 @@ module CsvRowModel
|
|
7
9
|
self.dynamic_column_names.each { |*args| define_dynamic_attribute_method(*args) }
|
8
10
|
end
|
9
11
|
|
12
|
+
def cell_objects
|
13
|
+
@dynamic_column_cell_objects ||= super.merge(array_to_block_hash(self.class.dynamic_column_names) do |column_name|
|
14
|
+
DynamicColumnCell.new(column_name, self)
|
15
|
+
end)
|
16
|
+
end
|
17
|
+
|
10
18
|
# @return [Array] an array of public_send(column_name) of the CSV model
|
11
19
|
def to_row
|
12
20
|
super.flatten
|
@@ -14,7 +22,7 @@ module CsvRowModel
|
|
14
22
|
|
15
23
|
# See Model::Export#formatted_attributes
|
16
24
|
def formatted_attributes
|
17
|
-
super.merge(
|
25
|
+
super.merge!(array_to_block_hash(self.class.dynamic_column_names) { |column_name| formatted_attribute(column_name) })
|
18
26
|
end
|
19
27
|
|
20
28
|
class_methods do
|
@@ -29,18 +37,8 @@ module CsvRowModel
|
|
29
37
|
# Define default attribute method for a dynamic_column
|
30
38
|
# @param column_name [Symbol] the cell's column_name
|
31
39
|
def define_dynamic_attribute_method(column_name)
|
32
|
-
define_method(column_name)
|
33
|
-
|
34
|
-
self.class.format_cell(
|
35
|
-
public_send(self.class.singular_dynamic_attribute_method_name(column_name), header_model),
|
36
|
-
column_name,
|
37
|
-
self.class.dynamic_index(column_name),
|
38
|
-
context
|
39
|
-
)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
define_method(singular_dynamic_attribute_method_name(column_name)) { |header_model| header_model }
|
40
|
+
define_method(column_name) { formatted_attribute(column_name) }
|
41
|
+
DynamicColumnCell.define_process_cell(self, column_name)
|
44
42
|
end
|
45
43
|
end
|
46
44
|
end
|