Ruby Enumerable ๐Ÿ“š Module: Exciting Methods

Enumerable is a collection of iteration methods, a Ruby module, and a big part of what makes Ruby a great programming language.

# count elements that evaluate to true inside a block
[1,2,34].count
=> 3

# Group enumerable elements by the block return value. Returns a hash
[12,3,7,9].group_by {|x| x.even? ? 'even' : 'not_even'}
=> {"even" => [12], "not_even" => [3, 7, 9]}

# Partition into two groups. Returns a two-dimensional array
> [1,2,3,4,5].partition { |x| x.even? }
=> [[2, 4], [1, 3, 5]]

# Returns true if the block returns true for ANY elements yielded to it
> [1,2,5,8].any? 4
=> false

> [1,2,5,8].any? { |x| x.even?}
=> true

# Returns true if the block returns true for ALL elements yielded to it
> [2,5,6,8].all? {|x| x.even?}
=> false

# Opposite of all?
> [2,2,5,7].none? { |x| x.even?}
=> false

# Repeat ALL the elements n times
> [3,4,6].cycle(3).to_a
=> [3, 4, 6, 3, 4, 6, 3, 4, 6]

# select - SELECT all elements which pass the block
> [18,4,5,8,89].select {|x| x.even?}
=> [18, 4, 8]
> [18,4,5,8,89].select(&:even?)
=> [18, 4, 8]

# Like select, but it returns the first thing it finds
> [18,4,5,8,89].find {|x| x.even?}
=> 18

# Accumulates the result of the previous block value & passes it into the next one. Useful for adding up totals
> [4,5,8,90].inject(0) { |x, sum| x + sum }
=> 107
> [4,5,8,90].inject(:+)
=> 107
# Note that 'reduce' is an alias of 'inject'.

# Combines together two enumerable objects, so you can work with them in parallel. Useful for comparing elements & for generating hashes

> [2,4,56,8].zip [3,4]
=> [[2, 3], [4, 4], [56, nil], [8, nil]]

# Transforms every element of the enumerable object & returns the new version as an array
> [3,6,9].map { |x| x+89-27/2*23 }
=> [-207, -204, -201]

What is :+ in [4, 5, 8, 90].inject(:+) in Ruby?

๐Ÿ”ฃ :+ is a Symbol representing the + method.

In Ruby, every operator (like +, *, etc.) is actually a method under the hood.

  • inject takes a symbol (:+)
  • Ruby calls .send(:+) on each pair of elements
  • It’s equivalent to:
    (((4 + 5) + 8) + 90) => 107

๐Ÿ”ฃ &: Explanation:

  • :even? is a symbol representing the method even?
  • &: is Ruby’s “to_proc” shorthand, converting a symbol into a block
  • So &:even? becomes { |n| n.even? } under the hood

Enjoy Enumerable ๐Ÿš€

Ruby Concepts ๐Ÿ’ : Blocks, Constants, Meta-Programming, Enum

Here we will look into the detailed explanation of some Ruby concepts with practical examples, and real-world scenarios:

1. Handling Many Constants in a Ruby Class

Problem:
A class with numerous constants becomes cluttered and harder to maintain.

Solutions & Examples:

  1. Nested Module for Grouping:
   class HTTPClient
     module StatusCodes
       OK = 200
       NOT_FOUND = 404
       SERVER_ERROR = 500
     end

     def handle_response(code)
       case code
       when StatusCodes::OK then "Success"
       when StatusCodes::NOT_FOUND then "Page missing"
       end
     end
   end

Why: Encapsulating constants in a module improves readability and avoids namespace collisions.

  1. Dynamic Constants with const_set:
   class DaysOfWeek
     %w[MON TUE WED THU FRI SAT SUN].each_with_index do |day, index|
       const_set(day, index + 1)
     end
   end
   puts DaysOfWeek::MON # => 1

Use Case: Generate constants programmatically (e.g., days, months).

  1. External Configuration (YAML):
   # config/constants.yml
   error_codes:
     NOT_FOUND: 404
     SERVER_ERROR: 500
   class App
     CONSTANTS = YAML.load_file('config/constants.yml')
     def self.error_message(code)
       CONSTANTS['error_codes'].key(code)
     end
   end

