Rails™ AntiPatterns Summary Part 1

Rails™ AntiPatterns Summary Part 1

Introduction and Chapter 1

Note: Some of the book's examples and sections have been modified to match modern rails (Rails 6+)

About the book

The book Rails™ AntiPatterns - Best Practice Ruby on Rails™ Refactoring was written by Chad Pytel and Tammer Saleh to improve the way of typing rails applications as we will discuss in the following sections, you can find the book on O'Reilly Rails™ AntiPatterns - Best Practice Ruby on Rails™ Refactoring.

Introduction

Rails has been used widely in many applications nowadays such as Shopify, Github, and Basecamp.
Also, a lot of people tend to use this framework for creating applications.
Before we start we have to understand some concepts:

What Are AntiPatterns?

AntiPatterns are common approaches to recurring problems that ultimately prove to be ineffective.
There must be at least two key elements present to formally distinguish an actual AntiPattern from a simple bad habit, bad practice, or bad idea:

  • A repeated pattern of action, process, or structure that initially appears to be beneficial but ultimately produces more bad consequences than beneficial results
  • A refactored solution that is clearly documented, proven in actual practice, and repeatable.

What Is Refactoring?

Refactoring is the act of modifying an application’s code NOT to change its functional behavior but instead to improve the quality of the application itself.
These improvements are intended to improve readability, reduce complexity, increase maintainability, and improve the extensibility (that is possibility for future growth) of the system.

Chapter 1: Models

The Model layer of your Rails application often provides the core structure of your application.
The Model layer should also contain the business logic of your application. Therefore, the models of your application will often receive the majority of developer attention throughout a project’s lifecycle.
Because so much attention is paid to the models, and because so much responsibility is contained within them, it’s relatively easy for things to get out of hand in the models as an application grows and evolves.

This chapter covers many of the common pitfalls that can occur in the Model layer of the application, and we present proven techniques for recovering from these issues—and avoiding them in the first place.

AntiPattern: Voyeuristic Models

  • A well-intentioned programmer may create an application that breaks the fundamental tenets of object-oriented programming for a variety of reasons. For example, if the programmer is coming from a less structured web development framework (or un-framework) such as Perl or PHP, they may simply not be aware of the structure that Ruby on Rails, MVC, and object-oriented programming provide. they may apply what they know about their current environment to a program she’s building using Ruby on Rails.
  • A programmer very experienced with object-oriented programming and MVC may first approach the Rails framework and be distracted by the dynamic nature of the Ruby language and unfamiliar with what he might consider being unique aspects of the Ruby language, such as modules. Distracted by these things, he might proceed to build a system without first considering the overall architecture and principles with which he is familiar because he perceives Ruby on Rails to be different. Or perhaps a programmer is just overwhelmed by what the Ruby on Rails framework provides—such as generators, lifecycle methods, and the Active Record ORM—that she may get distracted by the immense capability and build a system too quickly, too messily, and without foresight or engineering discipline.
  • The following sections present several scenarios that violate the core tenets of MVC and object-oriented programming, and they present alternative implementations and procedures for fixing these violations to help produce more readable and maintainable code.

Solution: Follow the Law of Demeter

An incredibly powerful feature of Ruby on Rails is Active Record associations, which are incredibly easy to set up, configure, and use. This ease of use allows you to dive deep down and across associations, particularly in views. However, while this functionality is powerful, it can make refactoring tedious and error-prone.
Say that you’ve properly encapsulated your application’s functionality inside different models, and you’ve been effectively breaking up functionality into small methods on the models to so the code look like the following:

# app/models/address.rb
class Address < ActiveRecord::Base
  belongs_to :customer
end

# app/models/customer.rb
class Customer < ActiveRecord::Base
  has_one :address
  has_many :invoices
end

# app/models/invoice.rb
class Invoice < ActiveRecord::Base
  belongs_to :customer
end

This code shows a simple invoice structure, with a customer who has a single address. The view code to display the address lines for the invoice would be as follows:

<!-- app/views/addresses/show.html.erb -->
<%= @invoice.customer.name %>
<%= @invoice.customer.address.street %>
<%= @invoice.customer.address.city %>
<%= @invoice.customer.address.state %>
<%= @invoice.customer.address.zip_code %>

Ruby on Rails allows you to easily navigate between the relationships of objects and therefore makes it easy to dive deep within and across related objects.

Problem

While this is really powerful, there are a few reasons it’s not ideal. For proper encapsulation, the invoice should NOT reach across the customer object to the street attribute of the address object. Because in the future if your application were to change so that a customer has both a billing address and a shipping address, every place in your code that reached across these objects to retrieve the street would break and would need to change.

Solution

To avoid the problem just described, it’s important to follow the Law of Demeter, also known as the Principle of Least Knowledge.
The concept is that an object can call methods on a related object but that it should NOT reach through that object to call a method on a third related object.
In Rails, this could be summed up as “use only one dot.” For example, @invoice.customer.name breaks the Law of Demeter, but @invoice.customer_name does not. Of course, this is an oversimplification of the principle, but it can be used as a guideline.
To follow the Law of Demeter, you could rewrite the code above as follows:

# app/models/address.rb
class Address < ActiveRecord::Base
  belongs_to :customer
end

# app/models/customer.rb
class Customer < ActiveRecord::Base
  has_one :address
  has_many :invoices

  def street
    address.street
  end

  def city
    address.city
  end

  def state
    address.state
  end

  def zip_code
    address.zip_code
  end
end

# app/models/invoice.rb
class Invoice < ActiveRecord::Base
  belongs_to :customer

  def customer_street
    customer.street
  end

  def customer_city
    customer.city
  end

  def customer_state
    customer.state
  end

  def customer_zip_code
    customer.zip_code
  end
end

Then the invoice view would look like:

<!-- app/views/addresses/show.html.erb -->
<%= @invoice.customer_name %>
<%= @invoice.customer_street %>
<%= @invoice.customer_city %>
<%= @invoice.customer_state %>
<%= @invoice.customer_zip_code %>

The downside to this approach is that the classes have been littered with many small wrapper methods. If things were to change, now all of these wrapper methods would need to be maintained.
Fortunately, Ruby on Rails includes a function that addresses the this concern. This method is the class-level delegate method. This method provides a shortcut for indicating that one or more methods that will be created on your object are actually provided by a related object. Using this delegate method, you can rewrite your example like this:

# app/models/address.rb
class Address < ActiveRecord::Base
  belongs_to :customer
end

# app/models/customer.rb
class Customer < ActiveRecord::Base
  has_one :address
  has_many :invoices

  delegate :street, :city, :state, :zip_code, to: :address
end

# app/models/invoice.rb
class Invoice < ActiveRecord::Base
  belongs_to :customer

  delegate :street, :city, :state, :zip_code, to: :customer, prefix: true #use prefix so you add customer before the delegated method
end

Now nothing needs to be changed in the view.