finishing_moves 0.3.1 → 0.4.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 -3
- data/.travis.yml +1 -1
- data/README.md +38 -1162
- data/finishing_moves.gemspec +1 -1
- data/lib/finishing_moves/array.rb +1 -0
- data/lib/finishing_moves/fiscal_logic.rb +132 -0
- data/lib/finishing_moves/string.rb +127 -127
- data/lib/finishing_moves/version.rb +1 -1
- data/provision.sh +2 -2
- data/spec/fiscal_logic_spec.rb +255 -0
- data/spec/string_spec.rb +0 -1
- metadata +7 -7
- data/.ruby-version +0 -1
- data/Gemfile.lock +0 -58
- data/lib/finishing_moves/date_and_time.rb +0 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 071f7f2b9b87bc0af4db17b2d7446280677de657
|
4
|
+
data.tar.gz: 6ae3e34b857d2c35dd6fb22051296955c95d4f26
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba0d30ea178be6cafc8df93a981557fb0087fc95a5569276577c25b33bc524eb041348f4202ea4e9aae1b6bac688aee1b14b3688b748933fb2d7dcbeec42027d
|
7
|
+
data.tar.gz: 2b4e2b32b5dbd18673e620888b2464f6c26f117d500526a804f693d53d25da322577754b2ab3928b54caac21f8411c17a56e1f3fa8b981b222b6217bf4da88a8
|
data/.gitignore
CHANGED
@@ -29,9 +29,9 @@ build/
|
|
29
29
|
|
30
30
|
# for a library or gem, you might want to ignore these files since the code is
|
31
31
|
# intended to run in multiple environments; otherwise, check them in:
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
Gemfile.lock
|
33
|
+
.ruby-version
|
34
|
+
.ruby-gemset
|
35
35
|
|
36
36
|
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
37
37
|
.rvmrc
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,19 +1,11 @@
|
|
1
1
|
# Finishing Moves
|
2
2
|
[![Gem Version](https://badge.fury.io/rb/finishing_moves.svg)](http://badge.fury.io/rb/finishing_moves)
|
3
|
-
|
4
|
-
|
3
|
+
[![Build Status](https://travis-ci.org/forgecrafted/finishing_moves.svg?branch=master)](https://travis-ci.org/forgecrafted/finishing_moves.svg?branch=master)
|
4
|
+
[![Hire Us!](https://img.shields.io/badge/biz-hire%20us!-F7931E.svg)](http://www.forgecrafted.com)
|
5
5
|
|
6
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.
|
7
7
|
|
8
|
-
In gamer terms, if standard Ruby methods are your default moves,
|
9
|
-
|
10
|
-
## Development approach
|
11
|
-
|
12
|
-
- **Never** override default Ruby behavior, only add functionality.
|
13
|
-
- Follow the Unix philosophy of *"Do one job really well."*
|
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.
|
16
|
-
- Test all the things.
|
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).
|
17
9
|
|
18
10
|
## Installation
|
19
11
|
|
@@ -27,1163 +19,50 @@ Command line
|
|
27
19
|
gem install 'finishing_moves'
|
28
20
|
```
|
29
21
|
|
30
|
-
|
31
|
-
|
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
|
-
- [`Hash#delete!`](#hashdelete)
|
40
|
-
- [`Hash#delete_each`](#hashdelete_each)
|
41
|
-
- [`Hash#delete_each!`](#hashdelete_each-1)
|
42
|
-
- [`Integer#length`](#integerlength)
|
43
|
-
- [`Boolean` Typecasting](#typecasting-to-boolean)
|
44
|
-
|
45
|
-
###### *New in 0.3.0!*
|
46
|
-
|
47
|
-
- [`Object#is_an?`](#objectis_an)
|
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)`
|
64
|
-
|
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.
|
66
|
-
|
67
|
-
##### Examples
|
68
|
-
|
69
|
-
```ruby
|
70
|
-
# foobar may have a transmogrify method...or it may not! Doooooom!
|
71
|
-
|
72
|
-
# without nil_chain, we check to make sure the method exists
|
73
|
-
|
74
|
-
foobar.transmogrify if foobar.respond_to? :transmogrify
|
75
|
-
|
76
|
-
# with nil_chain, we just do it, and kick those nil ghosts in the teeth
|
77
|
-
|
78
|
-
nil_chain{ foobar.transmogrify }
|
79
|
-
# => result of foobar.transmogrify, or nil
|
80
|
-
```
|
81
|
-
|
82
|
-
Not really saving much typing there, but how about an object assigned to a hash?
|
83
|
-
|
84
|
-
```ruby
|
85
|
-
# without nil_chain, we check to make sure the key exists
|
86
|
-
|
87
|
-
if my_hash.has_key? :foo
|
88
|
-
my_hash[:foo].do_stuff
|
89
|
-
end
|
90
|
-
|
91
|
-
# with nil_chain, things look a lot cleaner
|
92
|
-
|
93
|
-
nil_chain{ my_hash[:foo].do_stuff }
|
94
|
-
# => result of my_hash[:foo].do_stuff, or nil
|
95
|
-
```
|
96
|
-
|
97
|
-
Still pretty simple. Let's try it on a series of connected objects.
|
98
|
-
|
99
|
-
```ruby
|
100
|
-
class A
|
101
|
-
attr_accessor :b
|
102
|
-
def initialize(b)
|
103
|
-
@b = b
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
class B
|
108
|
-
attr_accessor :c
|
109
|
-
def initialize(c)
|
110
|
-
@c = c
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
class C
|
115
|
-
def hello
|
116
|
-
"Hello, world!"
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
c = C.new
|
121
|
-
b = B.new c
|
122
|
-
a = A.new b
|
123
|
-
|
124
|
-
a.b.c.hello
|
125
|
-
# => "Hello, world!"
|
126
|
-
```
|
127
|
-
|
128
|
-
Let's suppose the presence of attribute `c` is conditional. We must then check for a proper association between objects `b` and `c` before calling `hello`.
|
129
|
-
|
130
|
-
```ruby
|
131
|
-
b.c = nil
|
132
|
-
a.b.c.hello
|
133
|
-
# => NoMethodError: undefined method `hello' for nil:NilClass
|
134
|
-
|
135
|
-
a.b.c.hello unless b.c.nil? || b.c.empty?
|
136
|
-
# => nil
|
137
|
-
|
138
|
-
a.b = nil
|
139
|
-
|
140
|
-
# Now it's really getting ugly.
|
141
|
-
if !a.b.nil? && !a.b.empty?
|
142
|
-
a.b.c.hello unless b.c.nil? || b.c.empty?
|
143
|
-
end
|
144
|
-
|
145
|
-
# Imagine if we had a fourth association, or a fifth! The patterns, man!
|
146
|
-
```
|
147
|
-
|
148
|
-
Or we can just skip all that conditional nonsense.
|
149
|
-
|
150
|
-
```ruby
|
151
|
-
nil_chain{ a.b.c.hello }
|
152
|
-
# => output "Hello, world!" or nil
|
153
|
-
|
154
|
-
a = nil
|
155
|
-
nil_chain{ a.b.c.hello }
|
156
|
-
# => still just nil
|
157
|
-
```
|
158
|
-
|
159
|
-
##### Examples in Rails
|
160
|
-
|
161
|
-
We use `nil_chain` all the time in Rails projects. The A-B-C class example above was derived from a frequent use case in our models...
|
162
|
-
|
163
|
-
```ruby
|
164
|
-
# Model User has ZERO or more addresses, one of which is the primary.
|
165
|
-
# Model Address has a zip_code attribute.
|
166
|
-
|
167
|
-
user = User.find(9876)
|
168
|
-
nil_chain{ user.addresses.primary.zip_code }
|
169
|
-
# => returns nil if no addresses, or primary not set, otherwise returns zip_code
|
170
|
-
```
|
171
|
-
|
172
|
-
It also helps when dealing with optional parameters coming in from forms...
|
173
|
-
|
174
|
-
```ruby
|
175
|
-
# Somewhere in a random rails controller...
|
176
|
-
|
177
|
-
def search
|
178
|
-
case nil_chain { params[:case_state].downcase }
|
179
|
-
when 'open' then filter_only_open
|
180
|
-
when 'closed' then filter_only_closed
|
181
|
-
when 'invalid' then filter_only_invalid
|
182
|
-
when 'withdrawn' then filter_only_withdrawn
|
183
|
-
when 'canceled' then filter_only_canceled
|
184
|
-
end
|
185
|
-
# => apply a case state filter, or do nothing
|
186
|
-
end
|
187
|
-
```
|
188
|
-
|
189
|
-
Setting default values on form inputs in views...
|
190
|
-
|
191
|
-
```ruby
|
192
|
-
select_tag :date_field,
|
193
|
-
options_for_select(@dropdown_date_field, nil_chain{params[:date_field]} )
|
194
|
-
# => Sets the selected option in the dropdown if the :date_field parameter exists
|
195
|
-
```
|
196
|
-
|
197
|
-
##### Custom return value
|
198
|
-
|
199
|
-
You can change the value that `nil_chain` returns when it catches a `NoMethodError` or `NameError` exception. `nil_chain` accepts a single optional argument before the block to represent the return value. The default is `nil`, but you can set it to whatever you want.
|
200
|
-
|
201
|
-
We recently used this functionality in generating a CSV report. The client's use case required us to spit out an `'N/A'` string anytime a proper field value was missing. `nil_chain` made the adjustment easy.
|
202
|
-
|
203
|
-
```ruby
|
204
|
-
CSV.generate do |csv|
|
205
|
-
@records.each do |record| # each record represents a single line in the CSV
|
206
|
-
values = []
|
207
|
-
csv_fields_in_order.each do |field|
|
208
|
-
values << nil_chain('N/A') { record.send(field) }
|
209
|
-
# respond with a pretty value when the field is empty or invalid
|
210
|
-
end
|
211
|
-
csv << values
|
212
|
-
end
|
213
|
-
end
|
214
|
-
```
|
215
|
-
|
216
|
-
We also find this handy when doing conditional stuff based on presence/absence of a key in a hash.
|
217
|
-
|
218
|
-
```ruby
|
219
|
-
# without nil_chain
|
220
|
-
if my_hash[:foo]
|
221
|
-
# (by default, ruby returns nil when you request an unset key)
|
222
|
-
var = my_hash[:foo]
|
223
|
-
else
|
224
|
-
var = :default_value
|
225
|
-
end
|
226
|
-
|
227
|
-
# with nil_chain, we get a nice one liner
|
228
|
-
var = nil_chain(:default_value) { my_hash[:foo] }
|
229
|
-
|
230
|
-
# What if the default value is coming from somewhere else?
|
231
|
-
# What if we want to call a method directly on the hash?
|
232
|
-
# What if the ley lines are out of alignment!?
|
233
|
-
# No problem.
|
234
|
-
|
235
|
-
var = nil_chain(Geomancer.reset_ley_lines) { summon_fel_beast[:step_3].scry }
|
236
|
-
# => value of summon_fel_beast[:step_3].scry if it's set, or
|
237
|
-
# Geomancer.reset_ley_lines if it's not
|
238
|
-
```
|
239
|
-
|
240
|
-
##### Alias
|
241
|
-
|
242
|
-
`nil_chain` is aliased to `method_chain` for alternative clarity.
|
243
|
-
|
244
|
-
#### `Kernel#bool_chain`
|
245
|
-
|
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.
|
247
|
-
|
248
|
-
Following our A-B-C example above...
|
249
|
-
|
250
|
-
```ruby
|
251
|
-
bool_chain{ a.b.c.hello }
|
252
|
-
# => false
|
253
|
-
```
|
254
|
-
|
255
|
-
#### `Kernel#class_exists?`
|
256
|
-
|
257
|
-
Sure, Ruby has the `defined?` method, but the output is less than helpful when you're doing conditional flows.
|
258
|
-
|
259
|
-
```ruby
|
260
|
-
defined?(SuperSaiyan)
|
261
|
-
# => nil
|
262
|
-
|
263
|
-
require 'super_saiyan'
|
264
|
-
|
265
|
-
defined?(SuperSaiyan)
|
266
|
-
# => 'constant'
|
267
|
-
|
268
|
-
if defined?(SuperSaiyan) == 'constant'
|
269
|
-
# Power up to level 4
|
270
|
-
# But after that obtuse if-statement, I'm just too tired
|
271
|
-
end
|
272
|
-
```
|
273
|
-
|
274
|
-
`class_exists?` does exactly what you want, and provides an obvious, natural boolean response.
|
275
|
-
|
276
|
-
```ruby
|
277
|
-
class_exists? :Symbol
|
278
|
-
# => true
|
279
|
-
class_exists? :Symbology
|
280
|
-
# => false, unless you're Dan Brown
|
281
|
-
class_exists? :Rails
|
282
|
-
# => true in a Rails app
|
283
|
-
```
|
284
|
-
|
285
|
-
Because the class **might** exist, we cannot pass in the constant version of the name. You **must** use a symbol or string value.
|
286
|
-
|
287
|
-
```ruby
|
288
|
-
class_exists? DefinitelyFakeClass
|
289
|
-
# => NameError: uninitialized constant DefinitelyFakeClass
|
290
|
-
|
291
|
-
class_exists? :DefinitelyFakeClass
|
292
|
-
# => false (at least it better be; if you *actually* use this name, I will find you...)
|
293
|
-
```
|
294
|
-
|
295
|
-
#### `Kernel#cascade`
|
296
|
-
|
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:
|
298
|
-
|
299
|
-
1. Begin stepwise process.
|
300
|
-
2. Set `[values]` to a default starting state.
|
301
|
-
3. If `[first requirement]` is not met, bail out.
|
302
|
-
4. Perform steps that require `[first requirement]`, possibly mutating `[values]`.
|
303
|
-
5. If `[next requirement]` is not met, bail out.
|
304
|
-
6. Perform steps that require `[next requirement]`, possibly mutating `[values]` again.
|
305
|
-
7. (Repeat for as many steps as necessary.)
|
306
|
-
8. End stepwise process.
|
307
|
-
9. Perform follow-up action(s) based on resulting `[values]`.
|
308
|
-
|
309
|
-
Here's a contrived Rails-y sample of a login approval process:
|
310
|
-
|
311
|
-
```ruby
|
312
|
-
cascade do
|
313
|
-
logged_in = false
|
314
|
-
# not doing anything if they didn't provide creds
|
315
|
-
break if params['username'].nil? || params['password'].nil?
|
316
|
-
# ok, got creds, do they exist?
|
317
|
-
user = User.find_by username: params['username']
|
318
|
-
# does the user exist?
|
319
|
-
break if user.nil?
|
320
|
-
# does the password match?
|
321
|
-
break if user.validate_password(params['password'])
|
322
|
-
# maybe the user account is banned?
|
323
|
-
break if user.banned?
|
324
|
-
# everything looks good, let's do it
|
325
|
-
login user
|
326
|
-
logged_in = true
|
327
|
-
end
|
328
|
-
|
329
|
-
if logged_in
|
330
|
-
# additional follow-up steps for authenticated users
|
331
|
-
else
|
332
|
-
# display error message, log the failed attempt, whatever
|
333
|
-
end
|
334
|
-
```
|
335
|
-
|
336
|
-
We're using the [`loop`](http://www.ruby-doc.org/core-2.1.5/Kernel.html#method-i-loop) construct under the hood, which is what allows us to use the `break` statement as outlined in the example.
|
337
|
-
|
338
|
-
###### *"Why not just shove the logic into a method and use `return` instead of `break`?"*
|
339
|
-
|
340
|
-
You should absolutely use methods if it makes sense!
|
341
|
-
|
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.
|
343
|
-
|
344
|
-
To illustrate, here's a small real-world sample from one of our projects:
|
345
|
-
|
346
|
-
```ruby
|
347
|
-
class ReportsController < ApplicationController
|
348
|
-
|
349
|
-
before_action :define_search_params, only: :run_report
|
350
|
-
|
351
|
-
# ...
|
352
|
-
|
353
|
-
def define_search_params
|
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
|
361
|
-
cascade do
|
362
|
-
if @report == :ongoing
|
363
|
-
@category = :drug
|
364
|
-
break
|
365
|
-
end
|
366
|
-
@category = :medical
|
367
|
-
break if @report == :dismissals
|
368
|
-
@category = params[:category] if params[:category].in? allowable_categories
|
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
|
389
|
-
|
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"
|
419
|
-
end
|
420
|
-
|
421
|
-
def to_s
|
422
|
-
handle.to_s
|
423
|
-
end
|
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
|
435
|
-
```
|
436
|
-
|
437
|
-
##### Alias
|
438
|
-
|
439
|
-
`same_as` is aliased to `same_as?` for alternative clarity.
|
440
|
-
|
441
|
-
|
442
|
-
#### `Object#not_nil?`
|
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.
|
467
|
-
|
468
|
-
### Extensions to `Hash`
|
469
|
-
|
470
|
-
#### `Hash#delete!`
|
471
|
-
|
472
|
-
The normal [`Hash#delete`](http://www.ruby-doc.org/core-2.1.5/Hash.html#method-i-delete) method returns the value that's been removed from the hash, but it can be equally useful if we return the newly modified hash instead.
|
473
|
-
|
474
|
-
This approach effectively throws away the value being deleted, so don't use this when the deleted hash entry is valuable.
|
475
|
-
|
476
|
-
```ruby
|
477
|
-
power_rangers = {
|
478
|
-
:red => 'Jason Scott',
|
479
|
-
:blue => 'Billy Cranston',
|
480
|
-
:green => 'Tommy Oliver'
|
481
|
-
}
|
482
|
-
|
483
|
-
power_rangers.delete! :green
|
484
|
-
# => { :red => 'Jason Lee Scott', :blue => 'Billy Cranston' }
|
485
|
-
```
|
486
|
-
|
487
|
-
If the key is not found, the hash is returned unaltered.
|
488
|
-
|
489
|
-
```ruby
|
490
|
-
power_rangers.delete! :radiant_orchid
|
491
|
-
# => { :red => 'Jason Lee Scott', :blue => 'Billy Cranston' }
|
492
|
-
# It probably would've triggered if I included Kimberly
|
493
|
-
```
|
494
|
-
|
495
|
-
#### `Hash#delete_each`
|
496
|
-
|
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.
|
498
|
-
|
499
|
-
```ruby
|
500
|
-
mega_man_bosses = { :metal_man => 1, :bubble_man => 2, :heat_man => 3, :wood_man => 4 }
|
501
|
-
|
502
|
-
mega_man_bosses.delete_each :chill_penguin, :spark_mandrill
|
503
|
-
# => nil, and get your series straight
|
504
|
-
mega_man_bosses
|
505
|
-
# => { :metal_man => 1, :bubble_man => 2, :heat_man => 3, :wood_man => 4 }
|
506
|
-
|
507
|
-
mega_man_bosses.delete_each :metal_man
|
508
|
-
# => { :metal_man => 1 }
|
509
|
-
mega_man_bosses
|
510
|
-
# => { :bubble_man => 2, :heat_man => 3, :wood_man => 4 }
|
511
|
-
|
512
|
-
mega_man_bosses.delete_each :bubble_man, :heat_man, :wheel_gator
|
513
|
-
# => { :bubble_man => 2, :heat_man => 3 }
|
514
|
-
mega_man_bosses
|
515
|
-
# => { :wood_man => 4 }
|
516
|
-
```
|
517
|
-
|
518
|
-
#### `Hash#delete_each!`
|
519
|
-
|
520
|
-
Same logic as `delete_each`, but return the modified hash, and discard the deleted values.
|
521
|
-
|
522
|
-
Maintains parity with the contrast of `delete` vs `delete!` described above.
|
523
|
-
|
524
|
-
```ruby
|
525
|
-
mega_man_bosses = { :air_man => 5, :crash_man => 6, :flash_man => 7, :quick_man => 8 }
|
526
|
-
|
527
|
-
mega_man_bosses.delete_each! :yellow_devil, :air_man
|
528
|
-
# => { :crash_man => 6, :flash_man => 7, :quick_man => 8 }
|
529
|
-
|
530
|
-
mega_man_bosses.delete_each! :flash_man
|
531
|
-
# => { :crash_man => 6, :quick_man => 8 }
|
532
|
-
# Take out flash anytime after metal, I like to wait until I need a breather.
|
533
|
-
|
534
|
-
mega_man_bosses.delete_each! :crash_man, :quick_man
|
535
|
-
# => { }
|
536
|
-
```
|
537
|
-
|
538
|
-
### `Integer#length`
|
539
|
-
|
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.
|
541
|
-
|
542
|
-
```ruby
|
543
|
-
1.length
|
544
|
-
# => 1
|
545
|
-
9.length
|
546
|
-
# => 1
|
547
|
-
90.length
|
548
|
-
# => 2
|
549
|
-
900.length
|
550
|
-
# => 3
|
551
|
-
9000.length
|
552
|
-
# => 4
|
553
|
-
9001.length
|
554
|
-
# => OVER NINE THOUSAAAAAAND (also 4)
|
555
|
-
|
556
|
-
12356469787881584554556.class.name
|
557
|
-
# => "Bignum"
|
558
|
-
12356469787881584554556.length
|
559
|
-
# => 23
|
560
|
-
```
|
561
|
-
|
562
|
-
For consistency, we added matching methods to `Float` and `BigDecimal` that simply raise an `ArgumentError`.
|
563
|
-
|
564
|
-
```ruby
|
565
|
-
12356469.987.class.name
|
566
|
-
# => "Float"
|
567
|
-
12356469.987.length
|
568
|
-
# => ArgumentError: Cannot get length: "12356469.987" is not an integer
|
569
|
-
|
570
|
-
1265437718438866624512.123.class.name
|
571
|
-
# => "Float" (it's really BigDecimal, trust me)
|
572
|
-
1265437718438866624512.123.length
|
573
|
-
# => ArgumentError: Cannot get length: "1.2654377184388666e+21" is not an integer
|
574
|
-
```
|
575
|
-
|
576
|
-
##### Alias
|
577
|
-
|
578
|
-
`length` is aliased to `digits` for alternative clarity.
|
579
|
-
|
580
|
-
### Typecasting *to* `Boolean`
|
581
|
-
|
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.
|
583
|
-
|
584
|
-
So we made some for `String`, `Integer`, and `Nil`.
|
585
|
-
|
586
|
-
#### `String#to_bool`
|
587
|
-
|
588
|
-
Strings get analyzed and return `true` or `false` for a small set of potential values.
|
589
|
-
|
590
|
-
These comparisons are not case-sensitive.
|
591
|
-
|
592
|
-
```ruby
|
593
|
-
['1', 't', 'true', 'on', 'y', 'yes'].each do |true_string|
|
594
|
-
true_string.to_bool
|
595
|
-
# => true
|
596
|
-
|
597
|
-
true_string.upcase.to_bool
|
598
|
-
# => true
|
599
|
-
end
|
600
|
-
|
601
|
-
['0', 'f', 'false', 'off', 'n', 'no'].each do |false_string|
|
602
|
-
false_string.to_bool
|
603
|
-
# => false
|
604
|
-
|
605
|
-
false_string.upcase.to_bool
|
606
|
-
# => false
|
607
|
-
end
|
608
|
-
|
609
|
-
# empty strings and strings with only spaces evaluate to false
|
610
|
-
["", " ", " ", " "].each do |empty_string|
|
611
|
-
empty_string.to_bool
|
612
|
-
# => false
|
613
|
-
end
|
614
|
-
```
|
615
|
-
|
616
|
-
A string with anything other than these matching values will throw an error.
|
617
|
-
|
618
|
-
```ruby
|
619
|
-
["foo", "tru", "trueish", "druish", "00", "000"].each do |bad_string|
|
620
|
-
bad_string.to_bool
|
621
|
-
# => ArgumentError: invalid value for Boolean
|
622
|
-
end
|
623
|
-
```
|
624
|
-
|
625
|
-
#### `Fixnum#to_bool`
|
626
|
-
|
627
|
-
A zero is false, a one is true. That's it. Everything else throws `ArgumentError`.
|
628
|
-
|
629
|
-
```ruby
|
630
|
-
0.to_bool
|
631
|
-
# => false
|
632
|
-
|
633
|
-
1.to_bool
|
634
|
-
# => true
|
635
|
-
|
636
|
-
2.to_bool
|
637
|
-
# => ArgumentError: invalid value for Boolean: "2"
|
638
|
-
|
639
|
-
-1.to_bool
|
640
|
-
# => ArgumentError: invalid value for Boolean: "-1"
|
641
|
-
|
642
|
-
8675309.to_bool
|
643
|
-
# => ArgumentError: invalid value for Boolean: "8675309"
|
644
|
-
```
|
645
|
-
|
646
|
-
#### `NilClass#to_bool`
|
647
|
-
|
648
|
-
A nil value typecasted to a boolean is false.
|
649
|
-
|
650
|
-
```ruby
|
651
|
-
nil == false
|
652
|
-
# => false
|
653
|
-
|
654
|
-
nil.to_bool
|
655
|
-
# => false
|
656
|
-
|
657
|
-
nil.to_bool == false
|
658
|
-
# => true
|
659
|
-
```
|
660
|
-
|
661
|
-
#### `TrueClass#to_bool` and `FalseClass#to_bool`
|
662
|
-
|
663
|
-
In case your code calls `to_bool` on a variable of indeterminate type, they return what you expect.
|
664
|
-
|
665
|
-
```ruby
|
666
|
-
true.to_bool
|
667
|
-
# => true
|
668
|
-
|
669
|
-
false.to_bool
|
670
|
-
# => false
|
671
|
-
```
|
672
|
-
|
673
|
-
### Typecasting *from* `Boolean` and `Nil`
|
674
|
-
|
675
|
-
Complementing the methods to typecast boolean values coming out of data storage, we have methods to convert booleans and `nil` into integer and symbol representations.
|
676
|
-
|
677
|
-
```ruby
|
678
|
-
true.to_i
|
679
|
-
# => 1
|
680
|
-
true.to_sym
|
681
|
-
# => :true
|
682
|
-
|
683
|
-
false.to_i
|
684
|
-
# => 0
|
685
|
-
false.to_sym
|
686
|
-
# => :false
|
687
|
-
|
688
|
-
nil.to_i
|
689
|
-
# => 0 (following same logic as `NilClass#to_bool`)
|
690
|
-
nil.to_sym
|
691
|
-
# => :nil
|
692
|
-
```
|
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.
|
22
|
+
### Ruby Version
|
921
23
|
|
922
|
-
`
|
24
|
+
Tested against **`2.0.0` and above**. Probably works in `1.9.3`.
|
923
25
|
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
#
|
934
|
-
|
935
|
-
|
936
|
-
#
|
937
|
-
|
938
|
-
|
939
|
-
#
|
940
|
-
|
941
|
-
#
|
942
|
-
|
943
|
-
#
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
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
|
26
|
+
## List of Methods
|
27
|
+
##### (all documented in the [GitHub wiki](https://github.com/forgecrafted/finishing_moves/wiki))
|
28
|
+
|
29
|
+
- [`Array#to_hash_values`](https://github.com/forgecrafted/finishing_moves/wiki/Array#arrayto_hash_values)
|
30
|
+
- [`Array#to_indexed_hash`](https://github.com/forgecrafted/finishing_moves/wiki/Array#arrayto_indexed_hash)
|
31
|
+
- [`Array#to_hash_keys`](https://github.com/forgecrafted/finishing_moves/wiki/Array#arrayto_hash_keys)
|
32
|
+
- [`Enumerable#key_map`](https://github.com/forgecrafted/finishing_moves/wiki/Enumerable#enumerablekey_map)
|
33
|
+
- [`Enumerable#key_map_reduce`](https://github.com/forgecrafted/finishing_moves/wiki/Enumerable#enumerablekey_map_reduce)
|
34
|
+
- [`Hash#delete!`](https://github.com/forgecrafted/finishing_moves/wiki/Hash#hashdelete)
|
35
|
+
- [`Hash#delete_each`](https://github.com/forgecrafted/finishing_moves/wiki/Hash#hashdelete_each)
|
36
|
+
- [`Hash#delete_each!`](https://github.com/forgecrafted/finishing_moves/wiki/Hash#hashdelete_each-1)
|
37
|
+
- [`Integer#length`](https://github.com/forgecrafted/finishing_moves/wiki/Integer#integerlength)
|
38
|
+
- [`Kernel#nil_chain`](https://github.com/forgecrafted/finishing_moves/wiki/Kernel#kernelnil_chain)
|
39
|
+
- [`Kernel#cascade`](https://github.com/forgecrafted/finishing_moves/wiki/Kernel#kernelcascade)
|
40
|
+
- [`Kernel#class_exists?`](https://github.com/forgecrafted/finishing_moves/wiki/Kernel#kernelclass_exists)
|
41
|
+
- [`Object#same_as`](https://github.com/forgecrafted/finishing_moves/wiki/Object#objectsame_as)
|
42
|
+
- [`Object#not_nil?`](https://github.com/forgecrafted/finishing_moves/wiki/Object#objectnot_nil)
|
43
|
+
- [`Object#is_an?`](https://github.com/forgecrafted/finishing_moves/wiki/Object#objectis_an)
|
44
|
+
- [`String#dedupe`](https://github.com/forgecrafted/finishing_moves/wiki/String#stringdedupe)
|
45
|
+
- [`String#keyify`](https://github.com/forgecrafted/finishing_moves/wiki/String#stringkeyify)
|
46
|
+
- [`String#match?`](https://github.com/forgecrafted/finishing_moves/wiki/String#stringmatch)
|
47
|
+
- [`String#nl2br`](https://github.com/forgecrafted/finishing_moves/wiki/String#stringnl2br)
|
48
|
+
- [`String#remove_whitespace`](https://github.com/forgecrafted/finishing_moves/wiki/String#stringremove_whitespace)
|
49
|
+
- [`String#strip_all`](https://github.com/forgecrafted/finishing_moves/wiki/String#stringstrip_all)
|
50
|
+
- [Boolean Typecasting](https://github.com/forgecrafted/finishing_moves/wiki/Boolean-Typecasting)
|
1175
51
|
|
1176
|
-
|
52
|
+
## Development approach
|
1177
53
|
|
1178
|
-
-
|
1179
|
-
-
|
1180
|
-
-
|
54
|
+
- **Never** override default Ruby behavior, only add functionality.
|
55
|
+
- Follow the Unix philosophy of *"Do one job really well."*
|
56
|
+
- Minimize assumptions, e.g. avoid formatting output, mutating values, and conditional logic flows.
|
57
|
+
- Play nice with major Ruby players like Rake, Rails, and Sinatra.
|
58
|
+
- Never duplicate existing methods from Rails and the like.
|
59
|
+
- Test all the things.
|
1181
60
|
|
1182
61
|
## Bug Reports
|
1183
62
|
|
1184
63
|
[Drop us a line in the issues section](https://github.com/forgecrafted/finishing_moves/issues).
|
1185
64
|
|
1186
|
-
**Be sure to include
|
65
|
+
**Be sure to include sample code that reproduces the problem.**
|
1187
66
|
|
1188
67
|
## Add your own finisher!
|
1189
68
|
|
@@ -1193,9 +72,6 @@ We provide the same set of associated methods as `strip`.
|
|
1193
72
|
4. Repeat steps 2 and 3 until you see a brilliant luster
|
1194
73
|
5. Submit a pull request
|
1195
74
|
|
1196
|
-
###### Got a good nerdy reference for our code samples?
|
1197
|
-
We'll take pull requests on those too. Bonus karma points if you apply the reference to the specs too.
|
1198
|
-
|
1199
75
|
## Credits
|
1200
76
|
|
1201
77
|
[![forge software](http://www.forgecrafted.com/logo.png)](http://www.forgecrafted.com)
|