hero 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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: