Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 283 lines (213 sloc) 7.724 kb
e14e452 @jonleighton rework the readme
authored
1 # Focused Controller #
3def6e6 @jonleighton Initial pass at a README
authored
2
b31675e @jonleighton travis build status
authored
3 [![Build Status](https://secure.travis-ci.org/jonleighton/focused_controller.png?branch=master)](http://travis-ci.org/jonleighton/focused_controller)
3def6e6 @jonleighton Initial pass at a README
authored
4
e14e452 @jonleighton rework the readme
authored
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.
3def6e6 @jonleighton Initial pass at a README
authored
8
e14e452 @jonleighton rework the readme
authored
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.
3def6e6 @jonleighton Initial pass at a README
authored
12
ec7ccf1 @jonleighton link to blog post
authored
13 [I wrote a blog post to fully explain the
14 idea](http://jonathanleighton.com/articles/2012/explaining-focused-controller/).
15
7793d4c @jonleighton Update README.md
authored
16 There is a [mailing list](http://groups.google.com/group/focused_controller)
17 for discussion.
9b8a6d9 @shingara Add link about google group to speak on focused_controller
shingara authored
18
e14e452 @jonleighton rework the readme
authored
19 ## Synopsis ##
3def6e6 @jonleighton Initial pass at a README
authored
20
21 ``` ruby
e14e452 @jonleighton rework the readme
authored
22 class ApplicationController
3c40032 @jonleighton readme tweaks
authored
23 class Action < ApplicationController
24 include FocusedController::Mixin
25 end
e14e452 @jonleighton rework the readme
authored
26 end
27
3def6e6 @jonleighton Initial pass at a README
authored
28 module PostsController
3c40032 @jonleighton readme tweaks
authored
29 class Action < ApplicationController::Action
30 before_filter :authenticate
31 end
32
33 class Index < Action
e14e452 @jonleighton rework the readme
authored
34 expose(:posts) { Post.recent.limit(5) }
3def6e6 @jonleighton Initial pass at a README
authored
35 end
36
3c40032 @jonleighton readme tweaks
authored
37 class New < Action
e14e452 @jonleighton rework the readme
authored
38 expose(:post) { Post.new }
3def6e6 @jonleighton Initial pass at a README
authored
39 end
40
3c40032 @jonleighton readme tweaks
authored
41 class Singular < Action
3a9b346 @jonleighton add #expose
authored
42 expose(:post) { Post.find params[:id] }
e14e452 @jonleighton rework the readme
authored
43 before_filter { redirect_to root_path unless post.accessible_to?(current_user) }
44 end
3a9b346 @jonleighton add #expose
authored
45
e14e452 @jonleighton rework the readme
authored
46 class Show < Singular
47 end
48
49 class Update < Singular
50 def call
51 if post.update_attributes(params[:post])
52 # ...
53 else
54 # ...
55 end
56 end
3def6e6 @jonleighton Initial pass at a README
authored
57 end
58 end
59 ```
60
3c40032 @jonleighton readme tweaks
authored
61 You can include `FocusedController::Mixin` anywhere, so you don't have
62 to use Focused Controller in every single controller if you don't want
63 to. I find it useful to define `ApplicationController::Action` and
64 inherit from that where needed.
65
66 The `#call` method is what gets invoked when the request is served.
67
68 The `#expose` declaration defines a method which runs the block and
69 memoizes the result. It also makes `post` a helper method so you can
70 call it from the view template.
71
72 The `before_filter` in Singular is inherited by precisely the actions
73 that need it, so we don't need to specify `:only` or `:except`.
e14e452 @jonleighton rework the readme
authored
74
3def6e6 @jonleighton Initial pass at a README
authored
75 ## Routing ##
76
dce9389 @tekin More minor tweaks
tekin authored
77 Rails' normal routing assumes your actions are methods inside an object
78 whose name ends with 'controller'. For example:
3def6e6 @jonleighton Initial pass at a README
authored
79
80 ``` ruby
81 get '/posts/new' => 'posts#new'
82 ```
83
84 will route `GET /posts/new` to `PostsController#new`.
85
f38aaa3 @tekin Tweak example code
tekin authored
86 To get around this, we use the `focused_controller_routes` helper:
3def6e6 @jonleighton Initial pass at a README
authored
87
88 ``` ruby
6ac073f @jonleighton Improve routing example. Fixes #15.
authored
89 Loco2::Application.routes.draw do
90 focused_controller_routes do
91 get '/posts/new' => 'posts#new'
92 end
3def6e6 @jonleighton Initial pass at a README
authored
93 end
94 ```
95
e14e452 @jonleighton rework the readme
authored
96 The route will now map to `PostsController::New#call`.
3def6e6 @jonleighton Initial pass at a README
authored
97
dce9389 @tekin More minor tweaks
tekin authored
98 All the normal routing macros are also supported:
3def6e6 @jonleighton Initial pass at a README
authored
99
100 ``` ruby
101 focused_controller_routes do
102 resources :posts
103 end
104 ```
105
106 ## Functional Testing ##
107
e14e452 @jonleighton rework the readme
authored
108 If you wish, focused controllers can be tested in the classical
109 'functional' style. It no longer makes sense to specify the method name
110 to be called as it would always be `#call`. So this is omitted:
3def6e6 @jonleighton Initial pass at a README
authored
111
112 ``` ruby
e14e452 @jonleighton rework the readme
authored
113 require 'focused_controller/functional_test_helper'
38b98a7 @jonleighton Support Rails 4
authored
114 require_dependency 'users_controller'
e14e452 @jonleighton rework the readme
authored
115
3def6e6 @jonleighton Initial pass at a README
authored
116 module UsersController
117 class CreateTest < ActionController::TestCase
118 include FocusedController::FunctionalTestHelper
119
120 test "should create user" do
121 assert_difference('User.count') do
122 post user: { name: 'Jon' }
123 end
124
125 assert_redirected_to user_path(@controller.user)
126 end
127 end
128 end
129 ```
130
131 There is also an equivalent helper for RSpec:
132
133 ``` ruby
e14e452 @jonleighton rework the readme
authored
134 require 'focused_controller/rspec_functional_helper'
135
3def6e6 @jonleighton Initial pass at a README
authored
136 describe UsersController do
137 include FocusedController::RSpecFunctionalHelper
138
139 describe UsersController::Create do
140 it "should create user" do
141 expect { post user: { name: 'Jon' } }.to change(User, :count).by(1)
142 response.should redirect_to(user_path(subject.user))
143 end
144 end
145 end
146 ```
147
148 ## Unit Testing ##
149
e14e452 @jonleighton rework the readme
authored
150 Unit testing is faster and better suited to testing logic than
151 functional testing. To do so, you instantiate your action class and call
152 methods on it:
3def6e6 @jonleighton Initial pass at a README
authored
153
154 ``` ruby
155 module UsersController
156 class ShowTest < ActiveSupport::TestCase
157 test 'finds the user' do
158 user = User.create
159
160 controller = UsersController::Show.new
161 controller.params = { id: user.id }
162
163 assert_equal user, controller.user
164 end
165 end
166 end
167 ```
168
e14e452 @jonleighton rework the readme
authored
169 ### The `#call` method ###
3def6e6 @jonleighton Initial pass at a README
authored
170
e14e452 @jonleighton rework the readme
authored
171 Testing the code in your `#call` method is a little more involved,
172 depending on what's in it. For example, your `#call` method may use
3def6e6 @jonleighton Initial pass at a README
authored
173 (explicitly or implicitly) any of the following objects:
174
175 * request
176 * response
177 * params
178 * session
179 * flash
180 * cookies
181
182 To make the experience smoother, Focused Controller sets up mock
183 versions of these objects, much like with classical functional testing.
f38aaa3 @tekin Tweak example code
tekin authored
184 It also provides accessors for these objects in your test class.
3def6e6 @jonleighton Initial pass at a README
authored
185
186 ``` ruby
e14e452 @jonleighton rework the readme
authored
187 require 'focused_controller/test_helper'
38b98a7 @jonleighton Support Rails 4
authored
188 require_dependency 'users_controller'
e14e452 @jonleighton rework the readme
authored
189
3def6e6 @jonleighton Initial pass at a README
authored
190 module UsersController
191 class CreateTest < ActiveSupport::TestCase
192 include FocusedController::TestHelper
193
194 test "should create user" do
e14e452 @jonleighton rework the readme
authored
195 controller.params = { user: { name: 'Jon' } }
196
3def6e6 @jonleighton Initial pass at a README
authored
197 assert_difference('User.count') do
e14e452 @jonleighton rework the readme
authored
198 controller.call
3def6e6 @jonleighton Initial pass at a README
authored
199 end
200
201 assert_redirected_to user_path(controller.user)
202 end
203 end
204 end
205 ```
206
207 ### Assertions ###
208
e14e452 @jonleighton rework the readme
authored
209 You have access to the normal assertions found in Rails' functional tests:
3def6e6 @jonleighton Initial pass at a README
authored
210
211 * `assert_template`
212 * `assert_response`
213 * `assert_redirected_to`
214
215 ### Filters ###
216
e14e452 @jonleighton rework the readme
authored
217 In unit tests, we're not testing through the Rack stack. We're just calling the
218 `#call` method. Therefore, filters do not get run. If some filter code is
219 crucial to what your action is doing then you should move it out of the filter.
220 If the filter code is separate, then you might want to unit-test it separately,
221 or you might decide that covering it in integration/acceptance tests is
222 sufficient.
3def6e6 @jonleighton Initial pass at a README
authored
223
224 ### RSpec ###
225
226 There is a helper for RSpec as well:
227
228 ``` ruby
e14e452 @jonleighton rework the readme
authored
229 require 'focused_controller/rspec_helper'
230
3def6e6 @jonleighton Initial pass at a README
authored
231 describe UsersController do
232 include FocusedController::RSpecHelper
233
234 describe UsersController::Create do
3c40032 @jonleighton readme tweaks
authored
235 it "creates a user" do
e14e452 @jonleighton rework the readme
authored
236 subject.params = { user: { name: 'Jon' } }
237 expect { subject.call }.to change(User, :count).by(1)
3def6e6 @jonleighton Initial pass at a README
authored
238 response.should redirect_to(user_path(subject.user))
239 end
240 end
241 end
242 ```
243
244 ## Isolated unit tests ##
245
e14e452 @jonleighton rework the readme
authored
246 It is possible to completely decouple your focused controller tests from the
247 Rails application. This means you don't have to pay the penalty of starting up
248 Rails every time you want to run a test. This is an advanced feature and your
249 mileage may vary. The benefit this brings will depend on how coupled your
250 controllers/tests are to other dependencies.
3def6e6 @jonleighton Initial pass at a README
authored
251
252 Your `config/routes.rb` file is a dependency. When you use a URL helper
253 you are depending on that file. As this is a common dependency, Focused
254 Controller provides a way to stub out URL helpers:
255
256 ``` ruby
257 module UsersController
258 class CreateTest < ActiveSupport::TestCase
259 include FocusedController::TestHelper
260 stub_url :user
261
262 # ...
263 end
264 end
265 ```
266
267 The `stub_url` declaration will make the `user_path` and `user_url`
268 methods in your test and your controller return stub objects. These can
269 be compared, so `user_path(user1) == user_path(user1)`, but
270 `user_path(user1) != user_path(user2)`.
271
272 ## More examples ##
273
274 The [acceptance
89638b0 @andyw8 Fixing links in README
andyw8 authored
275 tests](https://github.com/jonleighton/focused_controller/tree/master/test/acceptance)
3def6e6 @jonleighton Initial pass at a README
authored
276 for Focused Controller exercise a [complete Rails
89638b0 @andyw8 Fixing links in README
andyw8 authored
277 application](https://github.com/jonleighton/focused_controller/tree/master/test/app),
3def6e6 @jonleighton Initial pass at a README
authored
278 which uses the plugin. Therefore, you might wish to look there to get
279 more of an idea about how it can be used.
280
281 (Note that the code there is based on Rails' scaffolding, not how I
e14e452 @jonleighton rework the readme
authored
282 would typically write controllers and tests.)
Something went wrong with that request. Please try again.