Home About me Blog Contact Github Explore
Ruby on Rails

Small Things in Ruby That Make a Huge Difference

I've been writing Ruby for 15 years. And you know what Ive learned? The languages power isnt in the big features – its in the small, elegant details that compound over time.

Small Things in Ruby That Make a Huge Difference

These aren't flashy framework features or architectural patterns. These are the little Ruby idioms that, once you internalize them, make your code cleaner, shorter, and more expressive.

Let me show you what I mean.

Enumerable: Ruby's Secret Weapon

When I interview Ruby developers, I can immediately tell their experience level by how they use Enumerable methods. Juniors loop through everything. Seniors rarely write explicit loops.

The Progression of a Ruby Developer

Stage 1: The Beginner (Everything is a for loop)


# Find all published posts
published_posts = []
for post in posts
  if post.published?
    published_posts << post
  end
end

# Calculate total price
total = 0
for item in cart_items
  total = total + item.price
end

# Get user emails
emails = []
for user in users
  emails << user.email
end

This works. But it's verbose, imperative, and un-Ruby-like.

Stage 2: The Intermediate (Discovers basic Enumerable)


# Find all published posts
published_posts = posts.select { |post| post.published? }

# Calculate total price
total = cart_items.sum { |item| item.price }

# Get user emails
emails = users.map { |user| user.email }

Much better. More declarative. More readable. Less noise.

Stage 3: The Advanced (Chains methods fluently)


# Get emails of active users with verified accounts
User.active
    .verified
    .order(:created_at)
    .limit(100)
    .pluck(:email)

# Group orders by status and calculate totals
orders.group_by(&:status)
      .transform_values { |orders| orders.sum(&:total) }

# Find the most expensive item in each category
products.group_by(&:category)
        .transform_values { |prods| prods.max_by(&:price) }

This is where Ruby shines. Readable, expressive, elegant.

Real-World Examples That Changed My Code

Let me show you some before/after transformations from actual projects.

Example 1: Finding duplicates


# Before (ugly nested loops)
duplicates = []
users.each_with_index do |user, i|
  users.each_with_index do |other_user, j|
    if i != j && user.email == other_user.email
      duplicates << user unless duplicates.include?(user)
    end
  end
end

# After (one line)
duplicates = users.group_by(&:email)
                  .values
                  .select { |group| group.size > 1 }
                  .flatten

Example 2: Conditional transformations


# Before
result = []
items.each do |item|
  if item.active?
    result << item.name.upcase
  end
end

# After
result = items.select(&:active?)
              .map { |item| item.name.upcase }

# Even better (method chaining)
result = items.select(&:active?)
              .map(&:name)
              .map(&:upcase)

Example 3: Complex filtering

I had to implement a feature: "Find users who have placed orders in the last 30 days, spent over $100, and haven't left a review."


# Before (procedural nightmare)
eligible_users = []

users.each do |user|
  recent_orders = []
  
  user.orders.each do |order|
    if order.created_at > 30.days.ago
      recent_orders << order
    end
  end
  
  total_spent = 0
  recent_orders.each do |order|
    total_spent += order.total
  end
  
  has_review = false
  user.reviews.each do |review|
    if review.created_at > 30.days.ago
      has_review = true
      break
    end
  end
  
  if total_spent > 100 && !has_review
    eligible_users << user
  end
end

# After (declarative bliss)
eligible_users = users.select do |user|
  recent_orders = user.orders.where('created_at > ?', 30.days.ago)
  recent_orders.sum(:total) > 100 && user.reviews.recent.none?
end

But we can do even better with scopes:


# In User model
scope :with_recent_orders, -> { 
  joins(:orders).where('orders.created_at > ?', 30.days.ago).distinct 
}

scope :high_spenders, -> (amount) {
  joins(:orders)
    .where('orders.created_at > ?', 30.days.ago)
    .group('users.id')
    .having('SUM(orders.total) > ?', amount)
}

scope :without_recent_reviews, -> {
  where.not(id: Review.where('created_at > ?', 30.days.ago).select(:user_id))
}

