source/shop.rb
1# A mock shop, written to show every shape of choice the report
2# has to draw: conditions and paths, inside and outside methods.
3
4# A condition at the top level, outside any method.
VERBOSE = ENV.fetch('VERBOSE', '') == '1' || ENV.key?('DEBUG')
||001
├─ ENV.fetch('VERBOSE', '') == '1'TFF
└─ ENV.key?('DEBUG')TF
6
7# An if at the top level, outside any method.
if VERBOSE && $stdout.tty?
if001
├─ &&TFF
│ ├─ VERBOSETTF
│ └─ $stdout.tty?TF
└─ then✓
9 warn 'shop: verbose'
10end
11
12module Shop
13 # Endless methods, each a condition and nothing else.
def self.member?(user) = user[:member] && !user[:banned]
&&313
├─ user[:member]TTF
└─ !user[:banned]TF
def self.vip?(user) = member?(user) && (user[:spend] > 1000 || user[:invited])
&&0011
├─ member?(user)TTTF
└─ ||TFT
├─ user[:spend] > 1000FFT
└─ user[:invited]TF
16
17 # A long method, its paths and conditions tightly mixed.
18 def self.price(user, order)
19 items = order[:items]
fail ArgumentError, 'empty order' if items.empty?
if12
├─ items.empty?TF
└─ then✓
21
22 total = items.sum { |item| item[:price] }
23
if member?(user) && total > 50
if011
├─ &&FTF
│ ├─ member?(user)TTF
│ └─ total > 50FT
└─ then✓
25 total -= 5
26 end
27
if vip?(user)
if11
├─ vip?(user)TF
├─ then✓
└─ else✓
29 total -= 10
elsif user[:coupon] || order[:coupon]
if001
├─ ||TFT
│ ├─ user[:coupon]FFT
│ └─ order[:coupon]TF
└─ then✓✓
31 total -= 2
32 end
33
shipping = total > 100 || order[:pickup] ? 0 : 7
?:011
├─ ||TTF
│ ├─ total > 100TFF
│ └─ order[:pickup]TF
├─ ? 0✓✓
└─ : 7✓
35
unless order[:express]
unless11
├─ order[:express]TF
└─ then✓
37 shipping /= 2
38 end
39
if order[:gift] && (order[:wrap] || user[:member])
if0011
├─ &&TFTF
│ ├─ order[:gift]TTTF
│ └─ ||TFT
│ ├─ order[:wrap]TFF
│ └─ user[:member]FT
├─ then✓✓
└─ else✓✓
42 total += 3
43 else
total += 0 if order[:gift]
if01
├─ order[:gift]TF
└─ then✓
45 end
46
return total if order[:pickup]
if11
├─ order[:pickup]TF
└─ then✓
48
49 total + shipping
50 end
51
52 # A method that defines a method inside it.
53 def self.install_discounts(rates)
if rates.empty?
if11
├─ rates.empty?TF
└─ then✓
55 return
56 end
57
58 def self.discount(total, rate)
59 off = 0
if rate.positive? && total.positive?
if111
├─ &&TFF
│ ├─ rate.positive?TTF
│ └─ total.positive?TF
└─ then✓
61 off = total * rate
62 end
if off > 20
if03
├─ off > 20TF
└─ then✓
64 off = 20
65 end
66 off
67 end
68
if rates.size > 1 || rates.first > 0.5
if001
├─ ||TFT
│ ├─ rates.size > 1FFT
│ └─ rates.first > 0.5TF
└─ then✓✓
70 @tiered = true
71 end
72 end
73
74 # Choices inside a block, which runs once for each item.
75 def self.labels(items)
76 items.map do |item|
if item[:sale] && item[:price] < 10
if011
├─ &&FTF
│ ├─ item[:sale]TTF
│ └─ item[:price] < 10FT
├─ then✓
└─ else✓✓
78 'bargain'
elsif item[:sale]
if01
├─ item[:sale]TF
├─ then✓
└─ else✓
80 'sale'
81 else
82 'full price'
83 end
84 end
85 end
86
87 # A short method: one if, so its paths are its branches.
88 def self.greeting(user)
if user[:name] && !user[:name].empty?
if111
├─ &&TFF
│ ├─ user[:name]TTF
│ └─ !user[:name].empty?TF
├─ then✓
└─ else✓✓
90 "Hello #{user[:name]}"
91 else
92 'Hello'
93 end
94 end
95end
3/1700000000000000111
20FFFFFFFFFFFFFFTFF
24TTTTTTTFFFFFFFTF
28TTTFFFFTTTTFFFTF
40TFFTTFFTTFFTTFTF
47TTFTFTFTFTFTFFFT
2/3011
54FTF
69FT
2/40012
60TFTF
63TTFF