csv_row_model 0.4.1 → 1.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|