bumblebee 2.1.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -2
- data/.travis.yml +1 -1
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +1 -1
- data/README.md +172 -138
- data/bin/console +2 -6
- data/lib/bumblebee/bumblebee.rb +16 -62
- data/lib/bumblebee/column.rb +41 -63
- data/lib/bumblebee/column_dsl.rb +38 -0
- data/lib/bumblebee/column_set.rb +83 -0
- data/lib/bumblebee/converter.rb +97 -0
- data/lib/bumblebee/core_ext/hash.rb +21 -0
- data/lib/bumblebee/mutator.rb +54 -0
- data/lib/bumblebee/null_converter.rb +17 -0
- data/lib/bumblebee/object_interface.rb +66 -0
- data/lib/bumblebee/simple_converter.rb +109 -0
- data/lib/bumblebee/template.rb +23 -37
- data/lib/bumblebee/version.rb +1 -1
- data/spec/bumblebee/simple_converter_spec.rb +29 -0
- data/spec/bumblebee/template_spec.rb +151 -36
- data/spec/examples/converter_test_case.rb +113 -0
- data/spec/examples/person_template.rb +66 -0
- data/spec/examples/simple_object.rb +27 -0
- data/spec/fixtures/people/data.csv +3 -0
- data/spec/fixtures/people/data.yml +46 -0
- data/spec/fixtures/registrations/columns.yml +35 -0
- data/spec/fixtures/registrations/data.csv +3 -0
- data/spec/fixtures/registrations/data.yml +26 -0
- data/spec/fixtures/simple/columns.yml +4 -0
- data/spec/fixtures/{custom_readme_example.csv → simple/data.csv} +1 -1
- data/spec/fixtures/simple/data.yml +12 -0
- data/spec/spec_helper.rb +29 -3
- metadata +34 -10
- data/spec/bumblebee/bumblebee_spec.rb +0 -167
- data/spec/bumblebee/column_spec.rb +0 -213
- data/spec/fixtures/simple_readme_example.csv +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c3b95d42104a7a73d3af191aa0ab43a3aaadf03e6ab6bb612c07bd741a3cbd76
|
4
|
+
data.tar.gz: 7b4ef3e1738d81605786d36a1be4dedb6d9233e4709a7ad189a0552ca0a6cb21
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bc0eb85df6dd1c2562b92d485f407bdf7a3a5ea3a0b2315a66d9a5a314ffe93a05d2772df1b149af4744cdcee4b6d2ace0c3eafb4aba573f757d67eed9ab9bdc
|
7
|
+
data.tar.gz: ebbf2f57ee8c5f3b061d990478f7a1655dfe025e32e417c1cfce19f23373a6222a3d3cc23720e24bb51e7a8f3487bf29df90f69f357e216aab7662eaf592c766
|
data/.rubocop.yml
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
# 3.0.0 (March 7th, 2019)
|
2
|
+
|
3
|
+
**Breaking Changes: Public API Re-Write**
|
4
|
+
|
5
|
+
* Major lessons were learnt during the implementation of versions one and two. Version three seeks to iron out the DSL to make it more intuitive and usable. Therefore, the entire DSL has been re-written from the ground up with a whole new API.
|
6
|
+
|
1
7
|
# 2.1.0 (March 4th, 2019)
|
2
8
|
|
3
9
|
* Added two domain-specific language options for configuring Column objects through Template: subclass Template class or pass in block. This is optional and is backwards compatible.
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -33,18 +33,13 @@ id | name | dob | phone
|
|
33
33
|
Using the following column configuration:
|
34
34
|
|
35
35
|
````ruby
|
36
|
-
columns = [
|
37
|
-
{ field: :id },
|
38
|
-
{ field: :name },
|
39
|
-
{ field: :dob },
|
40
|
-
{ field: :phone }
|
41
|
-
]
|
36
|
+
columns = %i[id name dob phone]
|
42
37
|
````
|
43
38
|
|
44
39
|
We could parse this data and turn it into hashes:
|
45
40
|
|
46
41
|
````ruby
|
47
|
-
objects = Bumblebee.
|
42
|
+
objects = Bumblebee::Template.new(columns: columns).parse(data)
|
48
43
|
````
|
49
44
|
|
50
45
|
Then `objects` is this array of hashes:
|
@@ -72,12 +67,12 @@ ID # | First Name | Date of Birth | Phone #
|
|
72
67
|
Then we can explicitly map those as:
|
73
68
|
|
74
69
|
````ruby
|
75
|
-
columns =
|
76
|
-
{
|
77
|
-
{
|
78
|
-
|
79
|
-
{
|
80
|
-
|
70
|
+
columns = {
|
71
|
+
'ID #': { property: :id },
|
72
|
+
'First Name': { property: :name },
|
73
|
+
'Date of Birth': { property: :dob },
|
74
|
+
'Phone #': { property: :phone }
|
75
|
+
}
|
81
76
|
````
|
82
77
|
|
83
78
|
### Nested Objects
|
@@ -118,183 +113,220 @@ ID # | First Name | Date of Birth | Phone #
|
|
118
113
|
Using the following column config:
|
119
114
|
|
120
115
|
````ruby
|
121
|
-
columns =
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
116
|
+
columns = {
|
117
|
+
'ID #': {
|
118
|
+
property: :id
|
119
|
+
},
|
120
|
+
'First Name': {
|
121
|
+
property: :first,
|
122
|
+
through: :name
|
123
|
+
},
|
124
|
+
'Date of Birth': {
|
125
|
+
property: :dob,
|
126
|
+
through: :demo
|
127
|
+
},
|
128
|
+
'Phone #': {
|
129
|
+
property: :phone,
|
130
|
+
through: :contact
|
131
|
+
}
|
126
132
|
]
|
127
133
|
````
|
128
134
|
|
129
135
|
And executing the following:
|
130
136
|
|
131
137
|
````ruby
|
132
|
-
csv = Bumblebee.
|
138
|
+
csv = Bumblebee::Template.new(columns: columns).generate(objects)
|
133
139
|
````
|
134
140
|
|
135
141
|
The above columns config would work both ways, so if we received the CSV, we could parse it to an array of nested hashes. Unfortunately, for now, we cannot do better than an array of nested hashes.
|
136
142
|
|
137
|
-
### Custom
|
143
|
+
### Custom Formatting
|
138
144
|
|
139
|
-
You can also pass in functions that can do the value formatting. For example:
|
145
|
+
You can also pass in built-in or custom functions that can do the value formatting. For example:
|
140
146
|
|
141
147
|
````ruby
|
142
|
-
columns =
|
143
|
-
{
|
144
|
-
|
145
|
-
|
148
|
+
columns = {
|
149
|
+
'ID #': {
|
150
|
+
property: :id,
|
151
|
+
to_object: :integer
|
146
152
|
},
|
147
|
-
{
|
148
|
-
|
149
|
-
|
150
|
-
to_csv:
|
153
|
+
'First Name': {
|
154
|
+
property: :first,
|
155
|
+
through: :name,
|
156
|
+
to_csv: ->(v) { v.to_s.upcase }
|
151
157
|
},
|
152
|
-
{
|
153
|
-
|
154
|
-
|
155
|
-
|
158
|
+
'Date of Birth': {
|
159
|
+
property: :dob,
|
160
|
+
through: :demo,
|
161
|
+
to_object: { type: :date, nullable: true }
|
156
162
|
},
|
157
|
-
{
|
158
|
-
|
159
|
-
|
160
|
-
to_csv: [:contact, :phone]
|
163
|
+
'Phone #': {
|
164
|
+
property: :phone,
|
165
|
+
through: :contact
|
161
166
|
}
|
162
|
-
|
167
|
+
}
|
163
168
|
````
|
164
169
|
|
165
|
-
would ensure
|
170
|
+
would ensure:
|
166
171
|
|
167
|
-
|
172
|
+
* id is an integer data type when parsed
|
173
|
+
* the CSV has only upper-case `First Name` values
|
174
|
+
* dob is a date data type when parsed
|
168
175
|
|
169
|
-
|
176
|
+
Other formatting functions that can be used for to_object and/or to_csv:
|
170
177
|
|
171
|
-
|
178
|
+
* bigdecimal: converts to BigDecimal (nullable, non-nullable default is 0)
|
179
|
+
* boolean: converts to flexible boolean (nullable; non-nullable default is false). 1,t,true,y,yes all parse to true, 0,f,false,n,no all parse to false
|
180
|
+
* date: converts to Date (nullable; non-nullable default is 1900-01-01)
|
181
|
+
* integer: converts to Fixnum (nullable, non-nullable default is 0)
|
182
|
+
* join: array is joined by separator option (defaults to comma)
|
183
|
+
* float: converts to Float (nullable, non-nullable default is 0.0f)
|
184
|
+
* function: custom lambda function (input is the resolved value, output of lambda will be used resolved value)
|
185
|
+
* pluck_join: map the sub-property (sub_property option) then join them with separator (defaults to comma)
|
186
|
+
* pluck_split: array is split by separator option (defaults to comma), then new object (object_class option) is created and sub-property (sub_property option) set.
|
187
|
+
* split: array is split by separator option (defaults to comma)
|
188
|
+
* string: calls to_s method on the value
|
172
189
|
|
173
|
-
|
174
|
-
---- | ---------- | ------------- | ------------
|
175
|
-
1 | Matt | 1901-02-03 | 555-555-5555
|
176
|
-
2 | Nick | 1921-09-03 | 444-444-4444
|
177
|
-
3 | Sam | 1932-12-12 | 333-333-3333
|
190
|
+
### Pluck Join / Pluck Split Explained
|
178
191
|
|
179
|
-
|
192
|
+
Pluck join and pluck split comes in handy when you have an array of objects and would like to:
|
193
|
+
|
194
|
+
* map one value from each object and join it (in order to output in a CSV)
|
195
|
+
* take a string value, split it, the map each value to a new object (in order to parse as objects)
|
196
|
+
|
197
|
+
Take this input and configuration for example:
|
180
198
|
|
181
199
|
````ruby
|
182
|
-
|
200
|
+
objects = [
|
183
201
|
{
|
184
|
-
|
185
|
-
|
186
|
-
|
202
|
+
id: 1,
|
203
|
+
name: { first: 'Matt' },
|
204
|
+
demo: { dob: '1901-02-03' },
|
205
|
+
contact: { phone: '555-555-5555' },
|
206
|
+
children: [ { id: 9, name: 'Spunky' }, { id: 10, name: 'Dunker' } ]
|
187
207
|
},
|
188
208
|
{
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
{ field: :demo,
|
195
|
-
header: 'Date of Birth',
|
196
|
-
to_csv: %i[demo dob],
|
197
|
-
to_object: ->(o) { { dob: o['Date of Birth'] } }
|
209
|
+
id: 2,
|
210
|
+
name: { first: 'Nick' },
|
211
|
+
demo: { dob: '1921-09-03' },
|
212
|
+
contact: { phone: '444-444-4444' },
|
213
|
+
children: [ { id: 11, name: 'Bonzi' }, { id: 12, name: 'Buddy' } ]
|
198
214
|
},
|
199
|
-
{
|
200
|
-
|
201
|
-
|
202
|
-
|
215
|
+
{
|
216
|
+
id: 3,
|
217
|
+
name: { first: 'Sam' },
|
218
|
+
demo: { dob: '1932-12-12' },
|
219
|
+
contact: { phone: '333-333-3333' }
|
203
220
|
}
|
204
221
|
]
|
222
|
+
|
223
|
+
columns = {
|
224
|
+
'ID #': {
|
225
|
+
property: :id,
|
226
|
+
to_object: :integer
|
227
|
+
},
|
228
|
+
'Children ID #s': {
|
229
|
+
property: :children,
|
230
|
+
to_csv: { type: :pluck_join, separator: ';', sub_property: :id },
|
231
|
+
to_object: { type: :pluck_split, separator: ';', sub_property: :id },
|
232
|
+
}
|
233
|
+
}
|
234
|
+
````
|
235
|
+
|
236
|
+
Generating a CSV:
|
237
|
+
|
238
|
+
````ruby
|
239
|
+
csv = Bumblebee::Template.new(columns: columns).generate(objects)
|
205
240
|
````
|
206
241
|
|
207
|
-
|
242
|
+
would output:
|
243
|
+
|
244
|
+
ID # | Children ID #s
|
245
|
+
---- | --------------
|
246
|
+
1 | 9;10
|
247
|
+
2 | 11;12
|
248
|
+
|
249
|
+
Parsing a CSV:
|
208
250
|
|
209
251
|
````ruby
|
210
|
-
objects = Bumblebee.
|
252
|
+
objects = Bumblebee::Template.new(columns: columns).parse(csv)
|
211
253
|
````
|
212
254
|
|
213
|
-
|
255
|
+
would output:
|
214
256
|
|
215
257
|
````ruby
|
216
258
|
objects = [
|
217
259
|
{
|
218
260
|
id: 1,
|
219
|
-
|
220
|
-
demo: { dob: '1901-02-03' },
|
221
|
-
contact: { phone: '555-555-5555' }
|
261
|
+
children: [ { id: 9 }, { id: 10 } ]
|
222
262
|
},
|
223
263
|
{
|
224
264
|
id: 2,
|
225
|
-
|
226
|
-
demo: { dob: '1921-09-03' },
|
227
|
-
contact: { phone: '444-444-4444' }
|
265
|
+
children: [ { id: 11 }, { id: 12 } ]
|
228
266
|
},
|
229
267
|
{
|
230
|
-
id: 3
|
231
|
-
name: { first: 'Sam' },
|
232
|
-
demo: { dob: '1932-12-12' },
|
233
|
-
contact: { phone: '333-333-3333' }
|
268
|
+
id: 3
|
234
269
|
}
|
235
270
|
]
|
236
271
|
````
|
237
272
|
|
238
|
-
|
239
|
-
|
240
|
-
The two main methods:
|
241
|
-
|
242
|
-
* generate_csv
|
243
|
-
* parse_csv
|
273
|
+
### Parsing Into Custom Classes
|
244
274
|
|
245
|
-
|
246
|
-
|
247
|
-
#### Template DSL
|
248
|
-
|
249
|
-
You can choose to pass in a block for template/column specification if you would rather prefer a code-first approach over a configuration-first approach.
|
250
|
-
|
251
|
-
##### Using Blocks
|
275
|
+
Hash is the default return type when parsing a CSV. You can change this by providing a Hash-like class:
|
252
276
|
|
253
277
|
````ruby
|
254
|
-
|
255
|
-
|
256
|
-
to_object: ->(o) { o['ID #'].to_i }
|
278
|
+
objects = Bumblebee::Template.new(columns: columns, object_class: OpenStruct).parse(csv)
|
279
|
+
````
|
257
280
|
|
258
|
-
|
259
|
-
to_csv: %i[name first],
|
260
|
-
to_object: ->(o) { { first: o['First Name'] } }
|
261
|
-
end
|
281
|
+
Objects will now be an array of OpenStruct objects instead of Hash objects.
|
262
282
|
|
263
|
-
|
264
|
-
t.column :id, header: 'ID #',
|
265
|
-
to_object: ->(o) { o['ID #'].to_i }
|
283
|
+
* Note: you must also specify this in pluck_split:
|
266
284
|
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
285
|
+
````ruby
|
286
|
+
columns = {
|
287
|
+
'ID #': {
|
288
|
+
property: :id,
|
289
|
+
to_object: :integer
|
290
|
+
},
|
291
|
+
'Children ID #s': {
|
292
|
+
property: :children,
|
293
|
+
to_csv: { type: :pluck_join, separator: ';', sub_property: :id },
|
294
|
+
to_object: { type: :pluck_split, separator: ';', sub_property: :id, object_class: OpenStruct },
|
295
|
+
}
|
296
|
+
}
|
271
297
|
````
|
272
298
|
|
273
|
-
|
299
|
+
#### Further CSV Customization
|
274
300
|
|
275
|
-
|
301
|
+
The two main methods:
|
276
302
|
|
277
|
-
|
278
|
-
|
303
|
+
* Template#generate
|
304
|
+
* Template#parse
|
279
305
|
|
280
|
-
#
|
306
|
+
also accept custom options that [Ruby's CSV::new](https://ruby-doc.org/stdlib-2.6/libdoc/csv/rdoc/CSV.html#method-c-new) accepts. The only caveat is that Bumblebee needs headers for its mapping, so it overrides the header options.
|
281
307
|
|
282
|
-
|
283
|
-
t.column :id, header: 'ID #',
|
284
|
-
to_object: ->(o) { o['ID #'].to_i }
|
308
|
+
#### Template DSL
|
285
309
|
|
286
|
-
|
287
|
-
to_csv: %i[name first],
|
288
|
-
to_object: ->(o) { { first: o['First Name'] } }
|
289
|
-
end
|
290
|
-
````
|
310
|
+
You can choose to pass in a block for template/column specification if you would rather prefer a code-first approach over a configuration-first approach.
|
291
311
|
|
292
|
-
|
312
|
+
##### Using Blocks
|
293
313
|
|
294
314
|
````ruby
|
295
|
-
csv =
|
315
|
+
csv = Bumblebee::Template.new do |t|
|
316
|
+
t.column 'ID #', property: :id,
|
317
|
+
to_object: :integer
|
296
318
|
|
297
|
-
|
319
|
+
t.column 'First Name', property: :first,
|
320
|
+
through: :name
|
321
|
+
end.generate(objects)
|
322
|
+
|
323
|
+
objects = Bumblebee::Template.new do |t|
|
324
|
+
t.column 'ID #', property: :id,
|
325
|
+
to_object: :integer
|
326
|
+
|
327
|
+
t.column 'First Name', property: :first,
|
328
|
+
through: :name
|
329
|
+
end.parse(data)
|
298
330
|
````
|
299
331
|
|
300
332
|
##### Subclassing ::Bumblebee::Template
|
@@ -303,19 +335,17 @@ Another option is to subclass Template and declare your columns at the class-lev
|
|
303
335
|
|
304
336
|
````ruby
|
305
337
|
class PersonTemplate < Bumblebee::Template
|
306
|
-
column
|
307
|
-
|
338
|
+
column 'ID #', property: :id,
|
339
|
+
to_object: :integer
|
308
340
|
|
309
|
-
column
|
310
|
-
|
311
|
-
|
341
|
+
column 'First Name', property: :first,
|
342
|
+
through: :name,
|
343
|
+
to_object: :pluck_split
|
312
344
|
end
|
313
345
|
|
314
|
-
# Usage
|
315
|
-
|
316
346
|
template = PersonTemplate.new
|
317
|
-
csv = template.
|
318
|
-
objects = template.
|
347
|
+
csv = template.generate(objects)
|
348
|
+
objects = template.parse(data)
|
319
349
|
````
|
320
350
|
|
321
351
|
##### Column Precedence
|
@@ -330,18 +360,22 @@ To illustrate all three:
|
|
330
360
|
|
331
361
|
````ruby
|
332
362
|
class PersonTemplate < Bumblebee::Template # first
|
333
|
-
column
|
334
|
-
|
363
|
+
column 'ID #', property: :id,
|
364
|
+
to_object: :integer
|
335
365
|
|
336
|
-
column
|
337
|
-
|
338
|
-
|
366
|
+
column 'First Name', property: :first,
|
367
|
+
through: :name,
|
368
|
+
to_object: :pluck_split
|
339
369
|
end
|
340
370
|
|
341
|
-
|
371
|
+
columns = {
|
372
|
+
'Middle Name': {
|
373
|
+
property: :middle
|
374
|
+
}
|
375
|
+
}
|
342
376
|
|
343
|
-
template = PersonTemplate.new(
|
344
|
-
t.column
|
377
|
+
template = PersonTemplate.new(columns: columns) do |t| # second
|
378
|
+
t.column 'Last Name', property: :last # third
|
345
379
|
end
|
346
380
|
|
347
381
|
````
|