hero 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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