README.md in focused_controller-0.1.0 vs README.md in focused_controller-0.2.0
- old
+ new
@@ -1,271 +1,544 @@
+# Focused Controller #
+
[](http://travis-ci.org/jonleighton/focused_controller)
+
+Focused Controller alters Rails' conventions so that each individual action in
+a controller is represented by its own class. This makes it easier to break up
+the code within an action and share code between different actions.
+
+Focused Controller also provides test helpers which enable you to write unit
+tests for your controller code. This is much faster than functional testing,
+and better suited to testing fine grained logic that may exist in your actions.
+
+There is a [mailing list](http://groups.google.com/group/focused_controller)
+for discussion.
+
+## Synopsis ##
+
+``` ruby
+class ApplicationController
+ include FocusedController::Mixin
+end
+
+module PostsController
+ class Index < ApplicationController
+ expose(:posts) { Post.recent.limit(5) }
+ end
+
+ class New < ApplicationController
+ expose(:post) { Post.new }
+ end
+
+ class Singular < ApplicationController
+ expose(:post) { Post.find params[:id] }
+ before_filter { redirect_to root_path unless post.accessible_to?(current_user) }
+ end
+
+ class Show < Singular
+ end
+
+ class Update < Singular
+ def call
+ if post.update_attributes(params[:post])
+ # ...
+ else
+ # ...
+ end
+ end
+ end
+end
+```
+
+Some notes:
+
+* You can include `FocusedController::Mixin` anywhere, so you don't have
+ to use Focused Controller in every single controller if you don't want
+ to
+* `expose` makes the object returned by the block available in your view
+ template. It also memoizes the result so the block will only be
+ executed once.
+* It is not necessary to specify `:only` or `:except` on the before
+ filter, since we declare the filter for exactly the actions we want it
+ to run on. Rails has many methods which accept action names to limit
+ what they get applied to - this is basically irrelevant with Focused
+ Controller due to the increased granularity that having a class for each
+ action gives us.
+* The `#call` method is what gets invoked when the action runs, so put
+ the 'active ingredients' in here.
+
+## Routing ##
+
+Rails' normal routing assumes your actions are methods inside an object
+whose name ends with 'controller'. For example:
+
+``` ruby
+get '/posts/new' => 'posts#new'
+```
+
+will route `GET /posts/new` to `PostsController#new`.
+
+To get around this, we use the `focused_controller_routes` helper:
+
+``` ruby
+Loco2::Application.routes.draw do
+ focused_controller_routes do
+ get '/posts/new' => 'posts#new'
+ end
+end
+```
+
+The route will now map to `PostsController::New#call`.
+
+All the normal routing macros are also supported:
+
+``` ruby
+focused_controller_routes do
+ resources :posts
+end
+```
+
+## Functional Testing ##
+
+If you wish, focused controllers can be tested in the classical
+'functional' style. It no longer makes sense to specify the method name
+to be called as it would always be `#call`. So this is omitted:
+
+``` ruby
+require 'focused_controller/functional_test_helper'
+
+module UsersController
+ class CreateTest < ActionController::TestCase
+ include FocusedController::FunctionalTestHelper
+
+ test "should create user" do
+ assert_difference('User.count') do
+ post user: { name: 'Jon' }
+ end
+
+ assert_redirected_to user_path(@controller.user)
+ end
+ end
+end
+```
+
+There is also an equivalent helper for RSpec:
+
+``` ruby
+require 'focused_controller/rspec_functional_helper'
+
+describe UsersController do
+ include FocusedController::RSpecFunctionalHelper
+
+ describe UsersController::Create do
+ it "should create user" do
+ expect { post user: { name: 'Jon' } }.to change(User, :count).by(1)
+ response.should redirect_to(user_path(subject.user))
+ end
+ end
+end
+```
+
+## Unit Testing ##
+
+Unit testing is faster and better suited to testing logic than
+functional testing. To do so, you instantiate your action class and call
+methods on it:
+
+``` ruby
+module UsersController
+ class ShowTest < ActiveSupport::TestCase
+ test 'finds the user' do
+ user = User.create
+
+ controller = UsersController::Show.new
+ controller.params = { id: user.id }
+
+ assert_equal user, controller.user
+ end
+ end
+end
+```
+
+### The `#call` method ###
+
+Testing the code in your `#call` method is a little more involved,
+depending on what's in it. For example, your `#call` method may use
+(explicitly or implicitly) any of the following objects:
+
+* request
+* response
+* params
+* session
+* flash
+* cookies
+
+To make the experience smoother, Focused Controller sets up mock
+versions of these objects, much like with classical functional testing.
+It also provides accessors for these objects in your test class.
+
+``` ruby
+require 'focused_controller/test_helper'
+
+module UsersController
+ class CreateTest < ActiveSupport::TestCase
+ include FocusedController::TestHelper
+
+ test "should create user" do
+ controller.params = { user: { name: 'Jon' } }
+
+ assert_difference('User.count') do
+ controller.call
+ end
+
+ assert_redirected_to user_path(controller.user)
+ end
+ end
+end
+```
+
+### Assertions ###
+
+You have access to the normal assertions found in Rails' functional tests:
+
+* `assert_template`
+* `assert_response`
+* `assert_redirected_to`
+
+### Filters ###
+
+In unit tests, we're not testing through the Rack stack. We're just calling the
+`#call` method. Therefore, filters do not get run. If some filter code is
+crucial to what your action is doing then you should move it out of the filter.
+If the filter code is separate, then you might want to unit-test it separately,
+or you might decide that covering it in integration/acceptance tests is
+sufficient.
+
+### RSpec ###
+
+There is a helper for RSpec as well:
+
+``` ruby
+require 'focused_controller/rspec_helper'
+
+describe UsersController do
+ include FocusedController::RSpecHelper
+
+ describe UsersController::Create do
+ test "should create user" do
+ subject.params = { user: { name: 'Jon' } }
+ expect { subject.call }.to change(User, :count).by(1)
+ response.should redirect_to(user_path(subject.user))
+ end
+ end
+end
+```
+
+## Isolated unit tests ##
+
+It is possible to completely decouple your focused controller tests from the
+Rails application. This means you don't have to pay the penalty of starting up
+Rails every time you want to run a test. This is an advanced feature and your
+mileage may vary. The benefit this brings will depend on how coupled your
+controllers/tests are to other dependencies.
+
+Your `config/routes.rb` file is a dependency. When you use a URL helper
+you are depending on that file. As this is a common dependency, Focused
+Controller provides a way to stub out URL helpers:
+
+``` ruby
+module UsersController
+ class CreateTest < ActiveSupport::TestCase
+ include FocusedController::TestHelper
+ stub_url :user
+
+ # ...
+ end
+end
+```
+
+The `stub_url` declaration will make the `user_path` and `user_url`
+methods in your test and your controller return stub objects. These can
+be compared, so `user_path(user1) == user_path(user1)`, but
+`user_path(user1) != user_path(user2)`.
+
+## More examples ##
+
+The [acceptance
+tests](https://github.com/jonleighton/focused_controller/tree/master/test/acceptance)
+for Focused Controller exercise a [complete Rails
+application](https://github.com/jonleighton/focused_controller/tree/master/test/app),
+which uses the plugin. Therefore, you might wish to look there to get
+more of an idea about how it can be used.
+
+(Note that the code there is based on Rails' scaffolding, not how I
+would typically write controllers and tests.)