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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7edf17bb22f39f2a5ef98ff9a636605c0dfc0772
4
- data.tar.gz: 9f60832e76dfa4d8738844398bfef705dc76a059
3
+ metadata.gz: 42ea93a2ee588f125b572cf38ae4c1498d143bdb
4
+ data.tar.gz: 6a5f4e469d4342896ee4c01af854477f58d95537
5
5
  SHA512:
6
- metadata.gz: 89e13ebcf8b735570055a2d1a3270989b9424f2c47947c1f1c59af6451c1c2fa6ebc6b2ac92d93160bc1154738db2891110e269143deaae90173eee0498a1cdb
7
- data.tar.gz: 16e94f9abb4f914aaff7b37cacc9734ae07269a52d2eae05848648ab9e67e4833cc97a662cb24c629f541fcfd6b783db40f5eddb906f77772d05e53481ce442a
6
+ metadata.gz: e3743c09cf0ad5bbcbb40c9ab99434988150648f0b624fde2c2dfbdff11e694081f4a571d731aba7f5b555e4fb17f39f90580ffe0aaf15b7bd63b8349cbcda07
7
+ data.tar.gz: b3f8d3f73fcf940be756c60132f042cc38530a59febb382610d3a918a859a2f97e1dcac2bb8c0de5b1a353cc8a506f6d67872aea78d345108a18a849b11ecd81
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- finishing_moves (0.1.1)
4
+ finishing_moves (0.1.2)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
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
- ##### Aliases
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 us)
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
  ```
@@ -1,3 +1,3 @@
1
1
  module FinishingMoves
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  end
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(method_chain{a.b.c.hello}).to eq nil
19
+ expect(nil_chain{a.b.c.hello}).to eq nil
20
20
  a = nil
21
- expect(method_chain{a.b.c.hello}).to eq nil
21
+ expect(nil_chain{a.b.c.hello}).to eq nil
22
22
 
23
- expect( chain(true) { bogus_variable } ).to equal true
24
- expect( chain(false) { bogus_variable } ).to equal false
25
- expect( chain('gotcha!') { bogus_variable } ).to eq 'gotcha!'
26
- expect( chain('gotcha!') { params[:bogus_key] } ).to eq 'gotcha!'
27
- expect( chain('gotcha!') { params[:foo] } ).to eq 'bar'
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 classes
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.1.2
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: 2014-12-23 00:00:00.000000000 Z
12
+ date: 2015-01-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rb-readline