focused_controller 0.1.0 → 0.2.0

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/.travis.yml CHANGED
@@ -3,13 +3,9 @@ rvm:
3
3
  - 1.9.3
4
4
  - 1.9.2
5
5
  - 1.8.7
6
- - rbx-18mode
7
- # - rbx-19mode # Seems to hang, try again another day
8
- # Add JRuby support when poltergeist handles JRuby
6
+ - jruby-18mode
7
+ - jruby-19mode
9
8
  gemfile:
10
9
  - gemfiles/rails-3-2.gemfile
11
10
  - gemfiles/rails-3-1.gemfile
12
11
  - gemfiles/rails-3-0.gemfile
13
- before_script:
14
- - "export DISPLAY=:99.0"
15
- - "sh -e /etc/init.d/xvfb start"
data/Appraisals CHANGED
@@ -8,4 +8,5 @@ end
8
8
 
9
9
  appraise 'rails-3-0' do
10
10
  gem 'rails', '~> 3.0.0'
11
+ gem 'rack-test', '~> 0.5.7'
11
12
  end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Jonathan Leighton
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1 +1,274 @@
1
+ # Focused Controller #
2
+
1
3
  [![Build Status](https://secure.travis-ci.org/jonleighton/focused_controller.png?branch=master)](http://travis-ci.org/jonleighton/focused_controller)
4
+
5
+ Focused Controller alters Rails' conventions so that each individual action in
6
+ a controller is represented by its own class. This makes it easier to break up
7
+ the code within an action and share code between different actions.
8
+
9
+ Focused Controller also provides test helpers which enable you to write unit
10
+ tests for your controller code. This is much faster than functional testing,
11
+ and better suited to testing fine grained logic that may exist in your actions.
12
+
13
+ There is a [mailing list](http://groups.google.com/group/focused_controller)
14
+ for discussion.
15
+
16
+ ## Synopsis ##
17
+
18
+ ``` ruby
19
+ class ApplicationController
20
+ include FocusedController::Mixin
21
+ end
22
+
23
+ module PostsController
24
+ class Index < ApplicationController
25
+ expose(:posts) { Post.recent.limit(5) }
26
+ end
27
+
28
+ class New < ApplicationController
29
+ expose(:post) { Post.new }
30
+ end
31
+
32
+ class Singular < ApplicationController
33
+ expose(:post) { Post.find params[:id] }
34
+ before_filter { redirect_to root_path unless post.accessible_to?(current_user) }
35
+ end
36
+
37
+ class Show < Singular
38
+ end
39
+
40
+ class Update < Singular
41
+ def call
42
+ if post.update_attributes(params[:post])
43
+ # ...
44
+ else
45
+ # ...
46
+ end
47
+ end
48
+ end
49
+ end
50
+ ```
51
+
52
+ Some notes:
53
+
54
+ * You can include `FocusedController::Mixin` anywhere, so you don't have
55
+ to use Focused Controller in every single controller if you don't want
56
+ to
57
+ * `expose` makes the object returned by the block available in your view
58
+ template. It also memoizes the result so the block will only be
59
+ executed once.
60
+ * It is not necessary to specify `:only` or `:except` on the before
61
+ filter, since we declare the filter for exactly the actions we want it
62
+ to run on. Rails has many methods which accept action names to limit
63
+ what they get applied to - this is basically irrelevant with Focused
64
+ Controller due to the increased granularity that having a class for each
65
+ action gives us.
66
+ * The `#call` method is what gets invoked when the action runs, so put
67
+ the 'active ingredients' in here.
68
+
69
+ ## Routing ##
70
+
71
+ Rails' normal routing assumes your actions are methods inside an object
72
+ whose name ends with 'controller'. For example:
73
+
74
+ ``` ruby
75
+ get '/posts/new' => 'posts#new'
76
+ ```
77
+
78
+ will route `GET /posts/new` to `PostsController#new`.
79
+
80
+ To get around this, we use the `focused_controller_routes` helper:
81
+
82
+ ``` ruby
83
+ Loco2::Application.routes.draw do
84
+ focused_controller_routes do
85
+ get '/posts/new' => 'posts#new'
86
+ end
87
+ end
88
+ ```
89
+
90
+ The route will now map to `PostsController::New#call`.
91
+
92
+ All the normal routing macros are also supported:
93
+
94
+ ``` ruby
95
+ focused_controller_routes do
96
+ resources :posts
97
+ end
98
+ ```
99
+
100
+ ## Functional Testing ##
101
+
102
+ If you wish, focused controllers can be tested in the classical
103
+ 'functional' style. It no longer makes sense to specify the method name
104
+ to be called as it would always be `#call`. So this is omitted:
105
+
106
+ ``` ruby
107
+ require 'focused_controller/functional_test_helper'
108
+
109
+ module UsersController
110
+ class CreateTest < ActionController::TestCase
111
+ include FocusedController::FunctionalTestHelper
112
+
113
+ test "should create user" do
114
+ assert_difference('User.count') do
115
+ post user: { name: 'Jon' }
116
+ end
117
+
118
+ assert_redirected_to user_path(@controller.user)
119
+ end
120
+ end
121
+ end
122
+ ```
123
+
124
+ There is also an equivalent helper for RSpec:
125
+
126
+ ``` ruby
127
+ require 'focused_controller/rspec_functional_helper'
128
+
129
+ describe UsersController do
130
+ include FocusedController::RSpecFunctionalHelper
131
+
132
+ describe UsersController::Create do
133
+ it "should create user" do
134
+ expect { post user: { name: 'Jon' } }.to change(User, :count).by(1)
135
+ response.should redirect_to(user_path(subject.user))
136
+ end
137
+ end
138
+ end
139
+ ```
140
+
141
+ ## Unit Testing ##
142
+
143
+ Unit testing is faster and better suited to testing logic than
144
+ functional testing. To do so, you instantiate your action class and call
145
+ methods on it:
146
+
147
+ ``` ruby
148
+ module UsersController
149
+ class ShowTest < ActiveSupport::TestCase
150
+ test 'finds the user' do
151
+ user = User.create
152
+
153
+ controller = UsersController::Show.new
154
+ controller.params = { id: user.id }
155
+
156
+ assert_equal user, controller.user
157
+ end
158
+ end
159
+ end
160
+ ```
161
+
162
+ ### The `#call` method ###
163
+
164
+ Testing the code in your `#call` method is a little more involved,
165
+ depending on what's in it. For example, your `#call` method may use
166
+ (explicitly or implicitly) any of the following objects:
167
+
168
+ * request
169
+ * response
170
+ * params
171
+ * session
172
+ * flash
173
+ * cookies
174
+
175
+ To make the experience smoother, Focused Controller sets up mock
176
+ versions of these objects, much like with classical functional testing.
177
+ It also provides accessors for these objects in your test class.
178
+
179
+ ``` ruby
180
+ require 'focused_controller/test_helper'
181
+
182
+ module UsersController
183
+ class CreateTest < ActiveSupport::TestCase
184
+ include FocusedController::TestHelper
185
+
186
+ test "should create user" do
187
+ controller.params = { user: { name: 'Jon' } }
188
+
189
+ assert_difference('User.count') do
190
+ controller.call
191
+ end
192
+
193
+ assert_redirected_to user_path(controller.user)
194
+ end
195
+ end
196
+ end
197
+ ```
198
+
199
+ ### Assertions ###
200
+
201
+ You have access to the normal assertions found in Rails' functional tests:
202
+
203
+ * `assert_template`
204
+ * `assert_response`
205
+ * `assert_redirected_to`
206
+
207
+ ### Filters ###
208
+
209
+ In unit tests, we're not testing through the Rack stack. We're just calling the
210
+ `#call` method. Therefore, filters do not get run. If some filter code is
211
+ crucial to what your action is doing then you should move it out of the filter.
212
+ If the filter code is separate, then you might want to unit-test it separately,
213
+ or you might decide that covering it in integration/acceptance tests is
214
+ sufficient.
215
+
216
+ ### RSpec ###
217
+
218
+ There is a helper for RSpec as well:
219
+
220
+ ``` ruby
221
+ require 'focused_controller/rspec_helper'
222
+
223
+ describe UsersController do
224
+ include FocusedController::RSpecHelper
225
+
226
+ describe UsersController::Create do
227
+ test "should create user" do
228
+ subject.params = { user: { name: 'Jon' } }
229
+ expect { subject.call }.to change(User, :count).by(1)
230
+ response.should redirect_to(user_path(subject.user))
231
+ end
232
+ end
233
+ end
234
+ ```
235
+
236
+ ## Isolated unit tests ##
237
+
238
+ It is possible to completely decouple your focused controller tests from the
239
+ Rails application. This means you don't have to pay the penalty of starting up
240
+ Rails every time you want to run a test. This is an advanced feature and your
241
+ mileage may vary. The benefit this brings will depend on how coupled your
242
+ controllers/tests are to other dependencies.
243
+
244
+ Your `config/routes.rb` file is a dependency. When you use a URL helper
245
+ you are depending on that file. As this is a common dependency, Focused
246
+ Controller provides a way to stub out URL helpers:
247
+
248
+ ``` ruby
249
+ module UsersController
250
+ class CreateTest < ActiveSupport::TestCase
251
+ include FocusedController::TestHelper
252
+ stub_url :user
253
+
254
+ # ...
255
+ end
256
+ end
257
+ ```
258
+
259
+ The `stub_url` declaration will make the `user_path` and `user_url`
260
+ methods in your test and your controller return stub objects. These can
261
+ be compared, so `user_path(user1) == user_path(user1)`, but
262
+ `user_path(user1) != user_path(user2)`.
263
+
264
+ ## More examples ##
265
+
266
+ The [acceptance
267
+ tests](https://github.com/jonleighton/focused_controller/tree/master/test/acceptance)
268
+ for Focused Controller exercise a [complete Rails
269
+ application](https://github.com/jonleighton/focused_controller/tree/master/test/app),
270
+ which uses the plugin. Therefore, you might wish to look there to get
271
+ more of an idea about how it can be used.
272
+
273
+ (Note that the code there is based on Rails' scaffolding, not how I
274
+ would typically write controllers and tests.)
@@ -8,8 +8,8 @@ Gem::Specification.new do |s|
8
8
  s.authors = ["Jon Leighton"]
9
9
  s.email = ["j@jonathanleighton.com"]
10
10
  s.homepage = "http://github.com/jonleighton/focused_controller"
11
- s.summary = %q{Write Rails controllers that don't violate SRP}
12
- s.description = %q{Write Rails controllers that don't violate SRP}
11
+ s.summary = %q{Write Rails controllers with one class per action}
12
+ s.description = %q{Write Rails controllers with one class per action}
13
13
 
14
14
  s.rubyforge_project = "focused_controller"
15
15
 
@@ -20,11 +20,11 @@ Gem::Specification.new do |s|
20
20
 
21
21
  s.add_dependency 'actionpack', '~> 3.0'
22
22
 
23
- s.add_development_dependency 'minitest', '~> 2.11.2'
24
- s.add_development_dependency 'capybara', '~> 1.1.2'
25
- s.add_development_dependency 'capybara_minitest_spec', '~> 0.2.1'
26
- s.add_development_dependency 'poltergeist', '~> 0.4.0'
27
- s.add_development_dependency 'rspec', '~> 2.8.0'
28
- s.add_development_dependency 'rspec-rails', '~> 2.8.0'
29
- s.add_development_dependency 'appraisal', '~> 0.4.1'
23
+ s.add_development_dependency 'minitest', '~> 2.11'
24
+ s.add_development_dependency 'capybara', '~> 1.1'
25
+ s.add_development_dependency 'capybara_minitest_spec', '~> 0.2'
26
+ s.add_development_dependency 'poltergeist', '~> 0.7'
27
+ s.add_development_dependency 'rspec', '~> 2.8'
28
+ s.add_development_dependency 'rspec-rails', '~> 2.8'
29
+ s.add_development_dependency 'appraisal', '~> 0.4'
30
30
  end
@@ -3,5 +3,6 @@
3
3
  source "http://rubygems.org"
4
4
 
5
5
  gem "rails", "~> 3.0.0"
6
+ gem "rack-test", "~> 0.5.7"
6
7
 
7
8
  gemspec :path=>"../"
@@ -2,5 +2,5 @@ module FocusedController
2
2
  class << self
3
3
  attr_accessor :action_name
4
4
  end
5
- self.action_name = 'run'
5
+ self.action_name = 'call'
6
6
  end
@@ -20,6 +20,28 @@ module FocusedController
20
20
  def call(env)
21
21
  action(FocusedController.action_name).call(env)
22
22
  end
23
+
24
+ def expose(name, &block)
25
+ if block_given?
26
+ define_method(name) do |*args|
27
+ ivar = "@#{name}"
28
+
29
+ if instance_variable_defined?(ivar)
30
+ instance_variable_get(ivar)
31
+ else
32
+ instance_variable_set(ivar, instance_exec(block, *args, &block))
33
+ end
34
+ end
35
+ else
36
+ attr_reader name
37
+ end
38
+
39
+ helper_method name
40
+ end
41
+
42
+ def controller_name
43
+ name.split('::')[-2].sub(/Controller$/, '').underscore
44
+ end
23
45
  end
24
46
 
25
47
  def action_name
@@ -38,7 +60,7 @@ module FocusedController
38
60
  end
39
61
  end
40
62
 
41
- def run
63
+ def call
42
64
  end
43
65
  end
44
66
  end
@@ -24,15 +24,23 @@ module FocusedController
24
24
 
25
25
  def to_option
26
26
  if @options[:to] && !@options[:to].respond_to?(:call)
27
- @options[:to]
27
+ if @options[:to].include?('#')
28
+ stringify_controller_and_action(*@options[:to].split('#'))
29
+ else
30
+ @options[:to]
31
+ end
28
32
  elsif @options[:action] && @scope[:controller]
29
- name = ''
30
- name << @scope[:module].camelize << '::' if @scope[:module]
31
- name << @scope[:controller].camelize << 'Controller::'
32
- name << @options[:action].to_s.camelize
33
- name
33
+ stringify_controller_and_action(@scope[:controller], @options[:action])
34
34
  end
35
35
  end
36
+
37
+ def stringify_controller_and_action(controller, action)
38
+ name = ''
39
+ name << @scope[:module].camelize << '::' if @scope[:module]
40
+ name << controller.camelize << 'Controller::'
41
+ name << action.to_s.camelize
42
+ name
43
+ end
36
44
  end
37
45
 
38
46
  class ActionDispatch::Routing::Mapper
@@ -110,13 +110,6 @@ module FocusedController
110
110
  @response ||= TestResponse.new
111
111
  end
112
112
 
113
- def req(params = nil, session = nil, flash = nil)
114
- controller.params = params if params
115
- controller.session.update session if session
116
- controller.flash.update flash if flash
117
- controller.run
118
- end
119
-
120
113
  def session
121
114
  controller.session
122
115
  end
@@ -147,7 +140,7 @@ module FocusedController
147
140
  controller.url_for(*args)
148
141
  end
149
142
 
150
- def respond_to?(method_name)
143
+ def respond_to?(*args)
151
144
  unless defined?(@_routes_included) && @_routes_included
152
145
  self.class.include_routes
153
146
  @_routes_included = true
@@ -1,3 +1,3 @@
1
1
  module FocusedController
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -1,38 +1,20 @@
1
- class PostsController
1
+ module PostsController
2
2
  class Action < ApplicationController
3
3
  end
4
4
 
5
5
  class Index < Action
6
- def posts
7
- @posts ||= Post.all
8
- end
9
- helper_method :posts
10
- end
11
-
12
- class Singular < Action
13
- def post
14
- @post ||= begin
15
- if params[:id]
16
- Post.find(params[:id])
17
- else
18
- Post.new(params[:post])
19
- end
20
- end
21
- end
22
- helper_method :post
23
- end
24
-
25
- class Show < Singular
6
+ expose(:posts) { Post.all }
26
7
  end
27
8
 
28
- class New < Singular
9
+ class Initializer < Action
10
+ expose(:post) { Post.new params[:post] }
29
11
  end
30
12
 
31
- class Edit < Singular
13
+ class New < Initializer
32
14
  end
33
15
 
34
- class Create < Singular
35
- def run
16
+ class Create < Initializer
17
+ def call
36
18
  if post.save
37
19
  redirect_to post, :notice => 'Post was successfully created.'
38
20
  else
@@ -41,8 +23,18 @@ class PostsController
41
23
  end
42
24
  end
43
25
 
44
- class Update < Singular
45
- def run
26
+ class Finder < Action
27
+ expose(:post) { Post.find params[:id] }
28
+ end
29
+
30
+ class Show < Finder
31
+ end
32
+
33
+ class Edit < Finder
34
+ end
35
+
36
+ class Update < Finder
37
+ def call
46
38
  if post.update_attributes(params[:post])
47
39
  redirect_to post, :notice => 'Post was successfully updated.'
48
40
  else
@@ -51,8 +43,8 @@ class PostsController
51
43
  end
52
44
  end
53
45
 
54
- class Destroy < Singular
55
- def run
46
+ class Destroy < Finder
47
+ def call
56
48
  post.destroy
57
49
  redirect_to posts_url
58
50
  end
@@ -1,9 +1,6 @@
1
1
  require 'bundler/setup'
2
2
  require 'rspec'
3
3
  require 'focused_controller/rspec_helper'
4
- require 'focused_controller/rspec_functional_helper'
5
4
 
6
- require 'active_support/dependencies'
7
- ActiveSupport::Dependencies.autoload_paths += Dir[File.expand_path('../../app/*', __FILE__)]
8
-
9
- POSTS = []
5
+ APP_ROOT = File.expand_path('../../app', __FILE__)
6
+ POSTS = []
@@ -1,4 +1,7 @@
1
1
  require 'isolated_spec_helper'
2
+ require APP_ROOT + '/controllers/application_controller'
3
+ require APP_ROOT + '/controllers/posts_controller'
4
+ require APP_ROOT + '/models/post'
2
5
 
3
6
  describe PostsController do
4
7
  include FocusedController::RSpecHelper
@@ -10,7 +13,7 @@ describe PostsController do
10
13
 
11
14
  describe PostsController::Index do
12
15
  it "should get index" do
13
- req
16
+ subject.call
14
17
  response.should be_success
15
18
  subject.posts.should_not be(:nil)
16
19
  end
@@ -18,42 +21,47 @@ describe PostsController do
18
21
 
19
22
  describe PostsController::New do
20
23
  it "should get new" do
21
- req
24
+ subject.call
22
25
  response.should be_success
23
26
  end
24
27
  end
25
28
 
26
29
  describe PostsController::Create do
27
30
  it "should create post" do
28
- expect { req :post => @post.attributes }.to change(Post, :count).by(1)
31
+ subject.params = { :post => @post.attributes }
32
+ expect { subject.call }.to change(Post, :count).by(1)
29
33
  response.should redirect_to(post_url(subject.post))
30
34
  end
31
35
  end
32
36
 
33
37
  describe PostsController::Show do
34
38
  it "should show post" do
35
- req :id => @post.id
39
+ subject.params = { :id => @post.id }
40
+ subject.call
36
41
  response.should be_success
37
42
  end
38
43
  end
39
44
 
40
45
  describe PostsController::Edit do
41
46
  it "should get edit" do
42
- req :id => @post.id
47
+ subject.params = { :id => @post.id }
48
+ subject.call
43
49
  response.should be_success
44
50
  end
45
51
  end
46
52
 
47
53
  describe PostsController::Update do
48
54
  it "should update post" do
49
- req :id => @post.id
55
+ subject.params = { :id => @post.id }
56
+ subject.call
50
57
  response.should redirect_to(post_url(subject.post))
51
58
  end
52
59
  end
53
60
 
54
61
  describe PostsController::Destroy do
55
62
  it "should destroy post" do
56
- expect { req :id => @post.id }.to change(Post, :count).by(-1)
63
+ subject.params = { :id => @post.id }
64
+ expect { subject.call }.to change(Post, :count).by(-1)
57
65
  response.should redirect_to(posts_url)
58
66
  end
59
67
  end
@@ -9,7 +9,7 @@ describe PostsController do
9
9
 
10
10
  describe PostsController::Index do
11
11
  it "should get index" do
12
- req
12
+ subject.call
13
13
  response.should be_success
14
14
  subject.posts.should_not be(:nil)
15
15
  end
@@ -17,42 +17,47 @@ describe PostsController do
17
17
 
18
18
  describe PostsController::New do
19
19
  it "should get new" do
20
- req
20
+ subject.call
21
21
  response.should be_success
22
22
  end
23
23
  end
24
24
 
25
25
  describe PostsController::Create do
26
26
  it "should create post" do
27
- expect { req :post => @post.attributes }.to change(Post, :count).by(1)
27
+ subject.params = { :post => @post.attributes }
28
+ expect { subject.call }.to change(Post, :count).by(1)
28
29
  response.should redirect_to(post_path(subject.post))
29
30
  end
30
31
  end
31
32
 
32
33
  describe PostsController::Show do
33
34
  it "should show post" do
34
- req :id => @post.id
35
+ subject.params = { :id => @post.id }
36
+ subject.call
35
37
  response.should be_success
36
38
  end
37
39
  end
38
40
 
39
41
  describe PostsController::Edit do
40
42
  it "should get edit" do
41
- req :id => @post.id
43
+ subject.params = { :id => @post.id }
44
+ subject.call
42
45
  response.should be_success
43
46
  end
44
47
  end
45
48
 
46
49
  describe PostsController::Update do
47
50
  it "should update post" do
48
- req :id => @post.id
51
+ subject.params = { :id => @post.id }
52
+ subject.call
49
53
  response.should redirect_to(post_path(subject.post))
50
54
  end
51
55
  end
52
56
 
53
57
  describe PostsController::Destroy do
54
58
  it "should destroy post" do
55
- expect { req :id => @post.id }.to change(Post, :count).by(-1)
59
+ subject.params = { :id => @post.id }
60
+ expect { subject.call }.to change(Post, :count).by(-1)
56
61
  response.should redirect_to(posts_path)
57
62
  end
58
63
  end
@@ -1,6 +1,6 @@
1
1
  require 'test_helper'
2
2
 
3
- class PostsController
3
+ module PostsController
4
4
  class TestCase < ActionController::TestCase
5
5
  include FocusedController::FunctionalTestHelper
6
6
 
@@ -1,10 +1,7 @@
1
1
  require 'bundler/setup'
2
2
  require 'test/unit'
3
3
  require 'active_support/test_case'
4
- require 'focused_controller/functional_test_helper'
5
4
  require 'focused_controller/test_helper'
6
5
 
7
- require 'active_support/dependencies'
8
- ActiveSupport::Dependencies.autoload_paths += Dir[File.expand_path('../../app/*', __FILE__)]
9
-
10
- POSTS = []
6
+ APP_ROOT = File.expand_path('../../app', __FILE__)
7
+ POSTS = []
@@ -1,7 +1,9 @@
1
1
  require 'isolated_test_helper'
2
- require_dependency 'posts_controller'
2
+ require APP_ROOT + '/controllers/application_controller'
3
+ require APP_ROOT + '/controllers/posts_controller'
4
+ require APP_ROOT + '/models/post'
3
5
 
4
- class PostsController
6
+ module PostsController
5
7
  class TestCase < ActiveSupport::TestCase
6
8
  include FocusedController::TestHelper
7
9
  stub_url :post, :posts
@@ -13,7 +15,7 @@ class PostsController
13
15
 
14
16
  class IndexTest < TestCase
15
17
  test "should get index" do
16
- req
18
+ controller.call
17
19
  assert_response :success
18
20
  assert_not_nil controller.posts
19
21
  end
@@ -21,15 +23,17 @@ class PostsController
21
23
 
22
24
  class NewTest < TestCase
23
25
  test "should get new" do
24
- req
26
+ controller.call
25
27
  assert_response :success
26
28
  end
27
29
  end
28
30
 
29
31
  class CreateTest < TestCase
30
32
  test "should create post" do
33
+ controller.params = { :post => @post.attributes }
34
+
31
35
  assert_difference('Post.count') do
32
- req :post => @post.attributes
36
+ controller.call
33
37
  end
34
38
 
35
39
  assert_redirected_to post_url(controller.post)
@@ -38,29 +42,34 @@ class PostsController
38
42
 
39
43
  class ShowTest < TestCase
40
44
  test "should show post" do
41
- req :id => @post.id
45
+ controller.params = { :id => @post.id }
46
+ controller.call
42
47
  assert_response :success
43
48
  end
44
49
  end
45
50
 
46
51
  class EditTest < TestCase
47
52
  test "should get edit" do
48
- req :id => @post.id
53
+ controller.params = { :id => @post.id }
54
+ controller.call
49
55
  assert_response :success
50
56
  end
51
57
  end
52
58
 
53
59
  class UpdateTest < TestCase
54
60
  test "should update post" do
55
- req :id => @post.id
61
+ controller.params = { :id => @post.id }
62
+ controller.call
56
63
  assert_redirected_to post_url(controller.post)
57
64
  end
58
65
  end
59
66
 
60
67
  class DestroyTest < TestCase
61
68
  test "should destroy post" do
69
+ controller.params = { :id => @post.id }
70
+
62
71
  assert_difference('Post.count', -1) do
63
- req :id => @post.id
72
+ controller.call
64
73
  end
65
74
 
66
75
  assert_redirected_to posts_url
@@ -1,6 +1,6 @@
1
1
  require 'test_helper'
2
2
 
3
- class PostsController
3
+ module PostsController
4
4
  class TestCase < ActiveSupport::TestCase
5
5
  include FocusedController::TestHelper
6
6
 
@@ -11,7 +11,7 @@ class PostsController
11
11
 
12
12
  class IndexTest < TestCase
13
13
  test "should get index" do
14
- req
14
+ controller.call
15
15
  assert_response :success
16
16
  assert_not_nil controller.posts
17
17
  end
@@ -19,15 +19,17 @@ class PostsController
19
19
 
20
20
  class NewTest < TestCase
21
21
  test "should get new" do
22
- req
22
+ controller.call
23
23
  assert_response :success
24
24
  end
25
25
  end
26
26
 
27
27
  class CreateTest < TestCase
28
28
  test "should create post" do
29
+ controller.params = { :post => @post.attributes }
30
+
29
31
  assert_difference('Post.count') do
30
- req :post => @post.attributes
32
+ controller.call
31
33
  end
32
34
 
33
35
  assert_redirected_to post_path(controller.post)
@@ -36,29 +38,34 @@ class PostsController
36
38
 
37
39
  class ShowTest < TestCase
38
40
  test "should show post" do
39
- req :id => @post.id
41
+ controller.params = { :id => @post.id }
42
+ controller.call
40
43
  assert_response :success
41
44
  end
42
45
  end
43
46
 
44
47
  class EditTest < TestCase
45
48
  test "should get edit" do
46
- req :id => @post.id
49
+ controller.params = { :id => @post.id }
50
+ controller.call
47
51
  assert_response :success
48
52
  end
49
53
  end
50
54
 
51
55
  class UpdateTest < TestCase
52
56
  test "should update post" do
53
- req :id => @post.id
57
+ controller.params = { :id => @post.id }
58
+ controller.call
54
59
  assert_redirected_to post_path(controller.post)
55
60
  end
56
61
  end
57
62
 
58
63
  class DestroyTest < TestCase
59
64
  test "should destroy post" do
65
+ controller.params = { :id => @post.id }
66
+
60
67
  assert_difference('Post.count', -1) do
61
- req :id => @post.id
68
+ controller.call
62
69
  end
63
70
 
64
71
  assert_redirected_to posts_path
@@ -4,7 +4,7 @@ require 'action_controller'
4
4
 
5
5
  module FocusedController
6
6
  module FunctionalTestHelper
7
- class FakePostsController
7
+ module FakePostsController
8
8
  class Action < ActionController::Base; end
9
9
  class Index < Action; end
10
10
  class Show < Action; end
@@ -46,19 +46,19 @@ module FocusedController
46
46
  end
47
47
 
48
48
  subject.get :foo, :bar, :baz
49
- subject.last_process.must_equal ['run', :foo, :bar, :baz, 'GET']
49
+ subject.last_process.must_equal ['call', :foo, :bar, :baz, 'GET']
50
50
 
51
51
  subject.post :foo, :bar, :baz
52
- subject.last_process.must_equal ['run', :foo, :bar, :baz, 'POST']
52
+ subject.last_process.must_equal ['call', :foo, :bar, :baz, 'POST']
53
53
 
54
54
  subject.put :foo, :bar, :baz
55
- subject.last_process.must_equal ['run', :foo, :bar, :baz, 'PUT']
55
+ subject.last_process.must_equal ['call', :foo, :bar, :baz, 'PUT']
56
56
 
57
57
  subject.delete :foo, :bar, :baz
58
- subject.last_process.must_equal ['run', :foo, :bar, :baz, 'DELETE']
58
+ subject.last_process.must_equal ['call', :foo, :bar, :baz, 'DELETE']
59
59
 
60
60
  subject.head :foo, :bar, :baz
61
- subject.last_process.must_equal ['run', :foo, :bar, :baz, 'HEAD']
61
+ subject.last_process.must_equal ['call', :foo, :bar, :baz, 'HEAD']
62
62
  end
63
63
  end
64
64
  end
@@ -34,9 +34,9 @@ module FocusedController
34
34
  klass.controller_path.must_equal 'posts'
35
35
  end
36
36
 
37
- it "has a .call which dispatches the #run action" do
37
+ it "has a .call which dispatches the #call action" do
38
38
  def klass.action(name)
39
- if name.to_s == 'run'
39
+ if name.to_s == 'call'
40
40
  proc { |env| "omg" }
41
41
  end
42
42
  end
@@ -48,8 +48,12 @@ module FocusedController
48
48
  subject.action_name.must_equal 'show'
49
49
  end
50
50
 
51
- it "uses the run method for the action" do
52
- subject.method_for_action('whatever').must_equal 'run'
51
+ it "has a #controller_name of 'posts'" do
52
+ subject.controller_name.must_equal 'posts'
53
+ end
54
+
55
+ it "uses the call method for the action" do
56
+ subject.method_for_action('whatever').must_equal 'call'
53
57
  end
54
58
 
55
59
  it "removes all view assigns by default" do
@@ -62,8 +66,72 @@ module FocusedController
62
66
  subject.view_assigns['foo'].must_equal('bar')
63
67
  end
64
68
 
65
- it "has a #run method by default" do
66
- subject.run.must_equal nil
69
+ it "has a #call method by default" do
70
+ subject.call.must_equal nil
71
+ end
72
+ end
73
+
74
+ describe '.expose' do
75
+ subject do
76
+ @klass = Class.new do
77
+ include FocusedController::Mixin
78
+
79
+ @helper_methods = []
80
+
81
+ class << self
82
+ attr_reader :helper_methods
83
+
84
+ def helper_method(name)
85
+ @helper_methods << name
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ it 'defines a method' do
92
+ subject.expose(:foo) { 'bar' }
93
+ subject.new.foo.must_equal 'bar'
94
+ end
95
+
96
+ it 'declares the method a helper method' do
97
+ subject.expose(:foo) { 'bar' }
98
+ subject.helper_methods.must_equal [:foo]
99
+ end
100
+
101
+ it 'memoizes the result' do
102
+ count = 0
103
+ counter = proc { count += 1 }
104
+ subject.expose(:foo) { counter.call }
105
+
106
+ obj = subject.new
107
+ obj.foo.must_equal 1
108
+ obj.foo.must_equal 1
109
+ end
110
+
111
+ it 'it memoizes falsey values' do
112
+ val = true
113
+ meth = proc { val = !val }
114
+ subject.expose(:foo) { meth.call }
115
+
116
+ obj = subject.new
117
+ obj.foo.must_equal false
118
+ obj.foo.must_equal false
119
+ end
120
+
121
+ it 'instance evals the block' do
122
+ subject.expose(:foo) { @bar }
123
+ obj = subject.new
124
+ obj.instance_variable_set('@bar', 'bar')
125
+ obj.foo.must_equal 'bar'
126
+ end
127
+
128
+ it 'declares an attr_reader when called without a block' do
129
+ subject.expose :foo
130
+ subject.helper_methods.must_equal [:foo]
131
+
132
+ obj = subject.new
133
+ obj.instance_variable_set('@foo', 'bar')
134
+ obj.foo.must_equal 'bar'
67
135
  end
68
136
  end
69
137
  end
@@ -27,7 +27,8 @@ module FocusedController
27
27
  it 'creates routes that map to focused controllers' do
28
28
  route_set.draw do
29
29
  focused_controller_routes do
30
- match 'posts' => 'PostsController::Index'
30
+ match 'posts' => 'PostsController::Index'
31
+ match 'posts/all' => 'posts#index'
31
32
 
32
33
  resources :comments do
33
34
  resources :replies
@@ -43,6 +44,7 @@ module FocusedController
43
44
 
44
45
  mappings = {
45
46
  [:get, '/posts'] => 'PostsController::Index',
47
+ [:get, '/posts/all'] => 'PostsController::Index',
46
48
  [:get, '/comments'] => 'CommentsController::Index',
47
49
  [:get, '/comments/4'] => 'CommentsController::Show',
48
50
  [:put, '/comments/4'] => 'CommentsController::Update',
@@ -54,7 +56,7 @@ module FocusedController
54
56
  mappings.each do |(method, path), controller|
55
57
  route = recognize(path, :method => method)
56
58
  route.app.name.must_equal controller
57
- route.defaults[:action].must_equal 'run'
59
+ route.defaults[:action].must_equal 'call'
58
60
  end
59
61
  end
60
62
 
@@ -9,7 +9,7 @@ module FocusedController
9
9
  end
10
10
 
11
11
  class Index < Action
12
- def run
12
+ def call
13
13
  if params[:omg]
14
14
  "omg"
15
15
  elsif params[:set_session]
@@ -86,6 +86,8 @@ module FocusedController
86
86
  end
87
87
  end
88
88
 
89
+ let(:controller) { subject.controller }
90
+
89
91
  subject { FakePostsController::IndexTest.new }
90
92
 
91
93
  def must_fail(&block)
@@ -149,13 +151,17 @@ module FocusedController
149
151
  end
150
152
 
151
153
  it 'supports session' do
152
- subject.req(:set_session => true)
154
+ controller.params = { :set_session => true }
155
+ controller.call
156
+
153
157
  subject.session[:foo].must_equal 'omg'
154
158
  subject.session['foo'].must_equal 'omg'
155
159
  end
156
160
 
157
161
  it 'supports flash' do
158
- subject.req(:set_flash => true)
162
+ controller.params = { :set_flash => true }
163
+ controller.call
164
+
159
165
  subject.flash[:foo].must_equal 'omg'
160
166
 
161
167
  # This is consistent with the behaviour of standard rails functional tests
@@ -163,40 +169,10 @@ module FocusedController
163
169
  end
164
170
 
165
171
  it 'supports cookies' do
166
- subject.req(:set_cookie => true)
167
- subject.cookies[:foo].must_equal 'omg'
168
- end
169
-
170
- describe "#req" do
171
- it "sets params and calls the controller's #run" do
172
- subject.req(:omg => true).must_equal 'omg'
173
- end
174
-
175
- it 'sets session' do
176
- subject.req(nil, { :foo => 'bar' })
177
- subject.session[:foo].must_equal 'bar'
178
- end
172
+ controller.params = { :set_cookie => true }
173
+ controller.call
179
174
 
180
- it 'set flash' do
181
- subject.req(nil, nil, { :foo => 'bar' })
182
- subject.flash[:foo].must_equal 'bar'
183
- end
184
-
185
- it "doesn't overwrite existing params, session, or flash if new ones aren't provided" do
186
- subject.controller.params[:param] = true
187
- subject.controller.flash[:flash] = true
188
- subject.controller.session[:session] = true
189
-
190
- subject.req
191
-
192
- subject.controller.params[:param].must_equal true
193
- subject.controller.flash[:flash].must_equal true
194
- subject.controller.session[:session].must_equal true
195
-
196
- subject.req({})
197
-
198
- subject.controller.params[:param].must_equal(nil)
199
- end
175
+ subject.cookies[:foo].must_equal 'omg'
200
176
  end
201
177
 
202
178
  describe 'url stubbing' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: focused_controller
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-02-22 00:00:00.000000000 Z
12
+ date: 2012-08-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: actionpack
16
- requirement: &9754840 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,85 +21,125 @@ dependencies:
21
21
  version: '3.0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *9754840
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3.0'
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: minitest
27
- requirement: &9754340 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ~>
31
36
  - !ruby/object:Gem::Version
32
- version: 2.11.2
37
+ version: '2.11'
33
38
  type: :development
34
39
  prerelease: false
35
- version_requirements: *9754340
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '2.11'
36
46
  - !ruby/object:Gem::Dependency
37
47
  name: capybara
38
- requirement: &9753880 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
39
49
  none: false
40
50
  requirements:
41
51
  - - ~>
42
52
  - !ruby/object:Gem::Version
43
- version: 1.1.2
53
+ version: '1.1'
44
54
  type: :development
45
55
  prerelease: false
46
- version_requirements: *9753880
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.1'
47
62
  - !ruby/object:Gem::Dependency
48
63
  name: capybara_minitest_spec
49
- requirement: &9753420 !ruby/object:Gem::Requirement
64
+ requirement: !ruby/object:Gem::Requirement
50
65
  none: false
51
66
  requirements:
52
67
  - - ~>
53
68
  - !ruby/object:Gem::Version
54
- version: 0.2.1
69
+ version: '0.2'
55
70
  type: :development
56
71
  prerelease: false
57
- version_requirements: *9753420
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '0.2'
58
78
  - !ruby/object:Gem::Dependency
59
79
  name: poltergeist
60
- requirement: &9752960 !ruby/object:Gem::Requirement
80
+ requirement: !ruby/object:Gem::Requirement
61
81
  none: false
62
82
  requirements:
63
83
  - - ~>
64
84
  - !ruby/object:Gem::Version
65
- version: 0.4.0
85
+ version: '0.7'
66
86
  type: :development
67
87
  prerelease: false
68
- version_requirements: *9752960
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: '0.7'
69
94
  - !ruby/object:Gem::Dependency
70
95
  name: rspec
71
- requirement: &9752500 !ruby/object:Gem::Requirement
96
+ requirement: !ruby/object:Gem::Requirement
72
97
  none: false
73
98
  requirements:
74
99
  - - ~>
75
100
  - !ruby/object:Gem::Version
76
- version: 2.8.0
101
+ version: '2.8'
77
102
  type: :development
78
103
  prerelease: false
79
- version_requirements: *9752500
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: '2.8'
80
110
  - !ruby/object:Gem::Dependency
81
111
  name: rspec-rails
82
- requirement: &9752040 !ruby/object:Gem::Requirement
112
+ requirement: !ruby/object:Gem::Requirement
83
113
  none: false
84
114
  requirements:
85
115
  - - ~>
86
116
  - !ruby/object:Gem::Version
87
- version: 2.8.0
117
+ version: '2.8'
88
118
  type: :development
89
119
  prerelease: false
90
- version_requirements: *9752040
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ version: '2.8'
91
126
  - !ruby/object:Gem::Dependency
92
127
  name: appraisal
93
- requirement: &9751580 !ruby/object:Gem::Requirement
128
+ requirement: !ruby/object:Gem::Requirement
94
129
  none: false
95
130
  requirements:
96
131
  - - ~>
97
132
  - !ruby/object:Gem::Version
98
- version: 0.4.1
133
+ version: '0.4'
99
134
  type: :development
100
135
  prerelease: false
101
- version_requirements: *9751580
102
- description: Write Rails controllers that don't violate SRP
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ~>
140
+ - !ruby/object:Gem::Version
141
+ version: '0.4'
142
+ description: Write Rails controllers with one class per action
103
143
  email:
104
144
  - j@jonathanleighton.com
105
145
  executables: []
@@ -110,6 +150,7 @@ files:
110
150
  - .travis.yml
111
151
  - Appraisals
112
152
  - Gemfile
153
+ - LICENSE
113
154
  - README.md
114
155
  - Rakefile
115
156
  - focused_controller.gemspec
@@ -212,10 +253,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
212
253
  version: '0'
213
254
  requirements: []
214
255
  rubyforge_project: focused_controller
215
- rubygems_version: 1.8.15
256
+ rubygems_version: 1.8.24
216
257
  signing_key:
217
258
  specification_version: 3
218
- summary: Write Rails controllers that don't violate SRP
259
+ summary: Write Rails controllers with one class per action
219
260
  test_files:
220
261
  - test/acceptance/app_test.rb
221
262
  - test/app/.gitignore
@@ -282,4 +323,3 @@ test_files:
282
323
  - test/unit/rspec_functional_helper.rb
283
324
  - test/unit/rspec_helper_test.rb
284
325
  - test/unit/test_helper_test.rb
285
- has_rdoc: