Build Robust & Production Quality Applications - Lesson 3: Part 2

Items Covered:

  1. Structural Refactor
  2. Skinny Controller, Fat Models
  3. Rspec Macros
  4. Rspec Shared Examples

Structural Refactor

Controllers are more of traffic control and methods containing logic should refarctored to the model level

Typically you shouldn't have more than one conditional or return from a method within a method. You should refactor by moving a piece of the code to private method.

Skinny Controller, Fat Models

One of the most common refactors in Rails and one of the most well-known architectural principals in rails.

Fat Models, means the models assume the most responsibilty. Good blog post to read: Skinny Models, Fat Controllers Here - James talks about moving responsibilty from the view, to the controller, and then to the model.

Examples of Opensource Project Management Tools that have need to go on a Fat Controller Diet: ChilliProject Redmine

Refactoring to fat models in MyFlix

Tip: When refactoring your controllers to fat models, do not move methods that take parameters in a form because that is the purpose of the controller - to talk to the views and models. Look for those that iterate through the model.

Rspec Macros

Allows you to extract logic that can be used by multiple rspec controllers.

So rather than the following at the begining of each controller test:

before do
  frank = Fabricate(:user)
  session[:user_id] = frank.id
end

We could create a macro, spec/support/macros.rb and include the above code:

def set_current_user
  frank = Fabricate(:user)
    session[:user_id] = frank.id
  end

def current_user
  User.find(session[user_id])
end

AND in your test:

before {set_current_user}

Now, if I want to clear the current user to test the path of an unauthorized user create another macro called clearcurrentuser, make it the first line in your test.

def clear_current_user
  session[user_id] = nil
end

Shared Examples

What bout you want to test the redirect for unauthenticated users for not just index but index and new

Create a Shared Example:

#spec/support/shares_examples.rb
shared_examples "require_sign_in" do
  it "redirects to the front page" do
    response.should redirect_to root_path
  end
end

In your test: you can't just write:

it behaves_like "require_sign_in"

because you have to clear the current user and hit the action first:

context "user not signed in" do
  before do
    clear_current_user
    get :index
  end
  it behaves_like "require_sign_in"
end

But this is frustrating becasue although we DRY'd up our code a little, we still have to add a before block for the clearcurrentuser and the index.

It would be great if there's a way to pass the action to the shared example, and there is:

shared_examples "require_sign_in" do
  it "redirects to the front page" do
    clear_current_user
    action
    response.should redirect_to root_path
  end
end

AND in your test:

context "not_signed_in" do
    it_behaves_like "require_sign_in" do
    let(:action) { get :index }
  end
end

AND you actually don't need the context anymore:

<index>
it_behaves_like "require_sign_in" do
  let(:action) { get :index }
end

<new>
it_behaves_like "require_sign_in" do
  let(:action) { get :new }
end

<create>
it_behaves_like "require_sign_in" do
  let(:action) { post :create, todo: {name: 'something'} }
end

Shared examples can be used on both controller and model levels For more info:

Shared Examples Docs