hero 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +143 -6
- data/lib/hero.rb +7 -0
- data/lib/hero/formula.rb +1 -1
- data/lib/hero/observer.rb +49 -6
- data/spec/observer_spec.rb +8 -8
- metadata +2 -2
data/README.md
CHANGED
@@ -21,12 +21,12 @@ One that evolved from the real world with concrete use cases and actual producti
|
|
21
21
|
|
22
22
|
## Why Hero?
|
23
23
|
|
24
|
-
*
|
25
|
-
*
|
26
|
-
*
|
27
|
-
*
|
24
|
+
* It matches the mental map of your business requirements
|
25
|
+
* It produces testable components
|
26
|
+
* It easily handles changing requirements
|
27
|
+
* It reduces the ramp up time for new team members
|
28
28
|
|
29
|
-
|
29
|
+
## Process Modeling
|
30
30
|
|
31
31
|
The problem has always been: **How do you effectively model a business process within your app?**
|
32
32
|
|
@@ -40,4 +40,141 @@ these processes into managable chunks. And the best part... the components can b
|
|
40
40
|
|
41
41
|
---
|
42
42
|
|
43
|
-
|
43
|
+
## Quick Start
|
44
|
+
|
45
|
+
```bash
|
46
|
+
gem install hero
|
47
|
+
```
|
48
|
+
|
49
|
+
Lets model a business process for collecting the top news stories from Hacker News, Reddit, & Google and then emailing the results to someone.
|
50
|
+
|
51
|
+
Gather News
|
52
|
+
|
53
|
+
- Get news from Hacker News
|
54
|
+
- Get news from Reddit
|
55
|
+
- Get news from Google
|
56
|
+
- Email Results
|
57
|
+
|
58
|
+
Now that we have the basic requirements, lets model it with Hero.
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
Hero::Formula[:gather_news].add_step :hacker_news do |context|
|
62
|
+
# make api call
|
63
|
+
# parse results
|
64
|
+
# append results to context
|
65
|
+
end
|
66
|
+
|
67
|
+
Hero::Formula[:gather_news].add_step :reddit do |context|
|
68
|
+
# make api call
|
69
|
+
# parse results
|
70
|
+
# append results to context
|
71
|
+
end
|
72
|
+
|
73
|
+
Hero::Formula[:gather_news].add_step :google do |context|
|
74
|
+
# make api call
|
75
|
+
# parse results
|
76
|
+
# append results to context
|
77
|
+
end
|
78
|
+
|
79
|
+
Hero::Formula[:gather_news].add_step :email do |context|
|
80
|
+
# format news for email
|
81
|
+
# compose the email
|
82
|
+
# send the email
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
This looks surprising similar to the requirements.
|
87
|
+
In fact we can publish the specification directly from Hero.
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
puts Hero::Formula[:gather_news].publish
|
91
|
+
|
92
|
+
# => gather_news
|
93
|
+
# 1. hacker_news
|
94
|
+
# 2. reddit
|
95
|
+
# 3. google
|
96
|
+
# 4. email
|
97
|
+
```
|
98
|
+
|
99
|
+
Pretty slick.
|
100
|
+
Now... lets run the process.
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
news = {}
|
104
|
+
Hero::Formula[:gather_news].run(news)
|
105
|
+
```
|
106
|
+
|
107
|
+
And we're done.
|
108
|
+
|
109
|
+
### Key take aways
|
110
|
+
|
111
|
+
- **The implementation aligns perfectly with the requirements.**
|
112
|
+
*This means that developers and business folks can talk the same lingo.*
|
113
|
+
|
114
|
+
- **The formula is composed of smaller steps that are interchangable.**
|
115
|
+
*This means we are poised for changing requirements.*
|
116
|
+
|
117
|
+
- **Each step implements the interface `def call(context)`**
|
118
|
+
*This means we can create step classes to simplify the app structure.*
|
119
|
+
|
120
|
+
## Next Steps
|
121
|
+
|
122
|
+
As our app grows in complexity, we should change the steps from blocks to classes.
|
123
|
+
Here's an example.
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
# this
|
127
|
+
Hero::Formula[:gather_news].add_step :hacker_news do |context|
|
128
|
+
# make api call
|
129
|
+
# parse results
|
130
|
+
# append results to context
|
131
|
+
end
|
132
|
+
|
133
|
+
# changes to this
|
134
|
+
module GatherNews
|
135
|
+
class HackerNews
|
136
|
+
|
137
|
+
def call(context)
|
138
|
+
# make api call
|
139
|
+
# parse results
|
140
|
+
# append results to context
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
Hero::Formula[:gather_news].add_step GatherNews::HackerNews.new
|
147
|
+
```
|
148
|
+
|
149
|
+
We should also create a directory structure that maps to the business process.
|
150
|
+
Something like this.
|
151
|
+
|
152
|
+
```bash
|
153
|
+
|-app
|
154
|
+
|-formulas
|
155
|
+
|-gather_news
|
156
|
+
|-hacker_news.rb
|
157
|
+
|-reddit.rb
|
158
|
+
|-google.rb
|
159
|
+
|-email.rb
|
160
|
+
```
|
161
|
+
|
162
|
+
We also need an initializer to set the formula up.
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
# app/initializer.rb
|
166
|
+
Hero::Formula[:gather_news].add_step GatherNews::HackerNews.new
|
167
|
+
Hero::Formula[:gather_news].add_step GatherNews::Reddit.new
|
168
|
+
Hero::Formula[:gather_news].add_step GatherNews::Google.new
|
169
|
+
Hero::Formula[:gather_news].add_step GatherNews::Email.new
|
170
|
+
```
|
171
|
+
|
172
|
+
Now we have a well structured application that is ready to grow.
|
173
|
+
Notice how well organized everything is.
|
174
|
+
|
175
|
+
Also note that we can write tests for each step independent of anything else.
|
176
|
+
This is an important point and a powerful concept.
|
177
|
+
|
178
|
+
## Deep Cuts
|
179
|
+
|
180
|
+
Advanced usage coming soon...
|
data/lib/hero.rb
CHANGED
@@ -4,6 +4,13 @@ end
|
|
4
4
|
|
5
5
|
module Hero
|
6
6
|
class << self
|
7
|
+
|
8
|
+
# You can optionally register a Logger with Hero.
|
9
|
+
# If set, a logger.info message will be written for each step called when running Hero::Formulas.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# Hero.logger = Logger.new(STDOUT)
|
7
13
|
attr_accessor :logger
|
14
|
+
|
8
15
|
end
|
9
16
|
end
|
data/lib/hero/formula.rb
CHANGED
data/lib/hero/observer.rb
CHANGED
@@ -1,28 +1,71 @@
|
|
1
1
|
module Hero
|
2
|
+
|
3
|
+
# Hero::Observer is designed to observe Hero::Formulas.
|
4
|
+
# It executes all registered steps whenever Hero::Formula#run is invoked.
|
5
|
+
# A Hero::Formula should only have 1 Hero::Observer attached.
|
2
6
|
class Observer
|
7
|
+
|
8
|
+
# The name of the Hero::Formula being observed.
|
3
9
|
attr_reader :formula_name
|
4
10
|
|
11
|
+
# @param [Symbol, String] formula_name The name of the Hero::Formula being observed.
|
5
12
|
def initialize(formula_name)
|
6
13
|
@formula_name = formula_name
|
7
14
|
end
|
8
15
|
|
16
|
+
# @return [Array] All registered steps.
|
9
17
|
def steps
|
10
|
-
@steps ||=
|
11
|
-
@steps.sort{ |a, b| a.last[:index] <=> b.last[:index] }.map{ |k, v| { k => v[:step] } }
|
18
|
+
@steps ||= []
|
12
19
|
end
|
13
20
|
|
21
|
+
# Adds a step to be executed when the Hero::Formula is run.
|
22
|
+
#
|
23
|
+
# @example A step must implement the interface
|
24
|
+
# def call(*args)
|
25
|
+
#
|
26
|
+
# @example More specifically
|
27
|
+
# def call(context, options={})
|
28
|
+
#
|
29
|
+
# The simplest example being a Proc.
|
30
|
+
#
|
31
|
+
# @note Steps are called in the order they are added. 1st in 1st called
|
32
|
+
#
|
33
|
+
# @example
|
34
|
+
# add_step(:my_step) do |context, options|
|
35
|
+
# # logic here...
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# @example
|
39
|
+
# class MyStep
|
40
|
+
# def self.call(context, options={})
|
41
|
+
# # logic here...
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# add_step(:my_step, MyStep)
|
46
|
+
#
|
47
|
+
# @param [Symbol, String] name The name of the step.
|
48
|
+
# @param optional [Object] step The step to be executed.
|
49
|
+
# @yield optional [Object, Hash] Uses a passed block as the step to be executed.
|
14
50
|
def add_step(name, step=nil, &block)
|
15
|
-
|
51
|
+
steps.delete_if { |s| s.first == name }
|
16
52
|
step ||= block if block_given?
|
17
|
-
|
53
|
+
steps << [name, step]
|
18
54
|
end
|
19
55
|
|
56
|
+
# The callback triggered when Hero::Formula#run is invoked.
|
57
|
+
# This method runs all registered steps in order.
|
58
|
+
#
|
59
|
+
# @note A log message will be written to Hero.logger for each step that is called if Hero.logger has been set.
|
60
|
+
#
|
61
|
+
# @param [Object] context The context to be passed to each step.
|
62
|
+
# @param [Hash] options An option Hash to be passed to each step.
|
20
63
|
def update(context, options={})
|
21
64
|
steps.each do |step|
|
22
65
|
if Hero.logger
|
23
|
-
Hero.logger.info "HERO Formula: #{formula_name}, Step: #{step.
|
66
|
+
Hero.logger.info "HERO Formula: #{formula_name}, Step: #{step.first}, Context: #{context.inspect}, Options: #{options.inspect}"
|
24
67
|
end
|
25
|
-
step.
|
68
|
+
step.last.call(context, options)
|
26
69
|
end
|
27
70
|
end
|
28
71
|
|
data/spec/observer_spec.rb
CHANGED
@@ -8,8 +8,8 @@ describe "Hero::Observer instance" do
|
|
8
8
|
o = Hero::Observer.new(:example)
|
9
9
|
o.add_step(:one, step)
|
10
10
|
assert_equal o.steps.length, 1
|
11
|
-
assert_equal o.steps[0].
|
12
|
-
assert_equal o.steps[0].
|
11
|
+
assert_equal o.steps[0].first, :one
|
12
|
+
assert_equal o.steps[0].last, step
|
13
13
|
end
|
14
14
|
|
15
15
|
it "should support properly handle a double add" do
|
@@ -19,8 +19,8 @@ describe "Hero::Observer instance" do
|
|
19
19
|
o.add_step(:one, step1)
|
20
20
|
o.add_step(:one, step2)
|
21
21
|
assert_equal o.steps.length, 1
|
22
|
-
assert_equal o.steps[0].
|
23
|
-
assert_equal o.steps[0].
|
22
|
+
assert_equal o.steps[0].first, :one
|
23
|
+
assert_equal o.steps[0].last, step2
|
24
24
|
end
|
25
25
|
|
26
26
|
it "should properly sort steps based on the order they were added" do
|
@@ -31,9 +31,9 @@ describe "Hero::Observer instance" do
|
|
31
31
|
o.add_step(:four) {}
|
32
32
|
o.add_step(:one) {}
|
33
33
|
assert_equal o.steps.length, 4
|
34
|
-
assert_equal o.steps[0].
|
35
|
-
assert_equal o.steps[1].
|
36
|
-
assert_equal o.steps[2].
|
37
|
-
assert_equal o.steps[3].
|
34
|
+
assert_equal o.steps[0].first, :two
|
35
|
+
assert_equal o.steps[1].first, :three
|
36
|
+
assert_equal o.steps[2].first, :four
|
37
|
+
assert_equal o.steps[3].first, :one
|
38
38
|
end
|
39
39
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hero
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-08-
|
12
|
+
date: 2012-08-25 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: ! ' Simplify your apps with Hero.
|
15
15
|
|