dreader 0.5.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.org ADDED
@@ -0,0 +1,794 @@
1
+ #+TITLE: Dreader
2
+ #+AUTHOR: Adolfo Villafiorita
3
+ #+STARTUP: showall
4
+
5
+ Dreader is a simple DSL built on top of [[https://github.com/roo-rb/roo][Roo]] to read and process
6
+ tabular data (CSV, LibreOffice, Excel) in a simple and structured way.
7
+
8
+ Main advantages:
9
+
10
+ 1. All code to parse input data has the same structure, simplifying
11
+ code management and understanding (convention over configuration).
12
+ 2. It favors a declarative approach, clearly identifying from which
13
+ data has to be read and in which way.
14
+ 3. Has facilities to run simulations, to debug and check code and
15
+ data.
16
+
17
+ We use Dreader for importing fairly big files (in the order of
18
+ 10K-100K records) in MIP, an ERP to manage distribution of bins to the
19
+ population. The main issues we had before using Dreader were errors
20
+ and exceptional cases in the input data. We also had to manage
21
+ several small variations in the input files (coming from different
22
+ ERPs) and Dreader helped us standardizing the input code.
23
+
24
+ The gem depends on =roo=, from which it leverages all data
25
+ reading/parsing facilities keeping its size in about 250 lines of
26
+ code.
27
+
28
+ It should be relatively easy to use; /dreader/ stands for /d/ata /r/eader.
29
+
30
+ * Installation
31
+
32
+ Add this line to your application's Gemfile:
33
+
34
+ #+BEGIN_EXAMPLE ruby
35
+ gem 'dreader'
36
+ #+END_EXAMPLE
37
+
38
+ And then execute:
39
+
40
+ #+BEGIN_EXAMPLE
41
+ $ bundle
42
+ #+END_EXAMPLE
43
+
44
+ Or install it yourself as:
45
+
46
+ #+BEGIN_EXAMPLE
47
+ $ gem install dreader
48
+ #+END_EXAMPLE
49
+
50
+
51
+ * Usage
52
+
53
+ ** Quick start
54
+
55
+ Print name and age of people from the following data:
56
+
57
+ | Name | Date of birth |
58
+ |------------------+-----------------|
59
+ | Forest Whitaker | July 15, 1961 |
60
+ | Daniel Day-Lewis | April 29, 1957 |
61
+ | Sean Penn | August 17, 1960 |
62
+
63
+ #+BEGIN_EXAMPLE ruby
64
+ require 'dreader'
65
+
66
+ class Reader < Dreader::Engine
67
+ options do
68
+ # we start reading from row 2
69
+ first_row 2
70
+ end
71
+
72
+ column :name do
73
+ doc "column A contains :name, a string; doc is optional"
74
+ colref 'A'
75
+ end
76
+
77
+ # column B contains :birthdate, a date. We can use a Hash and omit
78
+ # colref
79
+ column({ birthdate: 'B' }) do
80
+ process do |c|
81
+ Date.parse(c)
82
+ end
83
+ end
84
+
85
+ # add as many example lines as you want to show examples of good
86
+ # records these example lines are added to the template generated with
87
+ # generate_template
88
+ example { name: "John", birthday: "27/03/2020" }
89
+
90
+ # for each line, :age is computed from :birthdate
91
+ virtual_column :age do
92
+ process do |row|
93
+ birthdate = row[:birthdate][:value]
94
+ birthday = Date.new(Date.today.year, birthdate.month, birthdate.day)
95
+ today = Date.today
96
+ [0, today.year - birthdate.year - (birthday < today ? 1 : 0)].max
97
+ end
98
+ end
99
+
100
+ # this is how we process each line of the input file
101
+ mapping do |row|
102
+ r = Dreader::Util.simplify(row)
103
+ puts "#{r[:name]} is #{r[:age]} years old (born on #{r[:birthdate]})"
104
+ end
105
+ end
106
+
107
+ reader = Reader.new
108
+
109
+ # read the file
110
+ reader.read filename: "Birthdays.ods"
111
+ # compute the virtual columns
112
+ reader.virtual_columns
113
+ # run the mapping declaration
114
+ reader.process
115
+
116
+ #
117
+ # Here we can do further processing on the data
118
+ #
119
+ File.open("ages.txt", "w") do |file|
120
+ reader.table.each do |row|
121
+ unless row[:row_errors].any?
122
+ file.puts "#{row[:name][:value]} #{row[:age][:value]}"
123
+ end
124
+ end
125
+ end
126
+ #+END_EXAMPLE
127
+
128
+ ** Gentler Introduction
129
+
130
+ To write an import function with Dreader:
131
+
132
+ - Declare which is the input file and where we can find data (Sheet
133
+ and first row)
134
+ - Declare the content of columns and how to check raw data, parse data,
135
+ and check parsed data
136
+ - Add virtual columns, that is, columns computed from other values
137
+ in the row
138
+ - Specify how to process data each line. This is where you do the actual work
139
+ (for instance, if you process a file line by line) or put together data for
140
+ processing after the file has been fully read --- see the next step.
141
+
142
+ Dreader has now collected and shaped the data according to your instructions
143
+ and collected errors in the process. We are now ready to do the actual
144
+ processing:
145
+
146
+ - Do the processing
147
+
148
+ Each step is described in more details in the following sections.
149
+
150
+ *** Declare which is the input file and where we can find data
151
+
152
+ Require =dreader= and declare a class which inherits from =Dreader::Engine=:
153
+
154
+
155
+ #+BEGIN_EXAMPLE ruby
156
+ require 'dreader'
157
+
158
+ class Reader < Dreader::Engine
159
+ [...]
160
+ end
161
+ #+END_EXAMPLE
162
+
163
+ In the class specify parsing option, using the following syntax:
164
+
165
+ #+BEGIN_EXAMPLE ruby
166
+ options do
167
+ filename 'example.ods'
168
+
169
+ sheet 'Sheet 1'
170
+
171
+ first_row 1
172
+ last_row 20
173
+
174
+ # optional (this allows to integrate with other applications already
175
+ # using a logger)
176
+ logger Logger.new
177
+ logger_level Logger::INFO
178
+ end
179
+ #+END_EXAMPLE
180
+
181
+ where:
182
+
183
+ - (optional) =filename= is the file to read. If not specified, you will
184
+ have to supply a filename when loading the file (see =read=, below).
185
+ The extension determines the file type. *Use =.tsv= for tab-separated
186
+ files.*
187
+ - (optional) =first_row= is the first line to read (use =2= if your file
188
+ has a header)
189
+ - (optional) =last_row= is the last line to read. If not specified, we
190
+ will rely on =roo= to determine the last row. This is useful for
191
+ those files in which you only want to process some of the content or
192
+ contain "garbage" after the records.
193
+ - (optional) =sheet= is the sheet name or number to read from. If not
194
+ specified, the first (default) sheet is used
195
+
196
+ #+BEGIN_NOTES
197
+ You can override some of the defaults by passing a hash as argument to
198
+ the =read= function. For instance:
199
+
200
+ #+BEGIN_EXAMPLE ruby
201
+ i.read filename: another_filepath
202
+ #+END_EXAMPLE
203
+
204
+ will read data from =another_filepath=, rather than from the filename
205
+ specified in the options. This might be useful, for instance, if the
206
+ same specification has to be used for different files.
207
+ #+END_NOTES
208
+
209
+
210
+ *** Declare the content of columns and how to parse them
211
+
212
+ Declare the columns you want to read by assigning them a name and a column
213
+ reference.
214
+
215
+ There are two notations:
216
+
217
+ #+BEGIN_EXAMPLE ruby
218
+ # First notation, colref is put in the block
219
+ i.column :name do
220
+ colref 'A'
221
+ end
222
+
223
+ # Second notation, a hash is passed in the name
224
+ i.column({ name: 'A' }) do
225
+ end
226
+ #+END_EXAMPLE
227
+
228
+ The reference to a column can either be a letter or a number. First column
229
+ is ='A'= or =1=.
230
+
231
+ The =column= declaration can contain Ruby blocks:
232
+
233
+ - one or more =check_raw= block check raw data as read from the input
234
+ file. They can be used, for instance, to verify presence of a value in the
235
+ input file. *Check must return true if there are no errors; any other
236
+ value (e.g. an array of messages) is considered an error.*
237
+ - =process= can be used to transform data into something closer to the input
238
+ data required for the importing (e.g., it can be used for downcase or
239
+ strip a string)
240
+ - one or more =check= block perform a check on the =process=ed data, to check
241
+ for errors. They can be used, for instance, to check that a model built with
242
+ =process= is valid. *Check must return true if there are no errors.*
243
+
244
+ #+begin_example
245
+ i.column({ name: 'A' }) do
246
+ check_raw do |cell|
247
+ !cell.nil?
248
+ end
249
+ end
250
+ #+end_example
251
+
252
+ #+begin_quote
253
+ *If you declare more than a check block of the same type per column, use a
254
+ unique symbol to distinguish the blocks or the error messages will be
255
+ overwritten*.
256
+ #+end_quote
257
+
258
+ #+begin_example
259
+ i.column({ name: 'A' }) do
260
+ check_raw :must_be_non_nil do |cell|
261
+ !cell.nil?
262
+ end
263
+
264
+ check_raw :first_letter_must_be_a do |cell|
265
+ cell[0] == 'A'
266
+ end
267
+ end
268
+ #+end_example
269
+
270
+ #+begin_quote
271
+ =process= is always executed before =check=. If you want to check raw data
272
+ use the =check_raw= directive.
273
+ #+end_quote
274
+
275
+ #+begin_quote
276
+ There can be only one process block. *If you define more than one per
277
+ column, only the last one is executed.*
278
+ #+end_quote
279
+
280
+ #+begin_example
281
+ i.column({ name: 'A' }) do
282
+ check_raw do |cell|
283
+ # Here cell is like in the input file
284
+ end
285
+
286
+ process do |cell|
287
+ cell.upcase
288
+ end
289
+
290
+ check do |cell|
291
+ # Here cell is upcase and
292
+ end
293
+ end
294
+ #+end_example
295
+
296
+ For instance, given the tabular data:
297
+
298
+ | Name | Date of birth |
299
+ |------------------+-----------------|
300
+ | Forest Whitaker | July 15, 1961 |
301
+ | Daniel Day-Lewis | April 29, 1957 |
302
+ | Sean Penn | August 17, 1960 |
303
+
304
+ we could use the following declaration to specify the data to read:
305
+
306
+ #+BEGIN_EXAMPLE ruby
307
+ # we want to access column 1 using :name (1 and A are equivalent)
308
+ # :name should be non nil and of length greater than 0
309
+ column :name do
310
+ colref 1
311
+ check do |x|
312
+ x and x.length > 0
313
+ end
314
+ end
315
+
316
+ # we want to access column 2 (Date of birth) using :birthdate
317
+ column :birthdate do
318
+ colref 2
319
+
320
+ # make sure the column is transformed into a Date
321
+ process do |x|
322
+ Date.parse(x)
323
+ end
324
+
325
+ # check age is a date (check is invoked on the value returned
326
+ # by process)
327
+ check do |x|
328
+ x.class == Date
329
+ end
330
+ end
331
+ #+END_EXAMPLE
332
+
333
+ #+BEGIN_NOTES
334
+ 1. The column name can be anything Ruby can use as a key for a Hash,
335
+ such as, for instance, symbols, strings, and even object instances.
336
+ 2. =colref= can be a string (e.g., ='A'=) or an integer, with
337
+ 1 and "A" being the first column.
338
+ 3. *You need to declare only the columns you want to import.* For
339
+ instance, we could skip the declaration for column 1, if 'Date of
340
+ Birth' is the only data we want to import
341
+ 4. If =process= and =check= are specified, then =check= will receive the
342
+ result of invoking =process= on the cell value. This makes sense if
343
+ process is used to make the cell value more accessible to ruby code
344
+ (e.g., transforming a string into an integer).
345
+ #+END_NOTES
346
+
347
+ If there are different columns that have to be read and processed in the same
348
+ way, =columns= (notice the plural form) allows for a more compact
349
+ representation:
350
+
351
+ #+BEGIN_EXAMPLE ruby
352
+ columns { a: 'A', b: 'B' }
353
+ #+END_EXAMPLE
354
+
355
+ is equivalent to:
356
+
357
+ #+BEGIN_EXAMPLE ruby
358
+ column :a do
359
+ colref 'A'
360
+ end
361
+
362
+ column :b do
363
+ colref 'B'
364
+ end
365
+ #+END_EXAMPLE
366
+
367
+ =columns= accepts a code block, which can be used to add =process= and =check=
368
+ declarations:
369
+
370
+ #+BEGIN_EXAMPLE ruby
371
+ columns({ a: 'A', b: 'B' }) do
372
+ process do |cell|
373
+ ...
374
+ end
375
+ end
376
+ #+END_EXAMPLE
377
+
378
+ See [[file:examples/wikipedia_us_cities/us_cities_bulk_declare.rb][us_cities_bulk_declare.rb]] for an example of =columns=.
379
+
380
+ #+BEGIN_NOTES
381
+ If you use code blocks, don't forget to put in parentheses the
382
+ column mapping, or the Ruby parser won't be able to distinguish the
383
+ hash from the code block.
384
+ #+END_NOTES
385
+
386
+
387
+ *** Add virtual columns
388
+
389
+ Sometimes it is convenient to aggregate or otherwise manipulate the data
390
+ read from each row, before doing the actual processing.
391
+
392
+ For instance, we might have a table with dates of birth, while we are
393
+ really interested in the age of people.
394
+
395
+ In such cases, we can use virtual column. A *virtual column* allows
396
+ one to add a column to the data read, computed using the values of
397
+ other cells in the same row.
398
+
399
+ The following declaration adds an =age= column to each row of the data
400
+ read from the previous example:
401
+
402
+ #+BEGIN_EXAMPLE ruby
403
+ virtual_column :age do
404
+ process do |row|
405
+ # the function `compute_birthday` has to be defined
406
+ compute_birthday(row[:birthdate])
407
+ end
408
+ end
409
+ #+END_EXAMPLE
410
+
411
+ Virtual columns are, of course, available to the =mapping= directive
412
+ (see below).
413
+
414
+
415
+ *** Specify how to process each line
416
+
417
+ The =mapping= directive specifies what to do with each line read. The
418
+ =mapping= declaration takes an arbitrary piece of ruby code, which can
419
+ reference the fields using the column names we declared.
420
+
421
+ For instance the following code gets the value of column =:name=, the
422
+ value of column =:age= and prints them to standard output
423
+
424
+ #+BEGIN_EXAMPLE ruby
425
+ mapping do |row|
426
+ puts "#{row[:name][:value]} is #{row[:age][:value]} years old"
427
+ end
428
+ #+END_EXAMPLE
429
+
430
+ The data read from each row of our input data is stored in a hash. The hash
431
+ uses column names as the primary key and stores the values in the =:value=
432
+ key.
433
+
434
+
435
+ *** Process data
436
+
437
+ If =mapping= does not work for your data processing activities (e.g., you need
438
+ to make elaborations on data which span different rows), you can add your own
439
+ code after the =process= directive.
440
+
441
+ A typical scenario works as follows:
442
+
443
+ 1. Instantiate the class: ~i = Reader.new~
444
+
445
+ 1. Use =i.read= or =i.load= (synonyms), to read all data.
446
+
447
+ #+BEGIN_EXAMPLE ruby
448
+ i.read
449
+ #+END_EXAMPLE
450
+
451
+ 2. Use =errors= to see whether any of the check functions failed:
452
+
453
+ #+BEGIN_EXAMPLE ruby
454
+ array_of_hashes = i.errors
455
+ array_of_hashes.each do |error_hash|
456
+ puts error_hash
457
+ end
458
+ #+END_EXAMPLE
459
+
460
+ 3. Use =virtual_columns= to generate the virtual columns:
461
+
462
+ #+BEGIN_EXAMPLE ruby
463
+ i.virtual_columns
464
+ #+END_EXAMPLE
465
+
466
+ (Optionally: check again for errors.)
467
+
468
+ 4. Use the =process= function to execute the =mapping=
469
+ directive on each line read from the file.
470
+
471
+ #+BEGIN_EXAMPLE ruby
472
+ i.process
473
+ #+END_EXAMPLE
474
+
475
+ (Optionally: check again for errors.)
476
+
477
+ 5. Add your own code to process data. Use the =table= function to access data.
478
+
479
+ Look in the examples directory for further details and a couple of
480
+ working examples.
481
+
482
+
483
+ *** Managing Errors
484
+
485
+ **** Finding errors in input data
486
+
487
+ Dreader collects errors in three specific ways:
488
+
489
+ 1. In each column specification, using =check_raw= and =check=. This allows
490
+ to check each field for errors (e.g., a =nil= value in a cell)
491
+ 2. In virtual columns, using =check_raw= and =check=. This allows to perform
492
+ more complex checks by putting together all the values read from a row
493
+ (e.g., =to_date= occurs before =from_date=)
494
+
495
+ The following, for instance checks that name or surname have a valid value:
496
+
497
+ #+begin_example ruby
498
+ virtual_column :global_check do
499
+ doc "Name or Surname must exist"
500
+ check :name_or_surname_must_be_defined do |row|
501
+ row[:name] || row[:surname]
502
+ end
503
+ end
504
+ #+end_example
505
+
506
+ If you prefer, you can also define a virtual column that contains the value of
507
+ the check:
508
+
509
+ #+begin_example ruby
510
+ virtual_column :name_or_surname_exist do
511
+ doc "Name or Surname must exist"
512
+ process do |row|
513
+ row[:name] || row[:surname]
514
+ end
515
+ end
516
+ #+end_example
517
+
518
+ You can then act in the mapping directive according to value returned by the
519
+ virtual column:
520
+
521
+ #+begin_example ruby
522
+ mapping do |row|
523
+ unless row[:global_check][:value] == false
524
+ [...]
525
+ end
526
+ #+end_example
527
+
528
+ **** Managing Errors
529
+
530
+ You can check for errors in two different ways:
531
+
532
+ The first is in the =mapping= directive, where can check whether some checks for
533
+ the =row= failed, by:
534
+
535
+ 1. checking from the =:error= boolean key associated to each column, that is:
536
+
537
+ =row[<column_name>][:error]=
538
+
539
+ 2. looking at the value of the =:row_errors= key, which contains all error messages
540
+ generated for the row:
541
+
542
+ =row[:row_errors]=
543
+
544
+ 3. After the processing, by using the method =errors=, which lists all the errors.
545
+
546
+ The utility function =Dreader::Util.errors= takes as input the errors generated by
547
+ Dreader and extract those of a specific row and, optionally column:
548
+
549
+ #+begin_example ruby
550
+ # get all the errors at line 2
551
+ Dreader::Util.errors i.errors, 2
552
+
553
+ # get all the errors at line 2, column 'C'
554
+ Dreader::Util.errors i.errors, 2, 3
555
+ #+end_example
556
+
557
+
558
+ * Generating a Template from the specification
559
+
560
+ From version 0.6.0 =dreader= allows to generate a template starting from the
561
+ specification.
562
+
563
+ The template is generated by the following call:
564
+
565
+ #+begin_example ruby
566
+ generate_template template_filename: "template.xlsx"
567
+ #+end_example
568
+
569
+ (The =template_filename= directive can also be specified in the =options=
570
+ section).
571
+
572
+ The template contains the following rows:
573
+
574
+ - The first row contains the names of the columns, as specified in the
575
+ =columns= declarations and made into a human readable form.
576
+ - The second row contains the doc strings of the columns, if set.
577
+ - The remaining rows contain the example records added with the
578
+ =example= directive
579
+
580
+ The position of the first row is determined by the value of =first_row=, that
581
+ is, if =first_row= is 2 (content starts from the second row), the header row
582
+ is put in row 1.
583
+
584
+ Only Excel is supported, at the moment.
585
+
586
+ An example of template generation can be found in the Examples.
587
+
588
+ ** Digging deeper
589
+
590
+ If you need to perform elaborations which cannot be performed row by
591
+ row you can access all data, with the =table= method:
592
+
593
+ #+BEGIN_EXAMPLE ruby
594
+ i.read
595
+ i.table
596
+ #+END_EXAMPLE
597
+
598
+ The function =i.table= returns an array of Hashes. Each element of
599
+ the array is a row of the input file. Each element/row has the
600
+ following structure:
601
+
602
+ #+BEGIN_EXAMPLE ruby
603
+ {
604
+ col_name1: { <info about col_name_1 in row_j> },
605
+ [...]
606
+ col_nameN: { <info about col_name_N in row_j> },
607
+ row_errors: [ <errors associated to row> ],
608
+ row_number: <row number>
609
+ }
610
+ #+END_EXAMPLE
611
+
612
+ where =col_name1=, ..., =col_nameN= are the names you have assigned to
613
+ the columns and the information stored for each cell is the
614
+ following:
615
+
616
+ #+BEGIN_EXAMPLE ruby
617
+ {
618
+ value: ..., # the result of calling process on the cell
619
+ row_number: ..., # the row number
620
+ col_number: ..., # the column number
621
+ error: ... # the result of calling check on the cell processed value
622
+ }
623
+ #+END_EXAMPLE
624
+
625
+ (Note that virtual columns only store =value= and a Boolean =virtual=,
626
+ which is always =true=.)
627
+
628
+ Thus, for instance, given the example above returns:
629
+
630
+ #+BEGIN_EXAMPLE ruby
631
+ i.table
632
+ [
633
+ {
634
+ name: { value: "John", row_number: 1, col_number: 1, errors: nil },
635
+ age: { value: 30, row_number: 1, col_number: 2, errors: nil }
636
+ },
637
+ {
638
+ name: { value: "Jane", row_number: 2, col_number: 1, errors: nil },
639
+ age: { value: 31, row_number: 2, col_number: 2, errors: nil }
640
+ }
641
+ ]
642
+ #+END_EXAMPLE
643
+
644
+
645
+ * Simplifying the hash with the data read
646
+
647
+ The =Dreader::Util= class provides some functions to simplify the
648
+ hashes built by =dreader=. This is useful to simplify the code you
649
+ write and to genereate hashes you can pass, for instance, to
650
+ ActiveRecord creators.
651
+
652
+ ** Simplify removes everything but the values
653
+
654
+ =Dreader::Util.simplify hash= removes all information but the value
655
+ and making the value accessible directly from the name of the column.
656
+
657
+ #+BEGIN_EXAMPLE ruby
658
+ i.table[0]
659
+ { name: { value: "John", row_number: 1, col_number: 1, errors: nil },
660
+ age: { value: 30, row_number: 1, col_number: 2, errors: nil } }
661
+
662
+ Dreader::Util.simplify i.table[0]
663
+ { name: "John", age: 30 }
664
+ #+END_EXAMPLE
665
+
666
+ *As an additional bonus, it removes the keys =row_number= and =row_errors=,
667
+ which are not part of the data read, in the first place.*
668
+
669
+ ** Slice and Clean select columns
670
+
671
+ =Dreader::Util.slice hash, keys= and =Dreader::Util.clean hash, keys=,
672
+ where =keys= is an arrays of keys, are respectively used to select or
673
+ remove some keys from the hash returned by Dreader. (Notice that the
674
+ Ruby Hash class already provides similar methods.)
675
+
676
+ #+BEGIN_EXAMPLE ruby
677
+ i.table[0]
678
+ { name: { value: "John", row_number: 1, col_number: 1, errors: nil },
679
+ age: { value: 30, row_number: 1, col_number: 2, errors: nil }}
680
+
681
+ Dreader::Util.slice i.table[0], :name
682
+ { name: { value: "John", row_number: 1, col_number: 1, errors: nil}
683
+
684
+ Dreader::Util.clean i.table[0], :name
685
+ { age: { value: 30, row_number: 1, col_number: 2, errors: nil }
686
+ #+END_EXAMPLE
687
+
688
+ The methods =slice= and =clean= are more useful when used in
689
+ conjuction with =simplify=:
690
+
691
+ #+BEGIN_EXAMPLE ruby
692
+ hash = Dreader::Util.simplify i.table[0]
693
+ { name: "John", age: 30 }
694
+
695
+ Dreader::Util.slice hash, [:age]
696
+ { age: 30 }
697
+
698
+ Dreader::Util.clean hash, [:age]
699
+ { name: "John" }
700
+ #+END_EXAMPLE
701
+
702
+ The output produced by =slice= and =simplify= is a hash which can be used to
703
+ create an =ActiveRecord= object.
704
+
705
+ ** Better Integration with ActiveRecord
706
+
707
+ Finally, the =Dreader::Util.restructure= method helps building hashes
708
+ to create [[http://api.rubyonrails.org/classes/ActiveModel/Model.html][ActiveModel]] objects with nested attributes:
709
+
710
+ #+BEGIN_EXAMPLE ruby
711
+ hash = {name: "John", surname: "Doe", address: "Unknown", city: "NY" }
712
+
713
+ Dreader::Util.restructure hash, [:name, :surname], :address_attributes, [:address, :city]
714
+ {name: "John", surname: "Doe", address_attributes: {address: "Unknonw", city: "NY"}}
715
+ #+END_EXAMPLE
716
+
717
+
718
+ * Debugging your specification
719
+
720
+ The =debug= function prints the current configuration, reads some
721
+ records from the input file(s), and shows the records read:
722
+
723
+ #+BEGIN_EXAMPLE ruby
724
+ i.debug
725
+ i.debug n: 40 # read 40 lines (from first_row)
726
+ i.debug n: 40, filename: filepath # like above, but read from filepath
727
+ #+END_EXAMPLE
728
+
729
+ By default =debug= invokes the =check_raw=, =process=, and =check=
730
+ directives. Pass the following options, if you want to disable this behavior;
731
+ this might be useful, for instance, if you intend to check only what data is
732
+ read:
733
+
734
+ #+BEGIN_EXAMPLE ruby
735
+ i.debug process: false, check: false
736
+ #+END_EXAMPLE
737
+
738
+ Notice that =check= implies =process=, since =check= is invoked on the
739
+ output of the =process= directive.`
740
+
741
+ If you prefer, in alternative to =debug= you can also use configuration
742
+ variables (but then you need to change the configuration according to the
743
+ environment):
744
+
745
+ #+begin_example ruby
746
+ i.options do
747
+ debug true
748
+ end
749
+ #+end_example
750
+
751
+
752
+ * Changelog
753
+
754
+ See [[file:CHANGELOG.ORG][CHANGELOG]].
755
+
756
+ * Known Limitations
757
+
758
+ At the moment:
759
+
760
+ - it is not possible to specify column references using header names
761
+ (like Roo does).
762
+ - it is not possible to pass options to the file readers. As a
763
+ consequence tab-separated files must have the =.tsv= extension or
764
+ they will not be parsed correctly
765
+ - some more testing wouldn't hurt.
766
+
767
+ * Known Bugs
768
+
769
+ Some known bugs and an unknown number of unknown bugs.
770
+
771
+ (See the open issues for the known bugs.)
772
+
773
+ * Development
774
+
775
+ After checking out the repo, run =bin/setup= to install dependencies.
776
+ You can also run =bin/console= for an interactive prompt that will
777
+ allow you to experiment.
778
+
779
+ To install this gem onto your local machine, run =bundle exec rake
780
+ install=. To release a new version, update the version number in
781
+ =version.rb=, and then run =bundle exec rake release=, which will
782
+ create a git tag for the version, push git commits and tags, and push
783
+ the =.gem= file to [[https://rubygems.org][rubygems.org]].
784
+
785
+ * Contributing
786
+
787
+ Bug reports and pull requests are welcome.
788
+
789
+ You need to get in touch with me by email, till I figure how to enable
790
+ it in Gitea.
791
+
792
+ * License
793
+
794
+ [[https://opensource.org/licenses/MIT][MIT License]].