Why: Centralize configuration for easy updates.


2. Meta-Programming: Dynamic Methods & Classes

Examples:

  1. define_method for Repetitive Methods:
   class User
     ATTRIBUTES = %w[name email age]

     ATTRIBUTES.each do |attr|
       define_method(attr) { instance_variable_get("@#{attr}") }
       define_method("#{attr}=") { |value| instance_variable_set("@#{attr}", value) }
     end
   end

   user = User.new
   user.name = "Alice"
   puts user.name # => "Alice"

Use Case: Auto-generate getters/setters for multiple attributes.

  1. Dynamic Classes with Class.new:
   Animal = Class.new do
     def speak
       puts "Animal noise!"
     end
   end

   dog = Animal.new
   dog.speak # => "Animal noise!"

Use Case: Generate classes at runtime (e.g., for plugins).

  1. class_eval for Modifying Existing Classes:
   String.class_eval do
     def shout
       upcase + "!"
     end
   end

   puts "hello".shout # => "HELLO!"

Why: Add/redefine methods in existing classes dynamically.


3. Why Classes Are Objects in Ruby

Explanation:

  • Every class is an instance of Class.
  String.class # => Class
  • Classes inherit from Module and ultimately Object, allowing them to have methods and variables:
  class Dog
    @count = 0 # Class instance variable
    def self.increment_count
      @count += 1
    end
  end
  • Real-World Impact: You can pass classes as arguments, modify them at runtime, and use them like any other object.

4. super Keyword: Detailed Usage

Examples:

  1. Implicit Argument Passing:
   class Vehicle
     def start_engine
       "Engine on"
     end
   end

   class Car < Vehicle
     def start_engine
       super + " (Vroom!)"
     end
   end

   puts Car.new.start_engine # => "Engine on (Vroom!)"
  1. Explicit super() for No Arguments:
   class Parent
     def greet
       "Hello"
     end
   end

   class Child < Parent
     def greet
       super() + " World!" # Explicitly call Parent#greet with no args
     end
   end

Pitfall: Forgetting () when overriding a method with parameters.


5. Blocks in Ruby Methods: Scenarios

A simple ruby method that accepts a block and executing via yield:

irb* def abhi_block
irb*   yield
irb*   yield
irb> end
=> :abhi_block
irb* abhi_block do.             # multi-line block
irb*   puts "*"*7
irb> end
*******
*******
irb> abhi_block { puts "*"*7 }. # single-line block
*******
*******
=> nil
irb* def abhi_block
irb*   yield 3
irb*   yield 7
irb*   yield 9
irb> end
=> :abhi_block
irb> abhi_block { |x| puts x }. # pass argument to block
3
7
9
=> nil

Note: We can call yield any number times that we want.

Proc

Procs are similar to blocks, however, they differ in that they may be saved to a variable to be used again and again. In Ruby, aย procย can be called directly using theย .callย method.

To create Proc, we callย newย on theย Procย class and follow it with the block of code

my_proc = Proc.new { |x| x*x*9 }
=> #<Proc:0x000000011f64ed38 (irb):34>

my_proc.call(6)
=> 324

> my_proc.call      # try to call without an argument
(irb):34:in 'block in <top (required)>': undefined method '*' for nil (NoMethodError)
lambda
> my_lambda = lambda { |x| x/3/5 }
=> #<Proc:0x000000011fe6fd28 (irb):44 (lambda)>

> my_lambda.call(233)
=> 15

> my_lambda = lambda.new { |x| x/3/5 } # wrong
in 'Kernel#lambda': tried to create Proc object without a block (ArgumentError)

> my_lambda = lambda                   # wrong
(irb):45:in 'Kernel#lambda': tried to create Proc object without a block (ArgumentError)

> my_lambda.call     # try to call without an argument
(irb):46:in 'block in <top (required)>': wrong number of arguments (given 0, expected 1) (ArgumentError)

Difference 1: lambda gets an ArgumentError if we call without an argument and Proc doesn’t.

Difference 2: lambda returns to its calling method rather than returning itself like Proc from its parent method.

