Rails refactoring

Are your controllers looking like this?

class IssuesController < ApplicationController
  default_search_scope :issues

  before_filter :find_issue, :only => [:show, :edit, :update]
  before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
  before_filter :find_project, :only => [:new, :create, :update_form]
  before_filter :authorize, :except => [:index]
  before_filter :find_optional_project, :only => [:index]
  before_filter :check_for_default_issue_status, :only => [:new, :create]
  before_filter :build_new_issue_from_params, :only => [:new, :create, :update_form]
  accept_rss_auth :index, :show
  accept_api_auth :index, :show, :create, :update, :destroy

  rescue_from Query::StatementInvalid, :with => :query_statement_invalid

  def bulk_update
    @issues.sort!
    @copy = params[:copy].present?
    attributes = parse_params_for_bulk_issue_attributes(params)

    unsaved_issues = []
    saved_issues = []

    if @copy && params[:copy_subtasks].present?
      # Descendant issues will be copied with the parent task
      # Don't copy them twice
      @issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}}
    end

    @issues.each do |orig_issue|
      orig_issue.reload
      if @copy
        issue = orig_issue.copy({},
          :attachments => params[:copy_attachments].present?,
          :subtasks => params[:copy_subtasks].present?
        )
      else
        issue = orig_issue
      end
      journal = issue.init_journal(User.current, params[:notes])
      issue.safe_attributes = attributes
      call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
      if issue.save
        saved_issues << issue
      else
        unsaved_issues << orig_issue
      end
    end

    if unsaved_issues.empty?
      flash[:notice] = l(:notice_successful_update) unless saved_issues.empty?
      if params[:follow]
        if @issues.size == 1 && saved_issues.size == 1
          redirect_to issue_path(saved_issues.first)
        elsif saved_issues.map(&:project).uniq.size == 1
          redirect_to project_issues_path(saved_issues.map(&:project).first)
        end
      else
        redirect_back_or_default _project_issues_path(@project)
      end
    else
      @saved_issues = @issues
      @unsaved_issues = unsaved_issues
      @issues = Issue.visible.where(:id => @unsaved_issues.map(&:id)).all
      bulk_edit
      render :action => 'bulk_edit'
    end
  end
end

Coding an app in Rails is always fun... for the first 3 months.

After some time, small, little things show up. The tests are no longer below 1 minute. Thin controllers are not really so thin anymore. Single Responsibility Principle is more of a not-so-funny joke than something that you can apply to your Rails models.

The build is taking so long at your machine, that you've almost automated it to immediately open Twitter, gmail, FB, HackerNews because nothing else can be done during the tests.

The speed of adding features slows down. It's no longer fun to demonstrate new features to the Customer.

Your development plan for this release sounds so great. The users could be so happy with the new features. Your code could be so elegantly designed.

Is this really all you did this week?

"Can we agree that we keep controllers thin?" - the team starts to have those little arguments. The model callbacks are killing you. They started simple and now you just keep adding the conditionals.

How can we unit test if it's all coupled together?

You'd love to unit test your models and controllers, but something doesn't feel right about the endless section of mocks, stubs. User.should_receive(this), should_receive(that). It feels like repeating the code. What's the point of such tests?

How do other Rails developers deal with such problems? Rails conventions are great at the start, after that you're on your own.

This shouldn't be so hard!

What if you could have speedy and intuitive conventions even after the app grew over time?

If you had conventions, you would spend less time moving code from one place to another, hoping that you will hide it somewhere. Less time arguing in the team means more features being delivered. More features means happy customers. We want to have happy users and customers.

What if you could add features as fast as at the start?

Fearless Refactoring: Rails Controllers - the book

It's possible to have the same speed of delivering features over time and the "Fearless Refactoring: Rails Controllers" book will teach you how.

This book contains a distilled knowledge taken from reliable architectures, like DDD, Hexagonal Architecture, DCI. I spend years researching those topics with one goal.

Make your application better, simply, step-by-step, under control.

I took what's best from the popular architectures, got rid of things that don't fit well with the Rails specifics and prepared recipes how to deal with a messy Rails code.

Who has time to stay on top of all the latest articles, ideas, and code samples? You need good, working, easy to change code.

This book is a step-by-step guide how to introduce a service layer in your existing Rails app. Every step is described in every detail, with code changes. All of that, so that you can safely refactor your codebase, even without a full test coverage.

This is a beta book. It's not quite finished but I wanted to get it into your hands so you can start using it. As an early adopter, you get a discount, all updates and access to the final copy

Buy now for only $49 $35 in PDF, epub and mobi.

Download a sample

Get a better idea of what is included with the book.

Sample chapter is describing refactoring technique called "render view with locals". It is great technique to make explicit what data must be provided for the view for it to be working correctly. In few years old Rails applications it is common that controllers set up a lot of instance variables and some of the views depend on them and some do not. Over time it's becoming harder and harder to maintain and refactor views and controllers.

Explicitness here makes it easier to find out common usages and where things differ. It makes refactoring easier for both parts. When you know in your controllers which instance variables are not used by the view you can more easily extract them to methods, local variables or even remove completely if they are no longer needed.

Refactoring view templates and partials also becomes easier because you know what must be provided for them to work. So you can more easily reuse already existing code in other parts of the applications. There are no longer hidden dependencies such as instance variables set up by before-filter from parent controller.

Receive a free course

Receive a free 7 day email course teaching you better refactoring skills focused on Rails controllers. Contains also one part about the human factor of refactorings (not all team members being equally pro-refactoring, different visions, etc). Get a discount when the book is completed.

What others say?

“"Fearless Refactoring - Rails Controllers" by Andrzej Krzywda is a great resource for everyone who at least once encountered legacy Rails application. With good examples, refactoring techniques, and step by step guides it is a worthy read for novices and intermediate developers. Even if you are an expert, the book will save you time by giving you tested solutions for dealing with legacy Rails apps.”

