Or: How to avoid and refactor spaghetti code
Please note that I tried to keep the examples small. The effects of the methods in this card are of course much more significant with real / more complex code.
What are the benefits of more modular code?
Code is written once but read often (by your future self and other developers who have to understand it in order to make changes for example). With more modular code you reduce the scope of what has to be understood in order to change something. Also, naming things gives you the opportunity to convey meaning which can be especially helpful for understanding.
Functions
When starting from scratch
Using code that does net yet exist
Sometimes it can be easier to find out the best API of a class by using that code first from the place that will be using it.
This will give you a better practical oriented viewpoint which exact methods will be required to make the usage of this class as easy and loosely coupled as possible.
- Write the view first for the required model
- Write the controller first for the required model
- Write the tests first for the code
bad:
While doing this you can for example see easier when you might be violating the 
  Law of Demeter
  
    Show archive.org snapshot
  
.ul
  @report.items.each do |item| 
    .li item.result.attribute
good:
.ul
  @report.items.each do |item| 
    .li item.result_attribute
Write an outline first
When writing a new function, start with writing down what it has to do as functions - kind of the table of contents:
good:
def create_report
  data = gather_data
  warn_about_missing_data(data)
  cleaned_data = clean_data(data)
  generate_report(cleaned_data)
end
Now try to implement each new function from your list. Do this again by thinking about what the function has to do and writing an outline. Repeat until you have to actually implement something because you can no longer write outlines in a meaningful way.
However, if 90% of your functions only have one line of code you may have overdone it.
When refactoring code
This could be code from someone else or code you've just written.
Write a function instead of variable assignment
bad:
def my_function(employees)
  contact = employees['product owner'] || employees['lead developer'] || employees['trainee']
  do_something(contact)
  # ...
end
better:
def my_function(employees)
  contact = find_contact(employees)
  do_something(contact)
  # ...
end
private 
def find_contact(employees)
  employees['product owner'] || employees['lead developer'] || employees['trainee']
end
Write a function instead of adding a comment over a block
This also works well if you could think a comment over several lines of code.
bad:
def my_function
  # prepare report data
  customer_data = read(:customer_data, {})
  customer_recommendations = read(:substituted_customer_recommendations, '')
  report_data = customer_data.merge({customer_recommendations: customer_recommendations})
  json_data = { report_data: report_data }
  # write report data
  filename = 'report_data.json'
  result = Result.new(
    file: FileIO.new(json_data.to_json, filename)
  )
  result.save!
end
better:
def my_function
  report_data = prepare_report_data
  write_report_data(report_data)
end
def prepare_report_data
  customer_data = read(:customer_data, {})
  customer_recommendations = read(:substituted_customer_recommendations, '')
  report_data = customer_data.merge({customer_recommendations: customer_recommendations})
  { report_data: report_data }
end
def write_report_data(report_data)
  filename = 'report_data.json'
  result = Result.new(
    file: FileIO.new(report_data.to_json, filename)
  )
  result.save!
end
Write a function instead of repeating code (-> DRY, don't repeat yourself)
bad:
def preview(data)
  sanitized = sanitze(data)
  write(sanitized)
  navigate_to(:preview)
end
def next(data)
  sanitized = sanitze(data)
  write(sanitized)
  navigate_to(:back)
end
better:
def preview(data)
  save_data(data)
  navigate_to(:preview)
end
def next(data)
  save_data(data)
  navigate_to(:back)
end
private
def save_data(data)
  sanitized = sanitze(data)
  write(sanitized)
end
Disclaimer: You should not try to overdo it with DRY. If you have to add many parameters to the new function to make it work for all use-cases it may be better to keep the code separate. Functions with lots of parameters are generally harder to understand.
Write a function instead of asking an object about all its interna: Tell don't ask
This is all about where code should live. If you're asking an object a lot about interna, that's a code smell. It would be better to ask the object to do the things you want for you. Try to refactor that out of your method into its own method in the class where it belongs.
bad:
class MyClass
  def create_target(network_profile)
    subnet = if network_profile.ip_configured?
      IPAddr.new("#{network_profile.ip_address}/#{network_profile.ip_netmask}").to_s
    end
    name = 'My Target'
    Target.create(subnet: subnet, name: name)
  end
end
better:
class MyClass
  def create_target(network_profile)
    Target.create(subnet: network_profile.subnet, name: 'My Target')
  end
end
class NetworkProfile
  def subnet
    if ip_configured?
      IPAddr.new("#{ip_address}/#{ip_netmask}").to_s
    end
  end
end
Classes
If you followed the advice in the first part of this card you now have lots of functions. Those should be structured with classes.
Write service classes
If you have several functions that could be grouped together (e.g. because they are operating on the same data or do something similar), consider adding a service class for them.
good:
class VersionParser
  def self.parse_version(version)
    version = remove_version_appendix(version)
    Versionomy.parse(version)
  end
  def self.major_version_change?(version_1, version_2)
    return false if version_1.blank? || version_2.blank?
    parse_major_version(version_1) != parse_major_version(version_2)
  end
  private
  def self.parse_major_version(version)
    parsed = version.is_a?(Versionomy::Value) ? version : parse_version(version)
    parsed.major
  end
  def self.remove_version_appendix(version)
    version.split('+').first if version
  end
end
Write form models
Form models Show archive.org snapshot are not only a nice way to structure your code but also help you get away from callback hell.
Catchy examples for form models are things like user signup or password change forms (note: you should use a library for such things instead of building them yourself. But it is a good example).
class User < ApplicationRecord
  attr_accessor :new_password, :new_password_confirmation
  validates :new_password, enforce_password_policy: true
  validates :new_password_confirmation, presence: true
  validates :password, presence: true
  before_save :store_new_password
  def store_new_password
    # ...
  end
  # ...
end
For most of the time you use the user model these validations and callbacks are useless because they are only used on one screen. So they should move to a form model:
class User::PasswordChange < ActiveType::Record[User]
  attr_accessor :new_password, :new_password_confirmation
  validates :new_password, enforce_password_policy: true
  validates :new_password_confirmation, presence: true
  validates :password, presence: true
  before_save :store_new_password
  def store_new_password
    # ...
  end
end
Write classes for parameters that belong together
Sometimes you see multiple functions that always use the same parameters. This often happens if the parameters only make sense together - in this case you could add a new class.
bad:
class Geometry
  def draw_circle(x, y, radius)
    # ...
  end
  def move_shape(start_x, start_y, end_x, end_y, shape)
    # ...
  end
  #...
end
better:
class Position
  attr_accessor :x, :y
  def initialize(x, y)
    @x = x
    @y = y
  end
  def get_x
    @x
  end
  # ...
end
class Geometry
  def draw_circle(position, radius)
    # ...
  end
  def move_shape(start_position, end_position, shape)
    # ...
  end
end
Namespaces
Use namespaces to structure your classes. Start early, latest when you have 3-4 classes that belong together.