You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
## Joining the rows of multiple sources that may have different fields
8
+
9
+
<!-- toc -->
10
+
11
+
-[Debugging with `pry`](#debugging-with-pry)
12
+
-[Multi-source jobs with CSV destination and sources don't have all the same fields](#multi-source-jobs-with-csv-destination-and-sources-dont-have-all-the-same-fields)
13
+
-[Using transform(s) within another transform](#using-transforms-within-another-transform)
14
+
-[Using transforms in job definitions](#using-transforms-in-job-definitions)
15
+
-[Calling a job with parameters](#calling-a-job-with-parameters)
-[Running jobs, and checking `srcrows` and `outrows` counts from client project code](#running-jobs-and-checking-srcrows-and-outrows-counts-from-client-project-code)
18
+
19
+
<!-- tocstop -->
20
+
21
+
## Debugging with `pry`
22
+
23
+
In most parts of a kiba-extend project, you can just put a `binding.pry` breakpoint anywhere in the code and it will work.
24
+
25
+
There are two exceptions to this: inside `Kiba.job_segment` blocks, and in any code where the breakpoint context interacts with the `dry-rb` gems that are used to build kiba-extend's registry and config functionality.
26
+
27
+
### In `Kiba.job_segment` blocks
28
+
29
+
Wrap your breakpoint in an inline transform:
30
+
31
+
~~~ruby
32
+
transform do |row|
33
+
binding.pry
34
+
row
35
+
end
36
+
~~~
37
+
38
+
Note that you can set the breakpoint conditionally, if you want to see what's going on at this point in the job for rows with certain characteristics:
39
+
40
+
~~~ruby
41
+
transform do |row|
42
+
binding.pry if row[:id]&.start_with?("A")
43
+
row
44
+
end
45
+
~~~
46
+
47
+
### Where `binding.pry` conflicts with `dry-rb` gem code or other code
48
+
49
+
It can be hard to predict when/where this is going to happen, but sometimes entering a `binding.pry` breakpoint in your code can result in the following kind of error:
50
+
51
+
~~~
52
+
RuntimeError:
53
+
Cannot create Binding object for non-Ruby caller
54
+
~~~
55
+
56
+
Basically, any error message about [Binding objects](https://ruby-doc.org/3.4.1/Binding.html) means the breakpoint is getting misinterpreted based on other code around it.
57
+
58
+
The fix is easy: change your breakpoint to `Kernel.binding.pry`. This ensures `binding` is interpreted as the default method available on all Ruby objects via [Kernel](https://ruby-doc.org/3.4.1/Kernel.html).
59
+
60
+
## Multi-source jobs with CSV destination and sources don't have all the same fields
9
61
10
62
**Works for `Kiba::Extend::Destinations::CSV` destinations only**
11
63
12
64
If you have multiple sources for a job, writing to a CSV destination will fail if all rows in all sources do not have exactly the same fields.
13
65
14
66
Especially when joining data from many tables, manually ensuring columns stay in sync across all sources is very tedious, especially as you are developing a set of jobs.
15
67
16
-
As of v2.7.0, {Kiba::Extend::Utils::MultiSourceNormalizer} and {Kiba::Extend::Jobs::MultiSourcePrepJob} are added to support handling this automagically.
68
+
**Recommended solution:**As of 4.0.0, you can fix this by adding `transform Clean::EnsureConsistentFields` to the end of tranform logic for multi-source jobs that output to CSV.
17
69
18
-
See {Kiba::Extend::Utils::MultiSourceNormalizer} for full usage docs.
19
-
20
-
See [name compilation jobs in Kiba::TMS](https://github.com/lyrasis/kiba-tms/blob/main/lib/kiba/tms/jobs/in_between/name_compilation.rb) for working example of use.
70
+
**Legacy solution (not recommended):**`Kiba::Extend::Utils::MultiSourceNormalizer` and `Kiba::Extend::Jobs::MultiSourcePrepJob` were introduced in v2.7.0 to address this issue, but will eventually be deprecated, as the `EnsureConsistentFields` transform is much easier to set up and use.
21
71
22
72
## Using transform(s) within another transform
23
73
24
74
### Aliasing/renaming a transform
25
75
26
76
This pattern is used with argument forwarding to deprecate/rename some transforms in kiba-extend, as shown below:
27
77
28
-
~~~
78
+
~~~ruby
29
79
classTransformer
30
80
definitialize(...)
31
81
@xform=MyOtherTransformer.new(...)
32
82
end
33
83
34
-
# @private
35
84
defprocess(row)
36
85
xform.process(row)
37
86
row
@@ -47,19 +96,18 @@ end
47
96
48
97
It can also be used in order to compose additional behavior in another transform as shown below:
49
98
50
-
~~~
99
+
~~~ruby
51
100
classNewTransformer
52
101
definitialize(param1:, param2:)
53
102
@param1= param1
54
103
@param2= param2
55
104
@xform=ExistingTransformer.new(opt1::something)
56
105
end
57
106
58
-
# @private
59
107
defprocess(row)
60
-
# do stuff to row
61
-
xform.process(row)
62
-
# do more stuff to row
108
+
row[:field1] = param1
109
+
xform.process(row)# calls the other transformer on the row
110
+
row[:field2] = param2
63
111
row
64
112
end
65
113
@@ -69,15 +117,17 @@ class NewTransformer
69
117
end
70
118
~~~
71
119
72
-
See the code for {Kiba::Extend::Transforms::Rename::Fields} for a simple example of embedding another transform to compose transformation logic.
120
+
See the code for `Kiba::Extend::Transforms::Rename::Fields` for a simple example of embedding another transform to compose transformation logic.
73
121
74
-
See {Kiba::Extend::Transforms::Collapse::FieldsToRepeatableFieldGroup} for a complex example, involving many other transforms.
122
+
See `Kiba::Extend::Transforms::Collapse::FieldsToRepeatableFieldGroup` for a complex example, involving many other transforms.
75
123
76
124
### Chaining multiple transforms in another transform
77
125
126
+
I often use this if I need to define a single, client-specific data cleanup transform class to be run from within kiba-tms, kiba-pastperfect_we, etc.
127
+
78
128
You can do:
79
129
80
-
~~~
130
+
~~~ruby
81
131
classNewTransformer
82
132
definitialize(...)
83
133
@xforms= [
@@ -105,33 +155,62 @@ All of the above patterns should work with normal transforms---those that proces
105
155
106
156
Be careful including the following types of transforms in any of the above patterns:
107
157
108
-
***Transforms that sometimes return the row and sometimes return nil.** Example: all the {Kiba::Extend::Transforms::FilterRows} transforms.
109
-
***Transforms that can output more than one row from a given input row.** The `:process` method of such transforms will `yield` rows and return `nil`. Example: {Kiba::Extend::Transforms::Explode::RowsFromMultivalField}
110
-
***Transforms that work on multiple rows (or the whole table) at a time** Such transforms will have a `:close` method that returns or yields rows. The `:process` method of such transforms will generally push rows to an accumulator Array or Hash defined as a class instance variable, and return `nil`. The `:close` method typically operates on the contents of the accumulator once all rows have been pushed into it. Example: {Kiba::Extend::Transforms::Deduplicate::Table}
158
+
***Transforms that sometimes return the row and sometimes return nil.** Example: all the `Kiba::Extend::Transforms::FilterRows` transforms.
159
+
***Transforms that can output more than one row from a given input row.** The `:process` method of such transforms will `yield` rows and return `nil`. Example: `Kiba::Extend::Transforms::Explode::RowsFromMultivalField`
160
+
***Transforms that work on multiple rows (or the whole table) at a time** Such transforms will have a `:close` method that returns or yields rows. The `:process` method of such transforms will generally push rows to an accumulator Array or Hash defined as a class instance variable, and return `nil`. The `:close` method typically operates on the contents of the accumulator once all rows have been pushed into it. Example: `Kiba::Extend::Transforms::Deduplicate::Table`
111
161
162
+
These might work ok, but I haven't done enough testing to verify they are actually safe. Try it and see. If they work, make a PR to update this documentation!
112
163
113
164
## Using transforms in job definitions
114
165
115
166
The following code snippets are equivalent.
116
167
117
168
This one relies on the domain specific language (DSL) "magic" defined in kiba:
This one uses plain Ruby to set up the transform class and calls its `:process` method on each row:
176
+
This one uses plain Ruby to set up two differently configured `Merge::ConstantValue` transform classes in the context of the job's transform logic. It then uses kiba's inline block transform functionality to call the appropriate transform's `:process` method on each row, depending on whether the :id field in the row starts with the given string:
row # A block transform must return `nil` or a row.
209
+
end
131
210
end
132
211
~~~
133
212
134
-
The second one might be useful in situations when you are trying to set things up more flexibly.
213
+
However, you might run into ways where you need to use transforms more flexibly, and this basic idea might help.
135
214
136
215
## Calling a job with parameters
137
216
@@ -160,3 +239,5 @@ puts "Some records omitted" if job.outrows < job.srcrows
160
239
This assumes `:prep__objects` is registered as a job.
161
240
162
241
This is being used in the publicly available `kiba-tms` project, in the auto-config generation and to-do check processes. [Examples](https://github.com/lyrasis/kiba-tms/search?q=Kiba%3A%3AExtend%3A%3ACommand%3A%3ARun.job)
242
+
243
+
Since 4.0.0, you can use [the `Kiba::Extend::Job.output?` method](https://lyrasis.github.io/kiba-extend/Kiba/Extend/Job.html#output%3F-class_method) to check that a job writes a file with actual data rows in it. This is helpful if you start writing dynamic/reusable code for projects that might not all have the exact same data. You can conditionally run some parts of a pipeline, only if the data is present.
0 commit comments