Michal Taszycki, Señor Software Developer at GunpowderLabs

“Fearless Refactoring helped me embrace the simplicity and powerfulness of service objects and lean controller code. Ever since I've started using exception-based control flow my code became much more readable and fun to work with. 10/10 would recommend.”

Michal Matyas, mid-senior developer at Growth Republic

“In our company each developer who achieved basic knowledge about rails way and all of it benefits and disatvantages gets a really important ticket - read Fearless Refactoring asap.

This book helps us refactor old legacy code that is a lot more complicated that it should be for given feature. All solutions included there give us knowledge how to resolve complex problems by writing clear and well-organized code using the best OOP rules.”

Adam Piotrowski, CTO at 2N

Table of Contents

  1. Table of Contents
  2. Rails controllers
    1. What is the problem with Rails controllers?
      1. Why the focus on controllers?
      2. Testing
      3. The gateway drug - service objects
      4. The Boy Scout rule
      5. Inspiration
    2. Why service objects?
      1. Why not service objects?
    3. What is a Rails service object?
      1. What it's not
    4. Rails Controller - Commands and Queries
    5. Refactoring
      1. Duplicate, duplicate, duplicate
    6. Refactoring and the human factor
      1. Do we really need to change the existing code?
      2. Refactoring takes a lot of time
      3. I wouldn't refactor this part
      4. I would refactor it differently
      5. Summary
    7. Tools
    8. How to start?
      1. Resources
  3. Refactoring techniques
    1. Inline controller filters
      1. Example
      2. Warnings
      3. Resources
    2. Explicitly render views with locals
      1. Introduction
      2. Example
      3. Benefits
      4. Warnings
      5. Resources
    3. Move model callback to the controller
      1. Introduction
      2. Algorithm
      3. Example
      4. Warnings
      5. Resources
    4. Wrap external API with an adapter
      1. Introduction
      2. Example
      3. Another long example
      4. Adapters and architecture
      5. Multiple Adapters
      6. Injecting and configuring adapters
      7. One more implementation
      8. The result
      9. Changing underlying gem
      10. Adapters configuration
      11. Testing adapters
      12. Dealing with exceptions
      13. Adapters ain't easy
      14. Summary
    5. Extract render/redirect methods
      1. Introduction
      2. Example
      3. Benefits
  4. Example: TripReservationsController/create
    1. Extracting a service object
      1. Move the whole action code to the service, using SimpleDelegator
      2. Explicit dependencies
      3. What's an external dependency?
      4. Resources
    2. Service - controller communication
      1. How do we deal with failures?
      2. Extracting exceptions
      3. No more controller dependency
      4. Move the service to its own file
      5. Summary
    3. The database access
  5. Example: logging time
    1. The starting point
      1. The `aha' moment
  6. Other Techniques
    1. 4 ways to early return from a rails controller
      1. 1. redirect_to and return (classic)
      2. 2. extracted_method and return
      3. 2.b extracted_method or return
      4. 3. extracted_method{ return }
      5. 4. extracted_method; return if performed?
      6. throw :halt (sinatra bonus)
      7. throw :halt (rails?)
      8. why not before filter?
    2. Using UUIDs
    3. Service::Input
  7. Testing
    1. Introduction
    2. Good tests tell a story.
    3. Unit tests in a typical Rails app
    4. Typical testing smells
    5. Techniques
      1. Service objects as a way of testing Rails apps (without factory_girl)
      2. Test with scenarios
      3. Turn fixtures into Service-based state
      4. Test with in-memory adapters
      5. Test with actors
  8. Related topics
    1. Service controller communication
    2. Naming Conventions
      1. The special .call method
    3. Where to keep services
    4. Routing constraints
      1. Resources
    5. Rails controller - the lifecycle
      1. Accessing instance variables in the view
      2. Resources
  9. Appendix
    1. Thank you

About the author

Andrzej Krzywda

Arkency

Hi, I'm Andrzej Krzywda, the founder and CEO of Arkency, a Rails consultancy. I spend time reviewing dozens of legacy Rails apps every year, finding patterns, applying fixes. I care about code quality, maintainability and explicitiness.

For 5 years I've been teaching Rails at the University of Wrocław in a way that shows my students the beauty and speed of delivering Rails applications, as well as the mess that they can sometimes turn into. Working with students have taught me patience and how to structure my knowledge so it is easily digestable.

I live in a small village near Wrocław in Poland, with two kids and my wife. As almost everyone in Arkency I work remotely from home.

Buy The Book

Fearless Refactoring: Rails controllers

In Fearless Refactoring: Rails controllers I'll teach how to improve your Rails controllers in a quick and safe way. This is step-by-step guide so you won't feel lost. Every step is described in every detail, with code changes. All of that, so that you can safely refactor your codebase, even without a full test coverage.

One more thing

What are the supported formats?

Right now we are supporting PDF, .ePub and Amazon Kindle (.Mobi) format. In our opinion it is most convinient to read the book as PDF. You will receive all three formats in a nicely packed ZIP file. What else could you want 😉 ?

What if I don't like this book?

If the product doesn't satisfy you, just reply to your purchase receipt email and I will issue a refund. No hard feelings. However only 0,5% customers asked for refund so far.

What does beta mean?

It's not quite finished but I wanted to get it into your hands so you can start using it. As an early adopter, you get a discount, all updates and access to the final copy. It means you can start refactoring faster.

How am I gonna get the updates?

We will send them to you via email to the same address that you provide during checkout process. As well with a Changelog so that you can easily focus on the new content.

Ok, where can I buy?

I thought you would never ask. Add to Cart

Are you ready to buy?

Privacy Policy