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.

Buy now for $49 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.

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 Untitled Kingdom

“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. Refactoring
      1. Duplicate, duplicate, duplicate
    5. 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
    6. Tools
    7. How to use this book
      1. The structure
  3. Refactoring recipes
    1. Inline controller filters
      1. Example
      2. Warnings
      3. Resources
    2. Explicitly render views with locals
      1. Introduction
      2. Algorithm
      3. Example
      4. Benefits
      5. Warnings
      6. Resources
    3. Extract render/redirect methods
      1. Introduction
      2. Algorithm
      3. Example
      4. Benefits
      5. Warnings
    4. Extract a Single Action Controller class
      1. Introduction
      2. Algorithm
      3. Example
      4. Benefits
      5. Warnings
      6. Resources
    5. Extract routing constraint
      1. Introduction
      2. Prerequisites
      3. Algorithm
      4. Example
      5. Benefits
      6. Warnings
      7. Resources
    6. Extract an adapter object
      1. Introduction
      2. Algorithm
      3. Example
      4. Benefits
      5. Warnings
      6. Resources
    7. Extract a repository object
      1. Introduction
      2. Prerequisites
      3. Algorithm
      4. Example
      5. Benefits
      6. Warnings
      7. Resources
    8. Extract a service object using the SimpleDelegator
      1. Prerequisites
      2. Algorithm
      3. Example
      4. Benefits
      5. Resources
  4. Example: TripReservationsController#create
    1. Extract 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
  5. Example: logging time
    1. The starting point
      1. The `aha' moment
  6. Patterns
    1. Instantiating service objects
      1. Boring style
      2. Modules
      3. Dependor
    2. The repository pattern
      1. ActiveRecord class as a repository
      2. Explicit repository object
      3. No logic in repos
      4. Transactions
      5. The danger of too small repositories
      6. In-memory repository
    3. 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
    4. 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?
    5. Service::Input
  7. Testing
    1. Introduction
    2. Good tests tell a story.
    3. Unit tests vs class tests
      1. Class tests slow me down
      2. Test units, not classes
      3. The Billing example
    4. Techniques
      1. Service objects as a way of testing Rails apps (without factory_girl)
  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
  10. Bonus
    1. Pretty, short urls for every route in your Rails app
      1. Top level routing for multiple resources
      2. Render or redirect
      3. Top level routing for everything
      4. Is it any good?
"Less Rails" - E-mail course accompanying the book

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 - more than 1000 copies already sold

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 if I want to buy more copies of the book?

Thinking of your team, right? That's great! We've got a special deal - you can now buy 5 copies in the price of 4, if you use the link: Fearless Refactoring: Rails Controllers x 5 reader license. You save $49 this way!

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.

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