Ruby Science

Tell, Don’t Ask

The Tell, Don’t Ask principle advises developers to tell objects what they want done, rather than querying objects and making decisions for them.

Consider the following example:

class Order
  def charge(user)
    if user.has_valid_credit_card?
      user.charge(total)
    else
      false
    end
  end
end

This example doesn’t follow tell, don’t ask. It first asks if the user has a valid credit card, and then makes a decision based on the user’s state.

In order to follow tell, don’t ask, we can move this decision into User#charge:

class User
  def charge(total)
    if has_valid_credit_card?
      payment_gateway.charge(credit_card, total)
      true
    else
      false
    end
  end
end

Now Order#charge can simply delegate to User#charge, passing its own relevant state (total):

class Order
  def charge(user)
    user.charge(total)
  end
end

Following this principle has a number of benefits, outlined below.

Encapsulation of Logic

Following tell, don’t ask encapsulates the conditions under which an operation can be performed in one place. In the above example, User should know when it makes sense to charge.

Encapsulation of State

Referencing another object’s state directly couples two objects together based on what they are, rather than on what they do. By following tell, don’t ask, we encapsulate state within the object that uses it, exposing only the operations that can be performed based on that state and hiding the state itself within private methods and instance variables.

Minimal Public Interface

In many cases, following tell, don’t ask will result in the smallest possible public interface between classes. In the above example, has_valid_credit_card? can now be made private, because it becomes an internal concern encapsulated within User.

Public methods are a liability. Before they can be changed, moved, renamed or removed, you will need to find every consumer class and update each one accordingly.

Tension with Model—View—Controller

This principle can be difficult to follow while also following Model—View—Controller (MVC).

Consider a view that uses the above Order model:

<%= form_for @order do |form| %>
  <% unless current_user.has_valid_credit_card? %>
    <%= render 'credit_card/fields', form: form %>
  <% end %>
  <!-- Order Fields -->
<% end %>

The view doesn’t display the credit card fields if the user already has a valid credit card saved. The view needs to ask the user a question and then change its behavior based on that question, violating tell, don’t ask.

You could obey tell, don’t ask by making the user know how to render the credit card form:

<%= form_for @order do |form| %>
  <%= current_user.render_credit_card_form %>
  <!-- Order Fields -->
<% end %>

However, this violates MVC by including view logic in the User model. In this case, it’s better to keep the model, view and controller concerns separate and step across the tell, don’t ask line.

When writing interactions between other models and support classes, though, make sure to give commands whenever possible and avoid deviations in behavior based on another class’s state.

Application

These smells may be a sign that you should be following tell, don’t ask more:

  • Feature envy is frequently a sign that a method or part of a method should be extracted and moved to another class, to reduce the number of questions that method must ask of another object.
  • Shotgun surgery may result from state and logic leaks. Consolidating conditionals using tell, don’t ask may reduce the number of changes required for new functionality.

If you find classes with these smells, they may require refactoring before you can follow tell, don’t ask:

  • Case statements that inflect on methods from another object generally get in the way.
  • Mixins blur the lines between responsibilities, as mixed in methods operate on the state of the objects they’re mixed into.

If you’re trying to refactor classes to follow tell, don’t ask, these solutions may be useful:

Many Law of Demeter violations point toward violations of tell, don’t ask. Following tell, don’t ask may lead to violations of the single responsibility principle and the open/closed principle, since moving operations onto the best class may require modifying an existing class and adding a new responsibility.

Ruby Science

The canonical reference for writing fantastic Rails applications from authors who have created hundreds.

Work with us to make a new Rails app, or to maintain, improve, or scale your existing app.