# Usage
eligible_users = User.with_recent_orders
                     .high_spenders(100)
                     .without_recent_reviews

Clean. Testable. Reusable.

The Enumerable Methods You Should Master

Here are the ones I use constantly:

select / reject – Filtering


# Get active users
User.all.select(&:active?)

# Exclude admins
users.reject(&:admin?)

# Complex conditions
posts.select { |p| p.published? && p.comments_count > 10 }

map / pluck – Transformation


# Transform objects
users.map(&:email)
users.map { |u| [u.id, u.name] }.to_h

# From database (more efficient)
User.pluck(:email)
User.pluck(:id, :name) # Returns array of arrays

sum / count – Aggregation


# Sum
orders.sum(&:total)
Order.sum(:total) # Database-level, more efficient

# Count
users.count(&:active?)
User.where(active: true).count # Database-level

group_by – Grouping


# Group users by role
users.group_by(&:role)
# => { "admin" => [...], "user" => [...] }

# Group orders by month
orders.group_by { |o| o.created_at.beginning_of_month }

# With transform_values
orders.group_by(&:status)
      .transform_values { |orders| orders.sum(&:total) }
# => { "pending" => 1500, "completed" => 8500 }

find / detect – First match


# First user with this email
users.find { |u| u.email == 'john@example.com' }

# More efficient from database
User.find_by(email: 'john@example.com')

any? / all? / none? – Checks


# Any published posts?
posts.any?(&:published?)

# All users verified?
users.all?(&:verified?)

# No pending orders?
orders.none? { |o| o.status == 'pending' }

partition – Split into two groups


# Split users into active and inactive
active, inactive = users.partition(&:active?)

zip – Combine arrays


names = ['Alice', 'Bob', 'Charlie']
ages = [25, 30, 35]
names.zip(ages)
# => [['Alice', 25], ['Bob', 30], ['Charlie', 35]]

names.zip(ages).to_h
# => { 'Alice' => 25, 'Bob' => 30, 'Charlie' => 35 }

reduce / inject – Fold


# Sum (though .sum is clearer)
[1, 2, 3, 4].reduce(0) { |sum, n| sum + n }

# Build a hash
users.reduce({}) { |hash, user| 
  hash.merge(user.id => user.name) 
}

# More complex: Build a tree structure
comments.reduce({}) do |tree, comment|
  tree[comment.parent_id] ||= []
  tree[comment.parent_id] << comment
  tree
end

The Power of Method Chaining

This is where Ruby becomes beautiful. You can chain Enumerable methods to build complex transformations:


# Get top 5 most active users who joined this year
User.where('created_at > ?', 1.year.ago)
    .sort_by { |u| -u.posts.count }
    .take(5)
    .map { |u| { name: u.name, posts: u.posts.count } }

# Calculate average order value by customer tier
orders.group_by { |o| o.user.tier }
      .transform_values { |orders| orders.sum(&:total) / orders.size }
      .sort_by { |tier, avg| -avg }
      .to_h

Read this out loud:

"Group orders by user tier, transform the values to average order value, sort by average descending, convert to hash."

That's exactly what the code does. That's the power of declarative programming.

Performance Considerations

A word of warning: Not all Enumerable methods are created equal.


# BAD: Loads all users into memory, then filters in Ruby
User.all.select { |u| u.active? }

# GOOD: Filters in database
User.where(active: true)

# BAD: N+1 queries
users.map { |u| u.posts.count }

# GOOD: Eager loading
User.includes(:posts).map { |u| u.posts.size }

# BAD: Multiple iterations
users.select(&:active?).count
users.select(&:active?).map(&:email)

# GOOD: One iteration, store result
active_users = users.select(&:active?)
active_users.count
active_users.map(&:email)

Rule of thumb:

  • For ActiveRecord collections: Use database methods (.where, .pluck, .sum)
  • For in-memory arrays: Use Enumerable methods (.select, .map, .sum)

Refactoring: The Art of Making Code Better

Good Ruby code isn't written – it's refactored into existence.

Extract Methods Aggressively

Before:


class OrdersController < ApplicationController
  def create
    @order = Order.new(order_params)
    @order.user = current_user
    @order.total = @order.line_items.sum { |li| li.quantity * li.price }
    
    if @order.total > 100
      @order.discount = @order.total * 0.1
      @order.total = @order.total - @order.discount
    end
    
    if current_user.premium?
      @order.shipping_cost = 0
    else
      @order.shipping_cost = 10
    end
    
    @order.final_total = @order.total + @order.shipping_cost
    
    if @order.save
      OrderMailer.confirmation(@order).deliver_later
      redirect_to @order, notice: 'Order created!'
    else
      render :new
    end
  end
end

After:


class OrdersController < ApplicationController
  def create
    @order = build_order
    
    if @order.save
      send_confirmation_email
      redirect_to @order, notice: 'Order created!'
    else
      render :new
    end
  end
  
  private
  
  def build_order
    Order.new(order_params).tap do |order|
      order.user = current_user
      order.calculate_totals
      order.apply_discount_if_eligible
      order.calculate_shipping
    end
  end
  
  def send_confirmation_email
    OrderMailer.confirmation(@order).deliver_later
  end
end

# In Order model
class Order < ApplicationRecord
  def calculate_totals
    self.total = line_items.sum { |li| li.quantity * li.price }
  end
  
  def apply_discount_if_eligible
    return unless total > 100
    
    self.discount = total * 0.1
    self.total -= discount
  end
  
  def calculate_shipping
    self.shipping_cost = user.premium? ? 0 : 10
    self.final_total = total + shipping_cost
  end
end

Why this is better:

  • Controller is thin (12 lines → 8 lines)
  • Business logic is in the model (where it belongs)
  • Each method does one thing
  • Easy to test each piece independently
  • Easy to read and understand

Use Ruby's Safe Navigation Operator

Before:


if user && user.profile && user.profile.avatar && user.profile.avatar.url
  avatar_url = user.profile.avatar.url
else
  avatar_url = '/default-avatar.png'
end

After:


avatar_url = user&.profile&.avatar&.url || '/default-avatar.png'

One line. Clear. Elegant.

Replace Conditionals with Polymorphism

Before:


class PaymentProcessor
  def process(payment)
    if payment.type == 'credit_card'
      charge_credit_card(payment)
    elsif payment.type == 'paypal'
      charge_paypal(payment)
    elsif payment.type == 'bank_transfer'
      charge_bank_transfer(payment)
    elsif payment.type == 'crypto'
      charge_crypto(payment)
    end
  end
end

After:


class CreditCardPayment < Payment
  def process
    # Credit card specific logic
  end
end

class PaypalPayment < Payment
  def process
    # PayPal specific logic
  end
end

# Usage
payment.process # Calls the right method based on payment type

Benefits:

  • No conditionals
  • Each payment type is isolated
  • Easy to add new payment methods (Open/Closed Principle)
  • Easier to test

Use try or &. for Nil Safety

Before:


def user_email
  if @user && @user.respond_to?(:email)
    @user.email
  else
    'No email'
  end
end

After:


def user_email
  @user&.email || 'No email'
end

Extract Complex Conditions

Before:


if user.active? && user.verified? && user.orders.any? && !user.banned? && user.created_at < 1.year.ago
  # Do something
end

After:

if user.eligible_for_promotion?
  # Do something
end

# In User model
def eligible_for_promotion?
  active? && verified? && orders.any? && !banned? && veteran?
end

def veteran?
  created_at < 1.year.ago
end

Name your conditions. It's self-documenting.

Use tap for Side Effects

Before:


user = User.new(user_params)
user.generate_token
user.set_default_role
user.send_welcome_email
user.save
user

After:


User.new(user_params).tap do |user|
  user.generate_token
  user.set_default_role
  user.send_welcome_email
  user.save
end

Or even better, use callbacks:


class User < ApplicationRecord
  after_create :generate_token
  after_create :set_default_role
  after_create :send_welcome_email
end

User.create(user_params) # Everything happens automatically

Code Readability: Write Code for Humans

Code is read 10x more than it's written. Optimize for readability.

Use Descriptive Names

Before:


def p(u, o)
  t = o.sum(&:t)
  if u.p?
    t * 0.9
  else
    t
  end
end

What does this even do?

After:


def calculate_total_price(user, orders)
  subtotal = orders.sum(&:total)
  
  if user.premium?
    apply_discount(subtotal)
  else
    subtotal
  end
end

def apply_discount(amount)
  amount * 0.9
end

Now it's clear.

Avoid Magic Numbers

Before:


def shipping_cost(weight)
  if weight < 5
    10
  elsif weight < 20
    25
  else
    50
  end
end

After:



ruby

LIGHT_PACKAGE_THRESHOLD = 5
MEDIUM_PACKAGE_THRESHOLD = 20

LIGHT_SHIPPING_COST = 10
MEDIUM_SHIPPING_COST = 25
HEAVY_SHIPPING_COST = 50

def shipping_cost(weight)
  case weight
  when 0...LIGHT_PACKAGE_THRESHOLD
    LIGHT_SHIPPING_COST
  when LIGHT_PACKAGE_THRESHOLD...MEDIUM_PACKAGE_THRESHOLD
    MEDIUM_SHIPPING_COST
  else
    HEAVY_SHIPPING_COST
  end
end

Every number has a name. Every name has meaning.

Use Guard Clauses

Before:


def process_refund(order)
  if order.present?
    if order.paid?
      if order.refundable?
        # Actually process refund
        order.refund!
        send_refund_email(order)
        true
      else
        false
      end
    else
      false
    end
  else
    false
  end
end

Nesting hell.

After:


def process_refund(order)
  return false if order.blank?
  return false unless order.paid?
  return false unless order.refundable?
  
  order.refund!
  send_refund_email(order)
  true
end

Flat. Readable. Clear failure conditions up front.

Use Keyword Arguments for Clarity

Before:


create_user('John', 'john@example.com', 'password123', true, false, 'admin')

What do those booleans mean? What's 'admin'?

After:


create_user(
  name: 'John',
  email: 'john@example.com',
  password: 'password123',
  verified: true,
  banned: false,
  role: 'admin'
)

Crystal clear.

Prefer Positive Conditionals

Before:


unless !user.inactive?
  # Do something
end

Double negative. Brain hurts.

After:


if user.active?
  # Do something
end

Reads like English.

Use Query Methods for Boolean Returns

Before:


def check_if_user_can_edit_post(user, post)
  user.id == post.author_id || user.admin == true
end

if check_if_user_can_edit_post(current_user, @post)
  # Allow editing
end

After:


def can_edit?(user)
  authored_by?(user) || user.admin?
end

def authored_by?(user)
  author == user
end

# In controller
if @post.can_edit?(current_user)
  # Allow editing
end

Reads like a question. Returns a boolean. Perfect.

The Compound Effect

None of these things alone will revolutionize your code. But together, over thousands of lines, they compound.

Compare this codebase:


# Beginner code
def get_active_user_emails_who_spent_over_100
  result = []
  for user in User.all
    if user.active == true
      total = 0
      for order in user.orders
        total = total + order.total
      end
      if total > 100
        result.push(user.email)
      end
    end
  end
  result
end

To this:


# Experienced Ruby developer
User.active
    .with_orders_over(100)
    .pluck(:email)

# With scope in User model
scope :with_orders_over, ->(amount) {
  joins(:orders)
    .group('users.id')
    .having('SUM(orders.total) > ?', amount)
}

Same functionality. 10x less code. Infinitely more readable.

Final Thoughts

These "small things" are what separate okay Ruby code from great Ruby code:

  1. Master Enumerable – Stop writing loops, start composing transformations
  2. Refactor relentlessly – Extract methods, use polymorphism, eliminate duplication
  3. Write for humans – Clear names, guard clauses, positive conditionals

Ruby gives you the tools to write beautiful, expressive code. Use them.

Your future self (and your teammates) will thank you.

Our website uses cookies to enhance your experience. By continuing to browse, you agree to our use of cookies. Read more about it