irb* def proc_method
irb*   my_proc = Proc.new { return "Proc returns" }
irb*   my_proc.call
irb*   "Retun by proc_method"  # neaver reaches here
irb> end
=> :proc_method

irb> p proc_method
"Proc returns"
=> "Proc returns"
irb* def lambda_method
irb*   my_lambda = lambda { return 'Lambda returns' }
irb*   my_lambda.call
irb*   "Method returns"
irb> end
=> :lambda_method
irb(main):079> p lambda_method
"Method returns"
=> "Method returns"

Use Cases & Examples:

  1. Resource Management (File Handling):
   def open_file(path)
     file = File.open(path, 'w')
     yield(file) if block_given?
   ensure
     file.close
   end

   open_file('log.txt') { |f| f.write("Data") }

Why: Ensures the file is closed even if an error occurs.

  1. Custom Iterators:
   class MyArray
     def initialize(items)
       @items = items
     end

     def custom_each
       @items.each { |item| yield(item) }
     end
   end

   MyArray.new([1,2,3]).custom_each { |n| puts n * 2 }
  1. Timing Execution:
   def benchmark
     start = Time.now
     yield
     puts "Time taken: #{Time.now - start}s"
   end

   benchmark { sleep(2) } # => "Time taken: 2.0s"
Procs And Lambdas in Ruby

proc = Proc.new { puts "I am the proc block" }
lambda = lambda { puts "I am the lambda block"}

proc_test.call # => I am the proc block
lambda_test.call # => I am the lambda block

6. Enums in Ruby

Approaches:

  1. Symbols/Constants:
   class TrafficLight
     STATES = %i[red yellow green].freeze

     def initialize
       @state = STATES.first
     end

     def next_state
       @state = STATES[(STATES.index(@state) + 1) % STATES.size]
     end
   end
  1. Rails ActiveRecord Enum:
   class User < ActiveRecord::Base
     enum role: { admin: 0, user: 1, guest: 2 }
   end

   user = User.new(role: :admin)
   user.admin? # => true

Why: Generates helper methods like admin? and user.admin!.


7. Including Enumerable

Why Needed:

  • Enumerable methods (map, select, etc.) rely on each being defined.
  • Example Without Enumerable:
  class MyCollection
    def initialize(items)
      @items = items
    end

    def each(&block)
      @items.each(&block)
    end
  end

  # Without Enumerable:
  collection = MyCollection.new([1,2,3])
  collection.map { |n| n * 2 } # Error: Undefined method `map`
  • With Enumerable:
  class MyCollection
    include Enumerable
    # ... same as above
  end

  collection.map { |n| n * 2 } # => [2,4,6]

8. Class Variables (@@)

Example & Risks:

class Parent
  @@count = 0

  def self.count
    @@count
  end

  def increment
    @@count += 1
  end
end

class Child < Parent; end

Parent.new.increment
Child.new.increment
puts Parent.count # => 2 (Shared across Parent and Child)

Why Avoid: Subclasses unintentionally modify the same variable.
Alternative (Class Instance Variables):

class Parent
  @count = 0

  def self.count
    @count
  end

  def self.increment
    @count += 1
  end
end

9. Global Variables ($)

Example & Issues:

$logger = Logger.new($stdout)

def log_error(message)
  $logger.error(message) # Accessible everywhere
end

# Problem: Tight coupling; changing $logger affects all code.

When to Use: Rarely, for truly global resources like $stdout or $LOAD_PATH.
Alternative: Dependency injection or singleton classes.

class AppConfig
  attr_reader :logger

  def initialize(logger:)
    @logger = logger
  end

  def info(msg)
    @logger.info(msg)
  end
end

config = AppConfig.new(Logger.new($stdout))
info = config.info("Safe")


Summary:

  • Constants: Organize with modules or external files.
  • Meta-Programming: Use define_method/Class.new for dynamic code.
  • Classes as Objects: Enable OOP flexibility.
  • super: Call parent methods with/without arguments.
  • Blocks: Abstract setup/teardown or custom logic.
  • Enums: Simulate via symbols or Rails helpers.
  • Enumerable: Include it and define each.
  • Class/Global Variables: Rarely used due to side effects.

Enjoy Ruby! ๐Ÿš€