hero 0.0.1 → 0.0.2

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 ADDED
@@ -0,0 +1,174 @@
1
+ # Hero
2
+
3
+ ## Its a bird, its a plane, its... its... my Hero
4
+
5
+ Ever wish that you could unwind the spaghetti and get out of the corner you've been backed into?
6
+
7
+ ### Hero is here to save the day
8
+
9
+ I've seen my share of poor app structure.
10
+ Hell, I wrote most of it.
11
+ Whether is fat controllers, giant models with mystery callbacks, or a junk drawer lib directory.
12
+
13
+ The question remains. **Where do I put my business logic?**
14
+
15
+ Finally... an answer that might even make DHH proud.
16
+ One that evolved from the real world with concrete use cases and actual production code.
17
+
18
+ ## Process Modeling
19
+
20
+ The problem has always been: How to effectively model a business process within your app.
21
+
22
+ Things start simply enough but eventually edge cases force *gothcas* into
23
+ various libs, modules, and classes. Before you know you it,
24
+ you have a lump of spaghetti that's difficult to maintain and even harder to improve.
25
+
26
+ ### Enter Hero
27
+
28
+ Hero provides a simple pattern that encourages you to
29
+ <a href="http://en.wikipedia.org/wiki/Decomposition_(computer_science)">decompose</a>
30
+ business processes into managable chunks.
31
+
32
+ And... the best part is, they are easily tested.
33
+
34
+ ---
35
+
36
+ Here's an example.
37
+ Assume we have a Rails app that needs to support logins.
38
+
39
+ Our implementation might look something like this.
40
+
41
+ ```ruby
42
+ # app/controllers/logins_controller.rb
43
+ class LoginsController < ApplicationController
44
+
45
+ def create
46
+ if user = User.authenticate(params[:username], params[:password])
47
+ session[:current_user_id] = user.id
48
+ redirect_to root_url
49
+ end
50
+ end
51
+
52
+ def destroy
53
+ @_current_user = session[:current_user_id] = nil
54
+ redirect_to root_url
55
+ end
56
+
57
+ end
58
+ ```
59
+
60
+ ```ruby
61
+ # app/controllers/application_controller.rb
62
+ class ApplicationController < ActionController::Base
63
+ before_filter :require_login
64
+
65
+ private
66
+
67
+ def require_login
68
+ unless logged_in?
69
+ flash[:error] = "You must be logged in to access this section"
70
+ redirect_to new_login_url # halts request cycle
71
+ end
72
+ end
73
+
74
+ def logged_in?
75
+ !!current_user
76
+ end
77
+
78
+ def current_user
79
+ @_current_user ||= session[:current_user_id] &&
80
+ User.find_by_id(session[:current_user_id])
81
+ end
82
+
83
+ end
84
+ ```
85
+
86
+ Hero approaches this problem differently.
87
+ It asks us to <a href="http://en.wikipedia.org/wiki/Decomposition_(computer_science)">decompose</a>
88
+ the login requirement into business processes which might look something like this.
89
+
90
+ #### Login
91
+
92
+ 1. Authenticate the user
93
+ 1. Save user info to session
94
+ 1. Send user to home page
95
+
96
+ #### Logout
97
+
98
+ 1. Remove user session
99
+ 1. Send user to home page
100
+
101
+ #### Protect Page
102
+
103
+ 1. Verify the user is logged in
104
+
105
+ Note that we just defined an [ontology](http://en.wikipedia.org/wiki/Process_ontology)
106
+ that can be used to discuss the requirement and its implementation with non developers.
107
+
108
+ Here's an example of an implementation with Hero.
109
+
110
+ ```ruby
111
+ # lib/errors.rb
112
+ class AuthenticationError < StandardError; end
113
+ class AuthorizationError < StandardError; end
114
+ ```
115
+
116
+ ```ruby
117
+ # config/initializers/login.rb
118
+ Hero::Formula[:login].add_step do |context|
119
+ user = User.authenticate(context.params[:username], context.params[:password])
120
+ raise AuthenticationError unless user
121
+ context.session[:current_user_id] = user.id
122
+ end
123
+
124
+ Hero::Formula[:logout].add_step do |context|
125
+ context.session[:current_user_id] = nil
126
+ end
127
+
128
+ Hero::Formula[:protect_page].add_step do |context|
129
+ raise AuthorizationError if context.session[:current_user_id].nil?
130
+ end
131
+ ```
132
+
133
+ ```ruby
134
+ # app/controllers/logins_controller.rb
135
+ class LoginsController < ApplicationController
136
+ rescue_from AuthenticationError { render "new" }
137
+
138
+ def create
139
+ Hero::Formula[:login].run(self)
140
+ redirect_to root_url
141
+ end
142
+
143
+ def destroy
144
+ Hero::Formula[:logout].run(self)
145
+ redirect_to root_url
146
+ end
147
+
148
+ end
149
+ ```
150
+
151
+ ```ruby
152
+ # app/controllers/application_controller.rb
153
+ class ApplicationController < ActionController::Base
154
+ before_filter :protect
155
+ rescue_from AuthorizationError, :with => :force_login
156
+
157
+ private
158
+
159
+ def protect
160
+ Hero::Formula[:protect_page].run(self)
161
+ end
162
+
163
+ def force_login
164
+ flash[:error] = "You must be logged in to access this section"
165
+ redirect_to new_login_url
166
+ end
167
+
168
+ end
169
+ ```
170
+
171
+ I know what you're thinking, and you're right.
172
+ This doesn't pass DHH's before/after test,
173
+ but lets start throwing edge cases at it and see what happens.
174
+
@@ -0,0 +1,52 @@
1
+ require 'observer'
2
+ require 'singleton'
3
+ require 'forwardable'
4
+
5
+ module Hero
6
+ class Formula
7
+ include Observable
8
+ include Singleton
9
+
10
+ class << self
11
+ extend Forwardable
12
+ def_delegator :formulas, :each, :each
13
+ def_delegator :formulas, :length, :count
14
+
15
+ def reset
16
+ @formulas = {}
17
+ end
18
+
19
+ def [](name)
20
+ formulas[name] ||= register(name)
21
+ end
22
+
23
+ def register(name)
24
+ observer = Hero::Observer.new
25
+ formula = Class.new(Hero::Formula).instance
26
+ formula.add_observer(observer)
27
+ formula.instance_eval { @observer = observer }
28
+ formulas[name] = formula
29
+ end
30
+
31
+ private
32
+
33
+ def formulas
34
+ @formulas ||= {}
35
+ end
36
+ end
37
+
38
+ def add_step(step=nil, &block)
39
+ step ||= block if block_given?
40
+ @observer.steps << step
41
+ end
42
+
43
+ def notify(target=nil, options={})
44
+ changed
45
+ notify_observers(target, options)
46
+ end
47
+
48
+ alias :run :notify
49
+
50
+ end
51
+ end
52
+
File without changes
data/lib/hero.rb CHANGED
@@ -1,52 +1,3 @@
1
- require 'observer'
2
- require 'singleton'
3
- require 'forwardable'
4
-
5
- module Hero
6
- class Formula
7
- include Observable
8
- include Singleton
9
-
10
- class << self
11
- extend Forwardable
12
- def_delegator :formulas, :each, :each
13
- def_delegator :formulas, :length, :count
14
-
15
- def reset
16
- @formulas = {}
17
- end
18
-
19
- def [](name)
20
- formulas[name]
21
- end
22
-
23
- def register(name)
24
- observer = Hero::Observer.new
25
- formula = Class.new(Hero::Formula).instance
26
- formula.add_observer(observer)
27
- formula.instance_eval { @observer = observer }
28
- formulas[name] = formula
29
- end
30
-
31
- private
32
-
33
- def formulas
34
- @formulas ||= {}
35
- end
36
- end
37
-
38
- def add_step(step=nil, &block)
39
- step ||= block if block_given?
40
- @observer.steps << step
41
- end
42
-
43
- def notify(target=nil, options={})
44
- changed
45
- notify_observers(target, options)
46
- end
47
-
48
- alias :run :notify
49
-
50
- end
1
+ Dir[File.join(File.dirname(__FILE__), "hero", "*rb")].each do |file|
2
+ require file
51
3
  end
52
-
data/spec/hero_spec.rb CHANGED
@@ -24,6 +24,12 @@ describe Hero::Formula do
24
24
  assert Hero::Formula[:test_formula].is_a? Hero::Formula
25
25
  end
26
26
 
27
+ it "should auto register formulas" do
28
+ Hero::Formula[:test_formula]
29
+ assert_equal Hero::Formula.count, 1
30
+ assert Hero::Formula[:test_formula].is_a? Hero::Formula
31
+ end
32
+
27
33
  it "should support registering N number of formulas" do
28
34
  10.times { |i| Hero::Formula.register("example_#{i}") }
29
35
  assert_equal Hero::Formula.count, 10
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.1
4
+ version: 0.0.2
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-22 00:00:00.000000000 Z
12
+ date: 2012-08-23 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: ! ' Simplify your apps with Hero.
15
15
 
@@ -20,10 +20,12 @@ executables: []
20
20
  extensions: []
21
21
  extra_rdoc_files: []
22
22
  files:
23
+ - lib/hero/formula.rb
24
+ - lib/hero/observer.rb
23
25
  - lib/hero.rb
24
- - lib/hero_observer.rb
25
26
  - Gemfile
26
27
  - Gemfile.lock
28
+ - README.md
27
29
  - spec/hero_spec.rb
28
30
  homepage: http://hopsoft.github.com/hero/
29
31
  licenses: