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 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
- * App structure matches the mental map of your business
25
- * Testable coponents
26
- * Faster ramp up time for new team members
27
- * Easily handles changing requirements
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
- ### Process Modeling
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
- More soon. Stay tuned...
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
@@ -60,7 +60,7 @@ module Hero
60
60
  def publish
61
61
  value = [name]
62
62
  steps.each_with_index do |step, index|
63
- value << "#{(index + 1).to_s.rjust(3)}. #{step.keys.first}"
63
+ value << "#{(index + 1).to_s.rjust(3)}. #{step.first}"
64
64
  end
65
65
  value.join("\n")
66
66
  end
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
- @steps ||= {}
51
+ steps.delete_if { |s| s.first == name }
16
52
  step ||= block if block_given?
17
- @steps[name] = { :step => step, :index => @steps.length }
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.keys.first}, Context: #{context.inspect}, Options: #{options.inspect}"
66
+ Hero.logger.info "HERO Formula: #{formula_name}, Step: #{step.first}, Context: #{context.inspect}, Options: #{options.inspect}"
24
67
  end
25
- step.values.first.call(context, options)
68
+ step.last.call(context, options)
26
69
  end
27
70
  end
28
71
 
@@ -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].keys.first, :one
12
- assert_equal o.steps[0].values.first, step
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].keys.first, :one
23
- assert_equal o.steps[0].values.first, step2
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].keys.first, :two
35
- assert_equal o.steps[1].keys.first, :three
36
- assert_equal o.steps[2].keys.first, :four
37
- assert_equal o.steps[3].keys.first, :one
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
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-24 00:00:00.000000000 Z
12
+ date: 2012-08-25 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: ! ' Simplify your apps with Hero.
15
15