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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e63f0ffdddd7bea22fb6da178b542556cde964543a2d7c4c68ef6e85b3bb06f3
4
- data.tar.gz: 28a41de9de06653b0d2d41d52818bb690d786f124e6e84be7e867a496da2a8c6
3
+ metadata.gz: c3b95d42104a7a73d3af191aa0ab43a3aaadf03e6ab6bb612c07bd741a3cbd76
4
+ data.tar.gz: 7b4ef3e1738d81605786d36a1be4dedb6d9233e4709a7ad189a0552ca0a6cb21
5
5
  SHA512:
6
- metadata.gz: 3153683a9564325488926f6bde6cb77ce59d1674ac6fcb3a9e60911a21870a26905b9d14d851f660d2c0c74b7df34986eadfd488a85d21734b3a7ff515e3453c
7
- data.tar.gz: 9d19b1ea86c98101bb523a8e2ab1d6e0a04230c6b6616a2ba76b0e9bf584ccf2fcb4184a9fb14980e467a5d93749a104bb2b87a26313e48b4b5d1e5653c6376e
6
+ metadata.gz: bc0eb85df6dd1c2562b92d485f407bdf7a3a5ea3a0b2315a66d9a5a314ffe93a05d2772df1b149af4744cdcee4b6d2ace0c3eafb4aba573f757d67eed9ab9bdc
7
+ data.tar.gz: ebbf2f57ee8c5f3b061d990478f7a1655dfe025e32e417c1cfce19f23373a6222a3d3cc23720e24bb51e7a8f3487bf29df90f69f357e216aab7662eaf592c766
@@ -2,10 +2,10 @@ Metrics/LineLength:
2
2
  Max: 100
3
3
 
4
4
  Metrics/BlockLength:
5
- ExcludedMethods: ['it', 'describe', 'context']
5
+ ExcludedMethods: ['let', 'it', 'describe', 'context']
6
6
 
7
7
  Metrics/MethodLength:
8
- Max: 12
8
+ Max: 25
9
9
 
10
10
  AllCops:
11
11
  TargetRubyVersion: 2.3
@@ -15,6 +15,6 @@ before_script:
15
15
  - ./cc-test-reporter before-build
16
16
  script:
17
17
  - bundle exec rubocop
18
- - bundle exec rspec
18
+ - bundle exec rspec spec --format documentation
19
19
  after_script:
20
20
  - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
@@ -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.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bumblebee (2.1.0)
4
+ bumblebee (3.0.0)
5
5
  acts_as_hashable (~> 1.0)
6
6
 
