focused_controller 0.1.0 → 0.2.0

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