finishing_moves 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +77 -6
- data/lib/finishing_moves/version.rb +1 -1
- data/spec/object_spec.rb +34 -8
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 42ea93a2ee588f125b572cf38ae4c1498d143bdb
|
4
|
+
data.tar.gz: 6a5f4e469d4342896ee4c01af854477f58d95537
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e3743c09cf0ad5bbcbb40c9ab99434988150648f0b624fde2c2dfbdff11e694081f4a571d731aba7f5b555e4fb17f39f90580ffe0aaf15b7bd63b8349cbcda07
|
7
|
+
data.tar.gz: b3f8d3f73fcf940be756c60132f042cc38530a59febb382610d3a918a859a2f97e1dcac2bb8c0de5b1a353cc8a506f6d67872aea78d345108a18a849b11ecd81
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -195,7 +195,6 @@ else
|
|
195
195
|
end
|
196
196
|
|
197
197
|
# with nil_chain, we get a nice one liner
|
198
|
-
|
199
198
|
var = nil_chain(:default_value) { my_hash[:foo] }
|
200
199
|
|
201
200
|
# What if the default value is coming from somewhere else?
|
@@ -208,9 +207,7 @@ var = nil_chain(Geomancer.reset_ley_lines) { summon_fel_beast[:step_3].scry }
|
|
208
207
|
# Geomancer.reset_ley_lines if it's not
|
209
208
|
```
|
210
209
|
|
211
|
-
|
212
|
-
|
213
|
-
`nil_chain` is aliased to `chain` for more brevity, and `method_chain` for alternative clarity.
|
210
|
+
`nil_chain` is aliased to `method_chain` for alternative clarity.
|
214
211
|
|
215
212
|
#### `Object#bool_chain`
|
216
213
|
|
@@ -327,7 +324,7 @@ class_exists? DefinitelyFakeClass
|
|
327
324
|
# => NameError: uninitialized constant DefinitelyFakeClass
|
328
325
|
|
329
326
|
class_exists? :DefinitelyFakeClass
|
330
|
-
# => false (at least it better be; if you actually use this name, I will find you...)
|
327
|
+
# => false (at least it better be; if you *actually* use this name, I will find you...)
|
331
328
|
```
|
332
329
|
|
333
330
|
#### `Object#not_nil?`
|
@@ -343,6 +340,80 @@ nil.not_nil?
|
|
343
340
|
|
344
341
|
Much better. Now pass me another PBR and my fedora.
|
345
342
|
|
343
|
+
#### `Object#cascade`
|
344
|
+
|
345
|
+
This method is designed to facilitate a set of consecutive, mutating actions which may be interrupted at multiple arbitrary points. In pseudo-code, the logic we're trying to write looks like this:
|
346
|
+
|
347
|
+
1. Begin stepwise process.
|
348
|
+
2. Set `[values]` to a default starting state.
|
349
|
+
3. If `[first requirement]` is not met, bail out.
|
350
|
+
4. Perform steps that require `[first requirement]`, possibly mutating `[values]`.
|
351
|
+
5. If [next requirement] is not met, bail out.
|
352
|
+
6. Perform steps that require `[first requirement]`, possibly mutating `[values]` again.
|
353
|
+
7. (Repeat for as many steps as necessary.)
|
354
|
+
8. End stepwise process.
|
355
|
+
9. Perform follow-up action(s) based on resulting `[values]`.
|
356
|
+
|
357
|
+
A common real-world scenario would be a login approval process. Here's a contrived Rails-y sample:
|
358
|
+
|
359
|
+
```ruby
|
360
|
+
cascade do
|
361
|
+
logged_in = false
|
362
|
+
# not doing anything if they didn't provide creds
|
363
|
+
break if params['username'].nil? || params['password'].nil?
|
364
|
+
# ok, got creds, do they exist?
|
365
|
+
user = User.find_by username: params['username']
|
366
|
+
# does the user exist?
|
367
|
+
break if user.nil?
|
368
|
+
# does the password match?
|
369
|
+
break if user.validate_password(params['password'])
|
370
|
+
# maybe the user account is banned?
|
371
|
+
break if user.banned?
|
372
|
+
# everything looks good, let's do it
|
373
|
+
login user
|
374
|
+
logged_in = true
|
375
|
+
end
|
376
|
+
|
377
|
+
if logged_in
|
378
|
+
# additional follow-up steps for authenticated users
|
379
|
+
else
|
380
|
+
# display error message, log the failed attempt, whatever
|
381
|
+
end
|
382
|
+
```
|
383
|
+
|
384
|
+
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.
|
385
|
+
|
386
|
+
###### *"Why not just shove the logic into a method and use `return` instead of `break`?"*
|
387
|
+
|
388
|
+
You should absolutely use methods if it makes sense. The example above is probably best handled by its own method, given it's complexity and the likelihood that you'll want to run tests on it.
|
389
|
+
|
390
|
+
`cascade` is ideal for small sets of logic, where you're *already* inside a method and further breakout is just silly.
|
391
|
+
|
392
|
+
To illustrate, here's an actual sample from a real project:
|
393
|
+
|
394
|
+
```ruby
|
395
|
+
class ReportsController
|
396
|
+
|
397
|
+
before_action :define_search_params, only: :run_report
|
398
|
+
|
399
|
+
def define_search_params
|
400
|
+
# Set the report type
|
401
|
+
# defaults to medical, skip if building a dismissals report
|
402
|
+
cascade do
|
403
|
+
@part = :medical
|
404
|
+
break if @report == :dismissals
|
405
|
+
@part = params[:part].to_sym if params[:part].to_sym.in? APP.enum.part.to_hash
|
406
|
+
end
|
407
|
+
|
408
|
+
# ...
|
409
|
+
end
|
410
|
+
|
411
|
+
end
|
412
|
+
```
|
413
|
+
|
414
|
+
It's overkill to break that bit of logic out into another method. We could have alternatively used nested `if` statements, but the vertically aligned codes reads better, in my opinion.
|
415
|
+
|
416
|
+
|
346
417
|
### Extensions to `Hash`
|
347
418
|
|
348
419
|
#### `Hash#delete!`
|
@@ -445,7 +516,7 @@ For consistency, we added matching methods to `Float` and `BigDecimal` that simp
|
|
445
516
|
# => ArgumentError: Cannot get length: "12356469.987" is not an integer
|
446
517
|
|
447
518
|
1265437718438866624512.123.class.name
|
448
|
-
# => "Float" (it's really BigDecimal, trust
|
519
|
+
# => "Float" (it's really BigDecimal, trust me)
|
449
520
|
1265437718438866624512.123.length
|
450
521
|
# => ArgumentError: Cannot get length: "1.2654377184388666e+21" is not an integer
|
451
522
|
```
|
data/spec/object_spec.rb
CHANGED
@@ -16,15 +16,18 @@ describe Object do
|
|
16
16
|
a = A.new b
|
17
17
|
expect(a.b.c.hello).to eq "Hello, world!"
|
18
18
|
b.c = nil
|
19
|
-
expect(
|
19
|
+
expect(nil_chain{a.b.c.hello}).to eq nil
|
20
20
|
a = nil
|
21
|
-
expect(
|
21
|
+
expect(nil_chain{a.b.c.hello}).to eq nil
|
22
22
|
|
23
|
-
expect(
|
24
|
-
expect(
|
25
|
-
expect(
|
26
|
-
expect(
|
27
|
-
expect(
|
23
|
+
expect( nil_chain(true) { bogus_variable } ).to equal true
|
24
|
+
expect( nil_chain(false) { bogus_variable } ).to equal false
|
25
|
+
expect( nil_chain('gotcha!') { bogus_variable } ).to eq 'gotcha!'
|
26
|
+
expect( nil_chain('gotcha!') { params[:bogus_key] } ).to eq 'gotcha!'
|
27
|
+
expect( nil_chain('gotcha!') { params[:foo] } ).to eq 'bar'
|
28
|
+
|
29
|
+
# alias
|
30
|
+
expect(method_chain{bogus_variable}).to eq nil
|
28
31
|
end
|
29
32
|
|
30
33
|
it "#bool_chain" do
|
@@ -68,9 +71,17 @@ describe Object do
|
|
68
71
|
expect(user.same_as(:FACELESS_ONE)).to eq false
|
69
72
|
end
|
70
73
|
|
74
|
+
it "#cascade" do
|
75
|
+
expect(test_cascade).to be_nil
|
76
|
+
expect(test_cascade('step1')).to eq 1
|
77
|
+
expect(test_cascade('step2')).to eq 2
|
78
|
+
expect(test_cascade('step3')).to eq 3
|
79
|
+
expect(test_cascade('foobar')).to eq 4
|
80
|
+
end
|
81
|
+
|
71
82
|
end
|
72
83
|
|
73
|
-
# test
|
84
|
+
# some small test fixtures
|
74
85
|
|
75
86
|
class A
|
76
87
|
attr_accessor :b
|
@@ -103,3 +114,18 @@ class User
|
|
103
114
|
handle.to_s
|
104
115
|
end
|
105
116
|
end
|
117
|
+
|
118
|
+
def test_cascade(trigger = nil)
|
119
|
+
ultimate_value = nil
|
120
|
+
cascade do
|
121
|
+
break if trigger.nil?
|
122
|
+
ultimate_value = 1
|
123
|
+
break if trigger == 'step1'
|
124
|
+
ultimate_value = 2
|
125
|
+
break if trigger == 'step2'
|
126
|
+
ultimate_value = 3
|
127
|
+
break if trigger == 'step3'
|
128
|
+
ultimate_value = 4
|
129
|
+
end
|
130
|
+
return ultimate_value
|
131
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: finishing_moves
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Frank Koehl
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2015-01-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rb-readline
|