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.
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 *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
+ 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 within the method, e.g. avoid formatting output, mutating values, and long conditional logic flows.
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
- ## Current Finishers
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
- #### `Object#bool_chain`
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
- If you read about `nil_chain`'s custom return value, you know that you can do this explicitly too. This shortcut just saves some typing.
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
- #### `Object#not_nil?`
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
- ```ruby
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 `[first requirement]`, possibly mutating `[values]` again.
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
- A common real-world scenario would be a login approval process. Here's a contrived Rails-y sample:
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. The example above is probably best handled by its own method, given it's complexity and the likelihood that you'll want to run tests on it.
340
+ You should absolutely use methods if it makes sense!
389
341
 
390
- `cascade` is ideal for small sets of logic, where you're *already* inside a method and further breakout is just silly.
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 an actual sample from a real project:
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
- # Set the report type
401
- # defaults to medical, skip if building a dismissals report
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
- @part = :medical
362
+ if @report == :ongoing
363
+ @category = :drug
364
+ break
365
+ end
366
+ @category = :medical
404
367
  break if @report == :dismissals
405
- @part = params[:part].to_sym if params[:part].to_sym.in? APP.enum.part.to_hash
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
- It's overkill to break that bit of logic out into another method. We could have alternatively used nested `if` statements, but the vertically aligned codes reads better, in my opinion.
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
- ### `Fixnum#length` and `Bignum#length`
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 one for strings, integers, and nil.
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 damn-good code AND run servers at the same time!
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 20XX (that means "forever") Forge Software, LLC. It is free software, and may be
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.