finishing_moves 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +12 -0
- data/Gemfile.lock +58 -58
- data/LICENSE +1 -1
- data/README.md +659 -110
- data/Rakefile +10 -0
- data/finishing_moves.gemspec +15 -0
- data/lib/finishing_moves/array.rb +30 -0
- data/lib/finishing_moves/date_and_time.rb +25 -0
- data/lib/finishing_moves/enumerable.rb +18 -0
- data/lib/finishing_moves/fixnum.rb +4 -7
- data/lib/finishing_moves/kernel.rb +35 -0
- data/lib/finishing_moves/object.rb +7 -30
- data/lib/finishing_moves/string.rb +127 -0
- data/lib/finishing_moves/version.rb +1 -1
- data/provision.sh +1 -2
- data/spec/array_spec.rb +127 -0
- data/spec/enumerable_spec.rb +37 -0
- data/spec/fixnum_spec.rb +8 -0
- data/spec/kernel_spec.rb +117 -0
- data/spec/object_spec.rb +1 -103
- data/spec/spec_helper.rb +5 -0
- data/spec/string_spec.rb +159 -0
- metadata +30 -3
data/README.md
CHANGED
@@ -1,16 +1,18 @@
|
|
1
1
|
# Finishing Moves
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/finishing_moves.svg)](http://badge.fury.io/rb/finishing_moves)
|
2
3
|
|
3
4
|
##### By the guys at [Forge Software](http://www.forgecrafted.com/)
|
4
5
|
|
5
|
-
Ruby includes a huge amount of default awesomeness that tackles most common development challenges. But every now and then, you find yourself in a situation where an
|
6
|
+
Ruby includes a huge amount of default awesomeness that tackles most common development challenges. But every now and then, you find yourself in a situation where an **elaborate-yet-precise** coding maneuver wins the day. Finishing Moves is a collection of methods designed to assist in those just-typical-enough-to-be-annoying scenarios.
|
6
7
|
|
7
|
-
In gamer terms, if standard Ruby methods are your default moves, `finishing_moves` would be mana-consuming techniques. Your cooldown spells. Your grenades (there's never enough grenades). In the right situation, they kick serious cyclomatic butt.
|
8
|
+
In gamer terms, if standard Ruby methods are your default moves, `finishing_moves` would be mana-consuming techniques. Your cooldown spells. Your grenades (there's never enough grenades!). In the right situation, they kick serious [cyclomatic butt](https://en.wikipedia.org/wiki/Cyclomatic_complexity).
|
8
9
|
|
9
10
|
## Development approach
|
10
11
|
|
11
12
|
- **Never** override default Ruby behavior, only add functionality.
|
12
13
|
- Follow the Unix philosophy of *"Do one job really well."*
|
13
|
-
- Minimize assumptions
|
14
|
+
- Minimize assumptions, e.g. avoid formatting output, mutating values, and long conditional logic flows.
|
15
|
+
- Play nice with major Ruby players like Rake, Rails, and Sinatra.
|
14
16
|
- Test all the things.
|
15
17
|
|
16
18
|
## Installation
|
@@ -27,11 +29,39 @@ gem install 'finishing_moves'
|
|
27
29
|
|
28
30
|
[Here's the gem link](https://rubygems.org/gems/finishing_moves), if you like looking at that stuff.
|
29
31
|
|
30
|
-
##
|
32
|
+
## List of Methods
|
33
|
+
|
34
|
+
- [`Kernel#nil_chain`](#kernelnil_chain)
|
35
|
+
- [`Kernel#cascade`](#kernelcascade)
|
36
|
+
- [`Kernel#class_exists?`](#kernelclass_exists)
|
37
|
+
- [`Object#same_as`](#objectsame_as)
|
38
|
+
- [`Object#not_nil?`](#objectnot_nil)
|
39
|
+
- [`Object#is_an?`](#objectis_an)
|
40
|
+
- [`Hash#delete!`](#hashdelete)
|
41
|
+
- [`Hash#delete_each`](#hashdelete_each)
|
42
|
+
- [`Hash#delete_each!`](#hashdelete_each-1)
|
43
|
+
- [`Integer#length`](#integerlength)
|
44
|
+
- [`Boolean` Typecasting](#typecasting-to-boolean)
|
45
|
+
|
46
|
+
###### *New in 0.3.0!*
|
47
|
+
|
48
|
+
- [`Array#to_hash_values`](#arrayto_hash_values)
|
49
|
+
- [`Array#to_indexed_hash`](#arrayto_indexed_hash)
|
50
|
+
- [`Array#to_hash_keys`](#arrayto_hash_keys)
|
51
|
+
- [`Enumerable#key_map`](#enumerablekey_map)
|
52
|
+
- [`Enumerable#key_map_reduce`](#enumerablekey_map_reduce)
|
53
|
+
- [`String#dedupe`](#stringdedupe)
|
54
|
+
- [`String#keyify`](#stringkeyify)
|
55
|
+
- [`String#match?`](#stringmatch)
|
56
|
+
- [`String#nl2br`](#stringnl2br)
|
57
|
+
- [`String#remove_whitespace`](#stringremove_whitespace)
|
58
|
+
- [`String#strip_all`](#stringstrip_all)
|
59
|
+
|
60
|
+
### Extensions to `Kernel`
|
61
|
+
|
62
|
+
#### `Kernel#nil_chain`
|
63
|
+
###### `nil_chain(ret_val = nil, &block)`
|
31
64
|
|
32
|
-
### Extensions to `Object`
|
33
|
-
|
34
|
-
#### `Object#nil_chain`
|
35
65
|
Arguably the sharpest knife in the block, `#nil_chain` allows you to write elaborate method chains without fear of tripping over `NoMethodError` and `NameError` exceptions when something in the chain throws out a nil value.
|
36
66
|
|
37
67
|
##### Examples
|
@@ -207,9 +237,11 @@ var = nil_chain(Geomancer.reset_ley_lines) { summon_fel_beast[:step_3].scry }
|
|
207
237
|
# Geomancer.reset_ley_lines if it's not
|
208
238
|
```
|
209
239
|
|
240
|
+
##### Alias
|
241
|
+
|
210
242
|
`nil_chain` is aliased to `method_chain` for alternative clarity.
|
211
243
|
|
212
|
-
#### `
|
244
|
+
#### `Kernel#bool_chain`
|
213
245
|
|
214
246
|
This is the same logic under the hood as `nil_chain`, however we forcibly return a boolean `false` instead of `nil` if the chain breaks.
|
215
247
|
|
@@ -220,74 +252,7 @@ bool_chain{ a.b.c.hello }
|
|
220
252
|
# => false
|
221
253
|
```
|
222
254
|
|
223
|
-
|
224
|
-
|
225
|
-
```ruby
|
226
|
-
nil_chain(false) { a.b.c.hello }
|
227
|
-
# => false
|
228
|
-
```
|
229
|
-
|
230
|
-
#### `Object#same_as`
|
231
|
-
|
232
|
-
Comparison operator that normalizes both sides into strings, then runs them over `==`.
|
233
|
-
|
234
|
-
The comparison will work on any class that has a `to_s` method defined on it.
|
235
|
-
|
236
|
-
```ruby
|
237
|
-
# All these comparisons will return true
|
238
|
-
|
239
|
-
:foobar.same_as 'foobar'
|
240
|
-
'foobar'.same_as :foobar
|
241
|
-
'1'.same_as 1
|
242
|
-
2.same_as '2'
|
243
|
-
3.same_as 3
|
244
|
-
```
|
245
|
-
|
246
|
-
Normal case-sensitivity rules apply.
|
247
|
-
|
248
|
-
```ruby
|
249
|
-
:symbol.same_as :SYMBOL
|
250
|
-
# => false
|
251
|
-
|
252
|
-
:symbol.same_as 'SYMBOL'
|
253
|
-
# => still false
|
254
|
-
```
|
255
|
-
|
256
|
-
Since this method is defined in Object, your own custom classes inherit it automatically, allowing you to compare literally anything at any time, without worrying about typecasting!
|
257
|
-
|
258
|
-
**Make sure you define sane output for `to_s`** and you're all set.
|
259
|
-
|
260
|
-
We love working with symbols in our code, but symbol values become strings when they hit the database. This meant typecasting wherever new and existing data might collide. No more!
|
261
|
-
|
262
|
-
```ruby
|
263
|
-
class User
|
264
|
-
attr_writer :handle
|
265
|
-
|
266
|
-
def handle
|
267
|
-
@handle || "faceless_one"
|
268
|
-
end
|
269
|
-
|
270
|
-
def to_s
|
271
|
-
handle.to_s
|
272
|
-
end
|
273
|
-
end
|
274
|
-
|
275
|
-
user = User.new
|
276
|
-
:faceless_one.same_as user
|
277
|
-
# => true
|
278
|
-
user.same_as :faceless_one
|
279
|
-
# => true
|
280
|
-
user.same_as 'faceless_one'
|
281
|
-
# => true
|
282
|
-
user.same_as 'FACELESS_ONE'
|
283
|
-
# => false
|
284
|
-
```
|
285
|
-
|
286
|
-
#### `Object#class_exists?`
|
287
|
-
|
288
|
-
> *I just want to know if [insert class name] has been defined!*
|
289
|
-
>
|
290
|
-
> -- Every dev at some point
|
255
|
+
#### `Kernel#class_exists?`
|
291
256
|
|
292
257
|
Sure, Ruby has the `defined?` method, but the output is less than helpful when you're doing conditional flows.
|
293
258
|
|
@@ -327,34 +292,21 @@ class_exists? :DefinitelyFakeClass
|
|
327
292
|
# => false (at least it better be; if you *actually* use this name, I will find you...)
|
328
293
|
```
|
329
294
|
|
330
|
-
#### `
|
331
|
-
|
332
|
-
Because that dangling `!` on the front of a call to `nil?` is just oh so not-ruby-chic.
|
295
|
+
#### `Kernel#cascade`
|
333
296
|
|
334
|
-
|
335
|
-
nil.not_nil?
|
336
|
-
# => false
|
337
|
-
'foobar'.not_nil?
|
338
|
-
# => true
|
339
|
-
```
|
340
|
-
|
341
|
-
Much better. Now pass me another PBR and my fedora.
|
342
|
-
|
343
|
-
#### `Object#cascade`
|
344
|
-
|
345
|
-
This method is designed to facilitate a set of consecutive, mutating actions which may be interrupted at multiple arbitrary points. In pseudo-code, the logic we're trying to write looks like this:
|
297
|
+
This method is designed to facilitate a set of **consecutive, mutating actions** which may be interrupted at multiple arbitrary points. In pseudo-code, the logic we're trying to write looks like this:
|
346
298
|
|
347
299
|
1. Begin stepwise process.
|
348
300
|
2. Set `[values]` to a default starting state.
|
349
301
|
3. If `[first requirement]` is not met, bail out.
|
350
302
|
4. Perform steps that require `[first requirement]`, possibly mutating `[values]`.
|
351
|
-
5. If [next requirement] is not met, bail out.
|
352
|
-
6. Perform steps that require `[
|
303
|
+
5. If `[next requirement]` is not met, bail out.
|
304
|
+
6. Perform steps that require `[next requirement]`, possibly mutating `[values]` again.
|
353
305
|
7. (Repeat for as many steps as necessary.)
|
354
306
|
8. End stepwise process.
|
355
307
|
9. Perform follow-up action(s) based on resulting `[values]`.
|
356
308
|
|
357
|
-
|
309
|
+
Here's a contrived Rails-y sample of a login approval process:
|
358
310
|
|
359
311
|
```ruby
|
360
312
|
cascade do
|
@@ -385,34 +337,133 @@ We're using the [`loop`](http://www.ruby-doc.org/core-2.1.5/Kernel.html#method-i
|
|
385
337
|
|
386
338
|
###### *"Why not just shove the logic into a method and use `return` instead of `break`?"*
|
387
339
|
|
388
|
-
You should absolutely use methods if it makes sense
|
340
|
+
You should absolutely use methods if it makes sense!
|
389
341
|
|
390
|
-
`cascade` is ideal for small sets of logic,
|
342
|
+
`cascade` is ideal for small sets of logic, when you've *already* broken out your logic into a method and further breakout is just silly.
|
391
343
|
|
392
|
-
To illustrate, here's
|
344
|
+
To illustrate, here's a small real-world sample from one of our projects:
|
393
345
|
|
394
346
|
```ruby
|
395
|
-
class ReportsController
|
347
|
+
class ReportsController < ApplicationController
|
396
348
|
|
397
349
|
before_action :define_search_params, only: :run_report
|
398
350
|
|
351
|
+
# ...
|
352
|
+
|
399
353
|
def define_search_params
|
400
|
-
|
401
|
-
|
354
|
+
@report = params[:report].to_sym
|
355
|
+
|
356
|
+
# Set the report category, :medical or :drug
|
357
|
+
# 1. An :ongoing report is always in the :drug category
|
358
|
+
# 2. Otherwise default to :medical
|
359
|
+
# 3. :dismissal reports are always :medical (so we use the default)
|
360
|
+
# 4. Finally just use the params value, if it matches an allowable value
|
402
361
|
cascade do
|
403
|
-
@
|
362
|
+
if @report == :ongoing
|
363
|
+
@category = :drug
|
364
|
+
break
|
365
|
+
end
|
366
|
+
@category = :medical
|
404
367
|
break if @report == :dismissals
|
405
|
-
@
|
368
|
+
@category = params[:category] if params[:category].in? allowable_categories
|
406
369
|
end
|
370
|
+
end
|
371
|
+
|
372
|
+
end
|
373
|
+
```
|
374
|
+
|
375
|
+
It's overkill to break that bit of logic for the value of `@category` out into another method.
|
376
|
+
|
377
|
+
Plus, we find the vertically aligned codes reads better, especially as the list of conditionals goes beyond two. This pattern also has the added benefit of making top-to-bottom "readable" sense.
|
378
|
+
|
379
|
+
### Extensions to `Object`
|
380
|
+
|
381
|
+
#### `Object#same_as`
|
382
|
+
|
383
|
+
Comparison operator that normalizes both sides into strings, then runs them over `==`.
|
384
|
+
|
385
|
+
The comparison will work on any class that has a `to_s` method defined on it.
|
386
|
+
|
387
|
+
```ruby
|
388
|
+
# All these comparisons will return true
|
407
389
|
|
408
|
-
|
390
|
+
:foobar.same_as 'foobar'
|
391
|
+
'foobar'.same_as :foobar
|
392
|
+
'1'.same_as 1
|
393
|
+
2.same_as '2'
|
394
|
+
3.same_as 3
|
395
|
+
```
|
396
|
+
|
397
|
+
Normal case-sensitivity rules apply.
|
398
|
+
|
399
|
+
```ruby
|
400
|
+
:symbol.same_as :SYMBOL
|
401
|
+
# => false
|
402
|
+
|
403
|
+
:symbol.same_as 'SYMBOL'
|
404
|
+
# => still false
|
405
|
+
```
|
406
|
+
|
407
|
+
Since this method is defined in Object, your own custom classes inherit it automatically, allowing you to compare literally anything at any time, without worrying about typecasting!
|
408
|
+
|
409
|
+
**Make sure you define sane output for `to_s`** and you're all set.
|
410
|
+
|
411
|
+
We love working with symbols in our code, but symbol values become strings when they hit the database. This meant typecasting wherever new and existing data might collide. No more!
|
412
|
+
|
413
|
+
```ruby
|
414
|
+
class User
|
415
|
+
attr_writer :handle
|
416
|
+
|
417
|
+
def handle
|
418
|
+
@handle || "faceless_one"
|
409
419
|
end
|
410
420
|
|
421
|
+
def to_s
|
422
|
+
handle.to_s
|
423
|
+
end
|
411
424
|
end
|
425
|
+
|
426
|
+
user = User.new
|
427
|
+
:faceless_one.same_as user
|
428
|
+
# => true
|
429
|
+
user.same_as :faceless_one
|
430
|
+
# => true
|
431
|
+
user.same_as 'faceless_one'
|
432
|
+
# => true
|
433
|
+
user.same_as 'FACELESS_ONE'
|
434
|
+
# => false
|
412
435
|
```
|
413
436
|
|
414
|
-
|
437
|
+
##### Alias
|
438
|
+
|
439
|
+
`same_as` is aliased to `same_as?` for alternative clarity.
|
440
|
+
|
441
|
+
|
442
|
+
#### `Object#not_nil?`
|
415
443
|
|
444
|
+
Because that dangling `!` on the front of a call to `nil?` is just oh so not-ruby-chic.
|
445
|
+
|
446
|
+
```ruby
|
447
|
+
nil.not_nil?
|
448
|
+
# => false
|
449
|
+
'foobar'.not_nil?
|
450
|
+
# => true
|
451
|
+
```
|
452
|
+
|
453
|
+
Pass me one of those PBR's.
|
454
|
+
|
455
|
+
#### `Object#is_an?`
|
456
|
+
|
457
|
+
Alias for the [`is_a?` method](http://ruby-doc.org/core-2.2.0/Object.html#method-i-is_a-3F), for even more Ruby chic!
|
458
|
+
|
459
|
+
```ruby
|
460
|
+
1.is_a? Integer
|
461
|
+
# => true, and a thorn in the side of grammar teachers everywhere!
|
462
|
+
1.is_an? Integer
|
463
|
+
# => still true, but now I don't mentally pause every time I read it.
|
464
|
+
```
|
465
|
+
|
466
|
+
Now pass me another PBR and my fedora.
|
416
467
|
|
417
468
|
### Extensions to `Hash`
|
418
469
|
|
@@ -442,6 +493,7 @@ power_rangers.delete! :radiant_orchid
|
|
442
493
|
```
|
443
494
|
|
444
495
|
#### `Hash#delete_each`
|
496
|
+
|
445
497
|
Deletes all records in a hash matching the keys passed in as an array. Returns a hash of deleted entries. Silently ignores any keys which are not found.
|
446
498
|
|
447
499
|
```ruby
|
@@ -483,7 +535,7 @@ mega_man_bosses.delete_each! :crash_man, :quick_man
|
|
483
535
|
# => { }
|
484
536
|
```
|
485
537
|
|
486
|
-
### `
|
538
|
+
### `Integer#length`
|
487
539
|
|
488
540
|
Ruby doesn't provide a native way to see how many digits are in an integer, but that's exactly what we worry about anytime database `INT` lengths collide with Ruby `Fixnum` or `Bignum` values.
|
489
541
|
|
@@ -521,11 +573,15 @@ For consistency, we added matching methods to `Float` and `BigDecimal` that simp
|
|
521
573
|
# => ArgumentError: Cannot get length: "1.2654377184388666e+21" is not an integer
|
522
574
|
```
|
523
575
|
|
576
|
+
##### Alias
|
577
|
+
|
578
|
+
`length` is aliased to `digits` for alternative clarity.
|
579
|
+
|
524
580
|
### Typecasting *to* `Boolean`
|
525
581
|
|
526
582
|
Boolean values are frequently represented as strings and integers in databases and file storage. So we always thought it was a little odd that Ruby lacked a boolean typecasting method, given the proliferation of `to_*` methods for `String`, `Symbol`, `Integer`, `Float`, `Hash`, etc.
|
527
583
|
|
528
|
-
So we made
|
584
|
+
So we made some for `String`, `Integer`, and `Nil`.
|
529
585
|
|
530
586
|
#### `String#to_bool`
|
531
587
|
|
@@ -635,6 +691,500 @@ nil.to_sym
|
|
635
691
|
# => :nil
|
636
692
|
```
|
637
693
|
|
694
|
+
### Extensions to `Array`
|
695
|
+
|
696
|
+
Ruby's [`to_h` method](http://ruby-doc.org/core-2.2.0/Array.html#method-i-to_h) converts an array to a hash by interpreting the array as an array of `[key, value]` pairs. But what if you have a one-dimensional array of things that you want to push into a hash, and the values (or keys) are yet to be determined? Finishing Moves provides a more flexible implementation.
|
697
|
+
|
698
|
+
#### `Array#to_hash_values`
|
699
|
+
###### `to_hash_values(starting_key = 0, &block)`
|
700
|
+
|
701
|
+
Convert an array of things into a hash with the array elements stored as values. By default the hash will be numerically indexed starting from zero.
|
702
|
+
|
703
|
+
```ruby
|
704
|
+
sages = ['Rauru', 'Saria', 'Darunia', 'Princess Ruto', 'Impa', 'Nabooru', 'Zelda']
|
705
|
+
|
706
|
+
sages_hash = sages.to_hash_values
|
707
|
+
# => {0=>"Rauru", 1=>"Saria", 2=>"Darunia", 3=>"Princess Ruto", 4=>"Impa", 5=>"Nabooru", 6=>"Zelda"}
|
708
|
+
```
|
709
|
+
|
710
|
+
`starting_key` represents where key indexing should start. Unless a block is provided, keys are assumed to be numerical and will increment by one. The above example is equivalent to `sages_hash = sages.to_hash_values(0)`.
|
711
|
+
|
712
|
+
The block syntax allows you to easily increment at any rate.
|
713
|
+
|
714
|
+
```ruby
|
715
|
+
sages_hash = sages.to_hash_values(0) { |key| key + 3 }
|
716
|
+
# => {0=>"Rauru", 3=>"Saria", 6=>"Darunia", 9=>"Princess Ruto", 12=>"Impa", 15=>"Nabooru", 18=>"Zelda"}
|
717
|
+
```
|
718
|
+
|
719
|
+
Using the block syntax you can create keys out of almost anything, making `to_hash_values` a powerful tool for generating collections of objects.
|
720
|
+
|
721
|
+
```ruby
|
722
|
+
class SageElements
|
723
|
+
|
724
|
+
def initialize
|
725
|
+
@keys = {
|
726
|
+
:first => :light,
|
727
|
+
:light => :forest,
|
728
|
+
:forest => :fire,
|
729
|
+
:fire => :water,
|
730
|
+
:water => :shadow,
|
731
|
+
:shadow => :spirit,
|
732
|
+
:spirit => :time,
|
733
|
+
:time => :first,
|
734
|
+
}
|
735
|
+
end
|
736
|
+
|
737
|
+
def first_key
|
738
|
+
@keys[:first]
|
739
|
+
end
|
740
|
+
|
741
|
+
def next_key(pointer)
|
742
|
+
@keys[pointer]
|
743
|
+
end
|
744
|
+
|
745
|
+
end
|
746
|
+
|
747
|
+
sages_hash = sages.to_hash_values(elements.first_key) do |key|
|
748
|
+
elements.next_key(key)
|
749
|
+
end
|
750
|
+
# => {:light=>"Rauru", :forest=>"Saria", :fire=>"Darunia", :water=>"Princess Ruto", :shadow=>"Impa", :spirit=>"Nabooru", :time=>"Zelda"}
|
751
|
+
```
|
752
|
+
|
753
|
+
#### `Array#to_indexed_hash`
|
754
|
+
###### `to_indexed_hash(starting_key = 0)`
|
755
|
+
|
756
|
+
Same logic as `to_hash_values`, but assumes an integer key, increments by 1, and skips the block syntax. It will raise an `ArgumentError` if the key is not of type `Integer` (floating point keys must use `to_hash_values` syntax).
|
757
|
+
|
758
|
+
```ruby
|
759
|
+
sages.to_indexed_hash(22)
|
760
|
+
# => {22=>"Rauru", 23=>"Saria", 24=>"Darunia", 25=>"Princess Ruto", 26=>"Impa", 27=>"Nabooru", 28=>"Zelda"}
|
761
|
+
|
762
|
+
sages.to_indexed_hash("e")
|
763
|
+
# => ArgumentError: "e" is not an integer
|
764
|
+
```
|
765
|
+
|
766
|
+
##### Alias
|
767
|
+
|
768
|
+
`to_hash_values` is aliased to `to_hash_as_values` for alternative clarity.
|
769
|
+
|
770
|
+
#### `Array#to_hash_keys`
|
771
|
+
###### `to_hash_keys(starting_value = 0, &block)`
|
772
|
+
|
773
|
+
Convert an array of things into a hash, with the array values becoming keys. `starting_value` will be set as the value for each pair in the new array.
|
774
|
+
|
775
|
+
```ruby
|
776
|
+
sages = ['Rauru', 'Saria', 'Darunia', 'Princess Ruto', 'Impa', 'Nabooru', 'Zelda']
|
777
|
+
|
778
|
+
sages_hash = sages.to_hash_keys
|
779
|
+
# => {"Rauru"=>0, "Saria"=>0, "Darunia"=>0, "Princess Ruto"=>0, "Impa"=>0, "Nabooru"=>0, "Zelda"=>0}
|
780
|
+
```
|
781
|
+
|
782
|
+
Note that the default `starting_value` is a numerical zero rather than `nil` deliberately. Ruby reports an undefined key as `nil`, so a non-nil value ensures each hash pair is fully "existent" in Ruby terms.
|
783
|
+
|
784
|
+
The block syntax allows for complex definitions of the value. This logic works precisely the same as `to_hash_values`, so see above for details.
|
785
|
+
|
786
|
+
##### Alias
|
787
|
+
|
788
|
+
`to_hash_keys` is aliased to `to_hash_as_keys` for alternative clarity.
|
789
|
+
|
790
|
+
|
791
|
+
#### `Array#to_hash`
|
792
|
+
|
793
|
+
###### **We Need your feedback!**
|
794
|
+
|
795
|
+
This is **not** currently defined, either in the standard Ruby spec or in Finishing Moves. We planned to make it an alias of either `to_hash_values` or `to_hash_keys`, but couldn't come to an agreement about which makes more sense. If you have some input, please drop your thoughts in the issues.
|
796
|
+
|
797
|
+
### Extensions to `Enumerable`
|
798
|
+
|
799
|
+
#### `Enumerable#key_map`
|
800
|
+
###### `key_map(key)`
|
801
|
+
|
802
|
+
[Standard `Enumerable#map`](http://ruby-doc.org/core-2.2.0/Enumerable.html#method-i-map) has a great shortcut when you want to create an `Array` by calling a method on each element in the collection. For example:
|
803
|
+
|
804
|
+
```ruby
|
805
|
+
class Pokemon
|
806
|
+
attr_accessor :name
|
807
|
+
def initialize(n)
|
808
|
+
@name = n
|
809
|
+
end
|
810
|
+
end
|
811
|
+
|
812
|
+
your_pokedex = [
|
813
|
+
Pokemon.new("Bulbasaur"),
|
814
|
+
Pokemon.new("Charmander"),
|
815
|
+
Pokemon.new("Squirtle"),
|
816
|
+
]
|
817
|
+
```
|
818
|
+
|
819
|
+
If you want an `Array` of Pokemon names, you use `Enumerable#map`:
|
820
|
+
|
821
|
+
your_pokedex.map { |p| p.name }
|
822
|
+
# => ["Bulbasaur", "Charmander", "Squirtle"]
|
823
|
+
|
824
|
+
A shortcut makes it easy for trivial, repeatable method calls (such as to `:name`):
|
825
|
+
|
826
|
+
your_pokedex.map(&:name)
|
827
|
+
# => ["Bulbasaur", "Charmander", "Squirtle"]
|
828
|
+
|
829
|
+
But what happens when my Pokedex isn't as well-structured as yours?
|
830
|
+
|
831
|
+
```ruby
|
832
|
+
my_pokedex = [
|
833
|
+
{name: "Bulbasaur"},
|
834
|
+
{name: "Charmander"},
|
835
|
+
{name: "Squirtle"},
|
836
|
+
]
|
837
|
+
```
|
838
|
+
|
839
|
+
I can still map the `:name` keys out to an `Array` with full block notation...
|
840
|
+
|
841
|
+
my_pokedex.map { |p| p[:name] }
|
842
|
+
# => ["Bulbasaur", "Charmander", "Squirtle"]
|
843
|
+
|
844
|
+
But such sad! I can haz no shortcut.
|
845
|
+
|
846
|
+
my_pokedex.map(??????)
|
847
|
+
# => ["Bulbasaur", "Charmander", "Squirtle"]
|
848
|
+
|
849
|
+
Enter `Enumerable#key_map`:
|
850
|
+
|
851
|
+
my_pokedex.key_map(:name)
|
852
|
+
# => ["Bulbasaur", "Charmander", "Squirtle"]
|
853
|
+
|
854
|
+
#### `Enumerable#key_map_reduce`
|
855
|
+
###### `key_map_reduce(key, arg = :+, &block)`
|
856
|
+
|
857
|
+
Building off of `Enumerable#key_map`, finishing_moves provides a convenience method when you need to perform a one-step map/reduce operation on a collection.
|
858
|
+
|
859
|
+
```ruby
|
860
|
+
my_pokedex = [
|
861
|
+
{name: "Bulbasaur", level: 2},
|
862
|
+
{name: "Charmander", level: 2},
|
863
|
+
{name: "Squirtle", level: 2},
|
864
|
+
]
|
865
|
+
```
|
866
|
+
|
867
|
+
In other words, this map/reduce operation
|
868
|
+
|
869
|
+
my_pokedex.key_map(:level).reduce(0) { |memo,lvl| memo + lvl }
|
870
|
+
# => 6
|
871
|
+
|
872
|
+
can be simplified to
|
873
|
+
|
874
|
+
my_pokedex.key_map_reduce(:level, :+)
|
875
|
+
# => 6
|
876
|
+
|
877
|
+
where `:+` can be any named method of `memo`, and is applied to each value (just as in [`Enumerable#reduce`](http://ruby-doc.org/core-2.2.0/Enumerable.html#method-i-reduce)). For additional flexibility, you can pass an intial value for `memo` and a custom `block` (and again, this works just like `Enumerable#reduce`):
|
878
|
+
|
879
|
+
my_pokedex.key_map_reduce(:level, 0) { |memo,lvl| memo + lvl }
|
880
|
+
# => 6
|
881
|
+
|
882
|
+
### Extensions to `String`
|
883
|
+
|
884
|
+
#### `String#dedupe`
|
885
|
+
###### `dedupe(str)`
|
886
|
+
|
887
|
+
Find multiple concurrent occurrences of a character and reduce them to a single occurrence.
|
888
|
+
|
889
|
+
```ruby
|
890
|
+
'hello___world'.dedupe('_')
|
891
|
+
# => 'hello_world'
|
892
|
+
|
893
|
+
'/crazy//concatenated////file/path'.dedupe('/')
|
894
|
+
# => '/crazy/concatenated/file/path'
|
895
|
+
```
|
896
|
+
|
897
|
+
You can dedupe multiple characters by passing them all together within a single string.
|
898
|
+
|
899
|
+
```ruby
|
900
|
+
'foo___bar_baz---bing'.dedupe('-_')
|
901
|
+
# => 'foo_bar_baz-bing'
|
902
|
+
```
|
903
|
+
|
904
|
+
`dedupe` won't automatically strip leading or trailing characters. You'll want to combine it with [`strip_all`](#stringstrip_all) to do that.
|
905
|
+
|
906
|
+
```ruby
|
907
|
+
'/crazy//concatenated////file/path/'.dedupe('/')
|
908
|
+
# => '/crazy/concatenated/file/path/'
|
909
|
+
|
910
|
+
'/crazy//concatenated////file/path/'.dedupe('/').strip_all('/')
|
911
|
+
# => 'crazy/concatenated/file/path'
|
912
|
+
```
|
913
|
+
|
914
|
+
##### Bang variant
|
915
|
+
|
916
|
+
`dedupe!` will perform the modifications in place, rather than returning a copy.
|
917
|
+
|
918
|
+
#### `String#keyify`
|
919
|
+
|
920
|
+
Sometimes we find ourselves in need of a codified version of a string value. For example, user-generated values that must be compared for basic sameness, or creating database keys based on user-driven data entry. We use `keyify` in these situations to normalize the string down into a handy code for these comparison and data storage purposes.
|
921
|
+
|
922
|
+
`keyify` will perform the following actions...
|
923
|
+
|
924
|
+
1. Replace all non-alphanumerics with underscores
|
925
|
+
2. Convert any existing `CamelCase` into `snake_case`
|
926
|
+
3. Strip any leading numbers and underscores
|
927
|
+
4. Combine multiple concurrent underscores into a single one
|
928
|
+
5. Convert to lowercase
|
929
|
+
6. Return as a symbol
|
930
|
+
|
931
|
+
```ruby
|
932
|
+
'FooBarBaz'.keyify
|
933
|
+
# => :foo_bar_baz
|
934
|
+
|
935
|
+
"Foo-Bar'Baz".keyify
|
936
|
+
# => :foo_bar_baz
|
937
|
+
|
938
|
+
'1234FooBAR'.keyify
|
939
|
+
# => :foo_bar
|
940
|
+
|
941
|
+
# Works with symbols as well
|
942
|
+
:FooBarBaz.keyify
|
943
|
+
# => :foo_bar_baz
|
944
|
+
```
|
945
|
+
|
946
|
+
Say a person's name is entered into a system by two different people, and we must now compare the values to see if they match. We all know user-entered data sucks, hopefully `keyify` can make it suck just a little less.
|
947
|
+
|
948
|
+
```ruby
|
949
|
+
'John Doe'.keyify
|
950
|
+
# => :john_doe
|
951
|
+
|
952
|
+
'JOHN DOE'.keyify
|
953
|
+
# => :john_doe
|
954
|
+
|
955
|
+
'John Doe'.keyify == 'JOHN DOE'.keyify
|
956
|
+
# => true
|
957
|
+
|
958
|
+
"Ted O'Baxter".keyify == 'Ted O Baxter'.keyify
|
959
|
+
# => true
|
960
|
+
```
|
961
|
+
|
962
|
+
How about a dropdown menu populated with options created by end users? An identifier other than the database's primary key can often be useful.
|
963
|
+
|
964
|
+
```ruby
|
965
|
+
'Not a covered benefit'.keyify
|
966
|
+
# => :not_a_covered_benefit
|
967
|
+
|
968
|
+
"User's Duplicate Claim".keyify
|
969
|
+
# => :user_s_duplicate_claim
|
970
|
+
|
971
|
+
"Included in global amount/bundled".keyify
|
972
|
+
# => :included_in_global_amount_bundled
|
973
|
+
```
|
974
|
+
|
975
|
+
In case you need something from the Ruby-verse, `keyify` also works on static class declarations.
|
976
|
+
|
977
|
+
```ruby
|
978
|
+
Integer.keyify
|
979
|
+
# => :integer
|
980
|
+
|
981
|
+
Math::DomainError.keyify
|
982
|
+
# => :math_domain_error
|
983
|
+
```
|
984
|
+
|
985
|
+
It also makes it easy to build a hash with keys based on string values.
|
986
|
+
|
987
|
+
```ruby
|
988
|
+
my_hash = {}
|
989
|
+
['Option A', 'Option B', 'Option C', 'Option D'].each do |opt|
|
990
|
+
my_hash[opt.keyify] = opt
|
991
|
+
end
|
992
|
+
|
993
|
+
my_hash
|
994
|
+
# => {:option_a=>"Option A", :option_b=>"Option B", :option_c=>"Option C", :option_d=>"Option D"}
|
995
|
+
```
|
996
|
+
|
997
|
+
##### Bang variant
|
998
|
+
|
999
|
+
The `keyify!` version performs the same actions, but will raise an `ArgumentError` if the value being keyified results in an empty string.
|
1000
|
+
|
1001
|
+
```ruby
|
1002
|
+
' '.keyify!
|
1003
|
+
# => ArgumentError: " " cannot be keyified, no valid characters
|
1004
|
+
|
1005
|
+
'!@#$%^'.keyify!
|
1006
|
+
# => ArgumentError: "!@#$%^" cannot be keyified, no valid characters
|
1007
|
+
|
1008
|
+
'12345678'.keyify!
|
1009
|
+
# => ArgumentError: "12345678" cannot be keyified, no valid characters
|
1010
|
+
```
|
1011
|
+
|
1012
|
+
#### `String#match?`
|
1013
|
+
|
1014
|
+
Ruby's [`match` method](http://ruby-doc.org/core-2.2.0/String.html#method-i-match) is often used in boolean operations to determine the presence or absence of a given pattern within a string. That's why we found it odd that Ruby doesn't include a shortcut method to return a boolean result.
|
1015
|
+
|
1016
|
+
`match?` operates exactly like `match`, and simply returns `true` or `false` based on the results of the lookup.
|
1017
|
+
|
1018
|
+
```ruby
|
1019
|
+
'hello'.match?('he')
|
1020
|
+
# => true
|
1021
|
+
|
1022
|
+
'hello'.match?('o')
|
1023
|
+
# => true
|
1024
|
+
|
1025
|
+
'hello'.match?('(.)')
|
1026
|
+
# => true
|
1027
|
+
|
1028
|
+
'hello'.match?(/(.)/)
|
1029
|
+
# => true
|
1030
|
+
|
1031
|
+
'hello'.match?('xx')
|
1032
|
+
# => false
|
1033
|
+
|
1034
|
+
'hello'.match?('he', 1)
|
1035
|
+
# => false
|
1036
|
+
```
|
1037
|
+
|
1038
|
+
#### `String#nl2br`
|
1039
|
+
|
1040
|
+
Converts newlines in a string into break tags. Will recognize Unix line feed (`\n`), standalone carriage returns (`\r`), and Windows formats (both `\r\n` and the improperly formatted `\n\r`).
|
1041
|
+
|
1042
|
+
A Unix newline is appended immediately following each break tag replacement.
|
1043
|
+
|
1044
|
+
```ruby
|
1045
|
+
"\n".nl2br
|
1046
|
+
# => "<br />\n"
|
1047
|
+
|
1048
|
+
"\n\r".nl2br
|
1049
|
+
# => "<br />\n"
|
1050
|
+
|
1051
|
+
"\r\n".nl2br
|
1052
|
+
# => "<br />\n"
|
1053
|
+
|
1054
|
+
"\n\r\n".nl2br
|
1055
|
+
# => "<br />\n<br />\n"
|
1056
|
+
|
1057
|
+
"\r\n\r\n".nl2br
|
1058
|
+
# => "<br />\n<br />\n"
|
1059
|
+
|
1060
|
+
"\r\r\n".nl2br
|
1061
|
+
# => "<br />\n<br />\n"
|
1062
|
+
|
1063
|
+
"\r\r".nl2br
|
1064
|
+
# => "<br />\n<br />\n"
|
1065
|
+
|
1066
|
+
"\n\r\r".nl2br
|
1067
|
+
# => "<br />\n<br />\n"
|
1068
|
+
```
|
1069
|
+
|
1070
|
+
#### `String#remove_whitespace`
|
1071
|
+
|
1072
|
+
Removes all the whitespace from a string. No muss, no fuss.
|
1073
|
+
|
1074
|
+
```ruby
|
1075
|
+
' a b c d e'.remove_whitespace
|
1076
|
+
# => 'abcde'
|
1077
|
+
|
1078
|
+
# Absolutely any string is valid
|
1079
|
+
'. $ ^ { [ ( " | " ) * + ?'.remove_whitespace
|
1080
|
+
# => '.$^{[("|")*+?'
|
1081
|
+
```
|
1082
|
+
|
1083
|
+
You can optionally provide a string that will replace the whitespace, rather than remove it entirely.
|
1084
|
+
|
1085
|
+
```ruby
|
1086
|
+
'1 2 3 4 5'.remove_whitespace('+')
|
1087
|
+
# => '1+2+3+4+5'
|
1088
|
+
```
|
1089
|
+
|
1090
|
+
Be careful, as `remove_whitespace` won't consolidate spaces before performing a replacement! If that's necessary, you should run your string over the [`dedupe`](#stringdedupe) method first.
|
1091
|
+
|
1092
|
+
```ruby
|
1093
|
+
'1 2 3 4 5'.remove_whitespace('+')
|
1094
|
+
# => '1+++2+3+4+5'
|
1095
|
+
|
1096
|
+
'1 2 3 4 5'.dedupe(' ').remove_whitespace('+')
|
1097
|
+
# => '1+2+3+4+5'
|
1098
|
+
```
|
1099
|
+
|
1100
|
+
#### `String#strip_all`
|
1101
|
+
|
1102
|
+
Ruby's [`strip` method](http://ruby-doc.org/core-2.2.0/String.html#method-i-strip) removes leading and trailing whitespace, but there's no method to strip other characters like dashes, underscores, or numbers. `strip_all` allows you to perform these kinds of cleanups without having to write any regular expressions.
|
1103
|
+
|
1104
|
+
The lone argument is a string of the characters you want to remove. By default, `strip_all` will remove dashes `-` and underscores `_`.
|
1105
|
+
|
1106
|
+
```ruby
|
1107
|
+
'___foo___'.strip_all
|
1108
|
+
# => 'foo'
|
1109
|
+
|
1110
|
+
'---foo---'.strip_all
|
1111
|
+
# => 'foo'
|
1112
|
+
```
|
1113
|
+
|
1114
|
+
Note that the argument is processed as a **regex group** (your argument ends up inside of a regex `[]`). This means we evaluate the individual characters of the argument, not an explicit character sequence. You do not need spaces between the characters.
|
1115
|
+
|
1116
|
+
```ruby
|
1117
|
+
'__-_--foo--_-__'.strip_all
|
1118
|
+
# => 'foo'
|
1119
|
+
|
1120
|
+
'123foo123'.strip_all('321')
|
1121
|
+
# => 'foo'
|
1122
|
+
|
1123
|
+
'xXxXfooXxXx'.strip_all('XYZx')
|
1124
|
+
# => 'foo'
|
1125
|
+
```
|
1126
|
+
|
1127
|
+
Case-sensitivity still applies.
|
1128
|
+
|
1129
|
+
```ruby
|
1130
|
+
'ABCfooABC'.strip_all('abc')
|
1131
|
+
# => 'ABCfooABC'
|
1132
|
+
```
|
1133
|
+
|
1134
|
+
`strip_all` is intended to be a drop-in enhancement of `strip`, and will therefore always remove whitespace and newlines, even when providing your own set of characters.
|
1135
|
+
|
1136
|
+
```ruby
|
1137
|
+
"//// foo ////\n".strip_all('/')
|
1138
|
+
# => 'foo'
|
1139
|
+
```
|
1140
|
+
|
1141
|
+
Everything passed in is escaped by default, so you don't have to worry about symbols.
|
1142
|
+
|
1143
|
+
```ruby
|
1144
|
+
'/[a|valid|regex]+/'.strip_all('/[]+|')
|
1145
|
+
# => 'a|valid|regex'
|
1146
|
+
|
1147
|
+
# The | pipes are still present because they are not leading or trailing in this string.
|
1148
|
+
# Remember, we're enhancing the strip method.
|
1149
|
+
```
|
1150
|
+
|
1151
|
+
The one exception is when you pass in regex character ranges: `0-9`, `a-z`, and `A-Z`. Those will be read as expressions to capture all numbers, all lowercase or all uppercase letters, respectively.
|
1152
|
+
|
1153
|
+
```ruby
|
1154
|
+
'0123456789 foo 9876543210'.strip_all('0-9')
|
1155
|
+
# => 'foo'
|
1156
|
+
|
1157
|
+
'FOO 314 BARBAZ'.strip_all('A-Z')
|
1158
|
+
# => '314'
|
1159
|
+
|
1160
|
+
'hello--314--world'.strip_all('a-z')
|
1161
|
+
# => '--314--'
|
1162
|
+
|
1163
|
+
'hello--314--world'.strip_all('a-z-') # note the extra dash at the end
|
1164
|
+
# => '314'
|
1165
|
+
|
1166
|
+
# you can really shoot yourself in the foot if you're not careful
|
1167
|
+
'hello world'.strip_all('a-z')
|
1168
|
+
# => ''
|
1169
|
+
|
1170
|
+
'abcdefghijklm foo123 nopqrstuvwxyz'.strip_all('a-z0-9')
|
1171
|
+
# => ''
|
1172
|
+
```
|
1173
|
+
|
1174
|
+
##### Variants
|
1175
|
+
|
1176
|
+
We provide the same set of associated methods as `strip`.
|
1177
|
+
|
1178
|
+
- **`lstrip_all`** removes only leading characters
|
1179
|
+
- **`rstrip_all`** removes only trailing characters
|
1180
|
+
- All three have bang variants -- **`strip_all!`**, **`lstrip_all!`**, and **`rstrip_all!`** -- that perform the replacement in place, rather than returning a copy.
|
1181
|
+
|
1182
|
+
## Bug Reports
|
1183
|
+
|
1184
|
+
[Drop us a line in the issues section](https://github.com/forgecrafted/finishing_moves/issues).
|
1185
|
+
|
1186
|
+
**Be sure to include some sample code that reproduces the problem.**
|
1187
|
+
|
638
1188
|
## Add your own finisher!
|
639
1189
|
|
640
1190
|
1. Fork this repo
|
@@ -648,13 +1198,12 @@ We'll take pull requests on those too. Bonus karma points if you apply the refer
|
|
648
1198
|
|
649
1199
|
## Credits
|
650
1200
|
|
651
|
-
![forge software](http://www.forgecrafted.com/logo.png)
|
1201
|
+
[![forge software](http://www.forgecrafted.com/logo.png)](http://www.forgecrafted.com)
|
652
1202
|
|
653
1203
|
Finishing Moves is maintained and funded by [Forge Software (forgecrafted.com)](http://www.forgecrafted.com)
|
654
1204
|
|
655
|
-
If you like our code, please give us a hollar if your company needs outside pro's who write
|
1205
|
+
If you like our code, please give us a hollar if your company needs outside pro's who can write good code AND run servers at the same time!
|
656
1206
|
|
657
1207
|
## License
|
658
1208
|
|
659
|
-
Finishing Moves is Copyright
|
660
|
-
redistributed under the terms specified in the LICENSE file.
|
1209
|
+
Finishing Moves is Copyright Forge Software, LLC. It is free software, and may be redistributed under the terms specified in the LICENSE file.
|