finishing_moves 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
[](http://badge.fury.io/rb/finishing_moves)
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
[](https://travis-ci.org/forgecrafted/finishing_moves.svg?branch=master)
|
|
4
|
+
[](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
|
[](http://www.forgecrafted.com)
|