7
7
  GEM
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.parse_csv(columns, data)
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
- { field: :id, header: 'ID #' },
77
- { field: :name, header: 'First Name' },
78
- { field: :dob, header: 'Date of Birth' },
79
- { field: :phone, header: 'Phone #' }
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
- { field: :id, header: 'ID #' },
123
- { field: [:name, :first], header: 'First Name' },
124
- { field: [:demo, :dob], header: 'Date of Birth' },
125
- { field: [:contact, :phone], header: 'Phone #' }
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.generate_csv(columns, objects)
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 To CSV Formatting
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
- field: :id,
145
- header: 'ID #'
148
+ columns = {
149
+ 'ID #': {
150
+ property: :id,
151
+ to_object: :integer
146
152
  },
147
- {
148
- field: :name,
149
- header: 'First Name',
150
- to_csv: [:name, :first, ->(o) { o.to_s.upcase }]
153
+ 'First Name': {
154
+ property: :first,
155
+ through: :name,
156
+ to_csv: ->(v) { v.to_s.upcase }
151
157
  },
152
- {
153
- field: :dob,
154
- header: 'Date of Birth',
155
- to_csv: [:demo, :dob]
158
+ 'Date of Birth': {
159
+ property: :dob,
160
+ through: :demo,
161
+ to_object: { type: :date, nullable: true }
156
162
  },
157
- {
158
- field: :phone,
159
- header: 'Phone #',
160
- to_csv: [:contact, :phone]
163
+ 'Phone #': {
164
+ property: :phone,
165
+ through: :contact
161
166
  }
162
- ]
167
+ }
163
168
  ````
164
169
 
165
- would ensure the CSV has only upper-case `First Name` values.
170
+ would ensure:
166
171
 
167
- ### Custom To Object Formatting
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
- You can also choose a custom method how the CSV's value is parsed just like you can customize how values are set for a CSV. This helps function as an intermediate extractor/formatter/converter, in theory, should be able to give you alot more custom control over the parsing.
176
+ Other formatting functions that can be used for to_object and/or to_csv:
170
177
 
171
- A previous example above showed a custom nested object-to-csv flow. This time, let's go csv-to-object with this dataset:
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
- ID # | First Name | Date of Birth | Phone #
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
- Using the following column config:
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
- columns = [
200
+ objects = [
183
201
  {
184
- field: :id,
185
- header: 'ID #',
186
- to_object: ->(o) { o['ID #'].to_i }
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
- field: :name,
190
- header: 'First Name',
191
- to_csv: %i[name first],
192
- to_object: ->(o) { { first: o['First Name'] } }
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
- { field: :contact,
200
- header: 'Phone #',
201
- to_csv: %i[contact phone],
202
- to_object: ->(o) { { phone: o['Phone #'] } }
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
- Executing the following:
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.parse_csv(columns, data)
252
+ objects = Bumblebee::Template.new(columns: columns).parse(csv)
211
253
  ````
212
254
 
213
- Would give us the following:
255
+ would output:
214
256
 
215
257
  ````ruby
216
258
  objects = [
217
259
  {
218
260
  id: 1,
219
- name: { first: 'Matt' },
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
- name: { first: 'Nick' },
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
- #### Further CSV Customization
239
-
240
- The two main methods:
241
-
242
- * generate_csv
243
- * parse_csv
273
+ ### Parsing Into Custom Classes
244
274
 
245
- 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.
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
- csv = Bumblebee.generate_csv(objects) do |t|
255
- t.column :id, header: 'ID #',
256
- to_object: ->(o) { o['ID #'].to_i }
278
+ objects = Bumblebee::Template.new(columns: columns, object_class: OpenStruct).parse(csv)
279
+ ````
257
280
 
258
- t.column :first, header: 'First Name',
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
- objects = Bumblebee.parse_csv(data) do |t|
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
- t.column :first, header: 'First Name',
268
- to_csv: %i[name first],
269
- to_object: ->(o) { { first: o['First Name'] } }
270
- end
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
- ##### Interacting Directly With ::Bumblebee::Template
299
+ #### Further CSV Customization
274
300
 
275
- You can also choose to interact/build templates directly instead of going through the top-level API:
301
+ The two main methods:
276
302
 
277
- ````ruby
278
- template = Bumblebee::Template.new(columns)
303
+ * Template#generate
304
+ * Template#parse
279
305
 
280
- # or
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
- template = Bumblebee::Template.new do |t|
283
- t.column :id, header: 'ID #',
284
- to_object: ->(o) { o['ID #'].to_i }
308
+ #### Template DSL
285
309
 
286
- t.column :first, header: 'First Name',
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
- Template class has the same top-level methods:
312
+ ##### Using Blocks
293
313
 
294
314
  ````ruby
295
- csv = template.generate_csv(objects)
315
+ csv = Bumblebee::Template.new do |t|
316
+ t.column 'ID #', property: :id,
317
+ to_object: :integer
296
318
 
297
- objects = template.parse_csv(data)
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 :id, header: 'ID #',
307
- to_object: ->(o) { o['ID #'].to_i }
338
+ column 'ID #', property: :id,
339
+ to_object: :integer
308
340
 
309
- column :first, header: 'First Name',
310
- to_csv: %i[name first],
311
- to_object: ->(o) { { first: o['First Name'] } }
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.generate_csv(objects)
318
- objects = template.parse_csv(data)
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 :id, header: 'ID #',
334
- to_object: ->(o) { o['ID #'].to_i }
363
+ column 'ID #', property: :id,
364
+ to_object: :integer
335
365
 
336
- column :first, header: 'First Name',
337
- to_csv: %i[name first],
338
- to_object: ->(o) { { first: o['First Name'] } }
366
+ column 'First Name', property: :first,
367
+ through: :name,
368
+ to_object: :pluck_split
339
369
  end
340
370
 
341
- # Usage
371
+ columns = {
372
+ 'Middle Name': {
373
+ property: :middle
374
+ }
375
+ }
342
376
 
343
- template = PersonTemplate.new({ field: :middle, header: 'Middle Name' }) do |t| # second
344
- t.column :last, header: 'Last Name' # third
377
+ template = PersonTemplate.new(columns: columns) do |t| # second
378
+ t.column 'Last Name', property: :last # third
345
379
  end
346
380
 
347
381
  ````