Author: Pooja Kotwani

  • Miscellaneous

    Ruby Types of Iterators

    Iterators in Ruby allow you to loop through collections like arrays, hashes, and ranges. Ruby provides several built-in methods to iterate over these collections.

    Common Iterators:

    1. each: Iterates over each element in a collection.
    2. map: Creates a new array by applying a block to each element.
    3. select: Returns an array of elements for which the block returns true.
    4. reject: Returns an array of elements for which the block returns false.
    5. reduce (also known as inject): Combines all elements of an enumerable by applying a binary operation.
    6. times: Iterates a given number of times.
    7. upto: Iterates from the current number up to the specified number.
    8. downto: Iterates from the current number down to the specified number.
    9. step: Iterates from the current number to the specified number, incrementing by the step value.

    Examples:

    1. each: The each iterator returns all elements of an array or hash, one by one.
    Syntax:

    collection.each do |variable_name|
      # code to iterate
    end

    Example:

    # Using each iterator with a range
    (0..5).each do |i|
      puts i
    end
    
    # Using each iterator with an array
    letters = ['A', 'B', 'C']
    letters.each do |letter|
      puts letter
    end

    2. Collect Iterator: The collect iterator returns all elements of a collection, either an array or hash, and can be used to transform elements.
    Syntax:

    result = collection.collect { |element| block }

    Example:

    # Using collect iterator to multiply each element
    numbers = [1, 2, 3, 4]
    result = numbers.collect { |x| x * 2 }
    puts result

    3. Times Iterator: The times iterator repeats a block of code a specified number of times, starting from 0 up to one less than the specified number.
    Syntax:

    t.times do |i|
      # code to execute
    end

    Example:

    # Using times iterator
    3.times do |i|
      puts i
    end

    4. Upto Iterator: The upto iterator starts from a number and continues up to the specified upper limit.
    Syntax:

    start.upto(limit) do |i|
      # code to execute
    end

    Example:

    # Using upto iterator
    1.upto(3) do |i|
      puts i
    end

    5. Downto Iterator: The downto iterator starts from a number and goes down to a specified lower limit.
    Syntax:

    start.downto(limit) do |i|
      # code to execute
    end

    Example:

    # Using downto iterator
    5.downto(2) do |i|
      puts i
    end

    6. Step Iterator: The step iterator is used when you want to skip a specified number of elements in a range during iteration.
    Syntax:

    range.step(step_value) do |i|
      # code to execute
    end

    Example:

    # Using step iterator to skip by 2
    (0..10).step(2) do |i|
      puts i
    end

    7. Each_Line Iterator: The each_line iterator iterates through each line in a string, often used when working with multi-line text.
    Syntax:

    string.each_line do |line|
      # code to execute
    end

    Example:

    # Using each_line iterator
    "Hello\nWorld\nRuby".each_line do |line|
      puts line
    end

    Ruby getters and setters Method

    In Ruby, instance variables (denoted by @) are private by default and are not accessible directly outside the class. To expose these variables for encapsulation purposes, Ruby provides getter and setter methods. A getter method retrieves the value of an instance variable, while a setter method assigns a value to it.

    Example 1: Simple Getter Method

    # Ruby program using getter method
    class Website
      # Constructor to initialize the class with a name instance variable
      def initialize(name)
        @name = name
      end
    
      # Classical getter method
      def name
        @name
      end
    end
    
    # Creating an object of the class
    site = Website.new("www.example.com")
    puts site.name

    Output:

    www.example.com

    In this example, the getter method allows access to the @name variable outside the class.

    Example 2: Simple Setter Method

    # Ruby program using setter method
    class Website
      # Constructor to initialize the class with a name instance variable
      def initialize(name)
        @name = name
      end
    
      # Classical getter method
      def name
        @name
      end
    
      # Classical setter method
      def name=(name)
        @name = name
      end
    end
    
    # Creating an object of the class
    site = Website.new("www.example.com")
    puts site.name
    
    # Changing the instance variable from outside the class
    site.name = "www.updated.com"
    puts site.name

    Output:

    www.example.com
    www.updated.com

    Here, the setter method allows modification of the @name variable from outside the class.

    Accessor Methods in Ruby

    Writing multiple getter and setter methods manually can make the code verbose, especially as the class grows. Ruby provides a convenient way to create these methods using accessor methods.

    • attr_reader: Automatically generates a getter method.
    • attr_writer: Automatically generates a setter method.
    • attr_accessor: Generates both getter and setter methods.

    Example : 

    # Ruby Program using accessor for getter and setter
    class Website
      # attr_accessor combines both getter and setter
      attr_accessor :url
    
      # Constructor to initialize the website URL
      def initialize(url)
        @url = url
      end
    end
    
    # Creating an object of the class
    my_site = Website.new("www.example.com")
    
    # Accessing the website using the getter
    puts my_site.url # Output: www.example.com
    
    # Changing the website using the setter
    my_site.url = "www.updatedsite.com"
    
    # Accessing the updated website
    puts my_site.url # Output: www.updatedsite.com

    Output:

    www.example.com
    www.updatedsite.com
  • Ruby Threading

    Ruby Introduction to Multi-threading

    Multi-threading in Ruby is a powerful feature that enables the concurrent execution of different parts of a program, optimizing CPU usage. Each part of the program is called a thread, and threads are essentially lightweight processes within a larger process. A single-threaded program executes instructions sequentially, while a multi-threaded program can run multiple threads concurrently, utilizing multiple cores of a processor. This leads to reduced memory usage and improved performance compared to single-threaded programs.

    Before Ruby version 1.9, Ruby used green threads, meaning thread scheduling was handled by Ruby’s interpreter. However, from Ruby 1.9 onwards, threading is handled by the operating system, making thread execution more efficient. Despite this improvement, two threads in the same Ruby application still cannot run truly concurrently.

    In Ruby, a multi-threaded program can be created using the Thread class. A new thread is usually created by calling a block using Thread.new.

    Creating Threads in Ruby

    To create a new thread in Ruby, you can use any of three blocks: Thread.newThread.start, or Thread.fork. The most commonly used is Thread.new. Once a thread is created, the main (original) thread resumes execution after the thread creation block, running in parallel with the new thread.

    Syntax:

    # Main thread runs here
    
    # Creating a new thread
    Thread.new {
        # Code to run inside the new thread
    }
    
    # Main thread continues executing

    Example:

    # Ruby program to demonstrate thread creation
    
    def task1
      count = 0
      while count <= 2
        puts "Task 1 - Count: #{count}"
        sleep(1) # Pauses execution for 1 second
        count += 1
      end
    end
    
    def task2
      count = 0
      while count <= 2
        puts "Task 2 - Count: #{count}"
        sleep(0.5) # Pauses execution for 0.5 seconds
        count += 1
      end
    end
    
    # Creating threads for each task
    thread1 = Thread.new { task1() }
    thread2 = Thread.new { task2() }
    
    # Ensuring the main program waits for threads to finish
    thread1.join
    thread2.join
    
    puts "All tasks completed"

    Output:

    Task 1 - Count: 0
    Task 2 - Count: 0
    Task 2 - Count: 1
    Task 1 - Count: 1
    Task 2 - Count: 2
    Task 1 - Count: 2
    All tasks completed

    Note: The exact output may vary depending on how the operating system allocates resources to the threads.

    Terminating Threads

    When the Ruby program finishes, all associated threads are also terminated. However, you can manually kill a thread using the Thread.kill method.

    Syntax:

    Thread.kill(thread)
    Thread Variables and Scope

    Each thread has access to local, global, and instance variables within the scope of the block where it is defined. However, variables defined within a thread block are local to that thread and cannot be accessed by other threads. If multiple threads need to access the same variable concurrently, proper synchronization is required.

    Example:

    # Ruby program to demonstrate thread variables
    
    # Global variable
    $message = "Hello from Ruby!"
    
    def task1
      counter = 0
      while counter <= 2
        puts "Task 1 - Counter: #{counter}"
        sleep(1)
        counter += 1
      end
      puts "Global message: #{$message}"
    end
    
    def task2
      counter = 0
      while counter <= 2
        puts "Task 2 - Counter: #{counter}"
        sleep(0.5)
        counter += 1
      end
      puts "Global message: #{$message}"
    end
    
    # Creating threads for each task
    thread1 = Thread.new { task1() }
    thread2 = Thread.new { task2() }
    
    # Waiting for both threads to finish
    thread1.join
    thread2.join
    
    puts "Program finished"

    Output:

    Task 1 - Counter: 0
    Task 2 - Counter: 0
    Task 2 - Counter: 1
    Task 1 - Counter: 1
    Task 2 - Counter: 2
    Task 1 - Counter: 2
    Global message: Hello from Ruby!
    Global message: Hello from Ruby!
    Program finished

    In Ruby, threads are utilized to implement concurrent programming. Programs that require multiple threads use the Thread class, which provides a variety of methods to handle thread-based operations.

    Public Class Methods

    1. abort_on_exception: This method returns the status of the global “abort on exception” setting. By default, its value is false. If set to true, all threads are aborted when an exception is raised in any of them.

    Thread.abort_on_exception -> true or false

    2. abort_on_exception=: This method sets the new state of the global “abort on exception” flag. When set to true, threads are aborted when an exception arises. The return value is a boolean.

    Thread.abort_on_exception= bool -> true or false

    Example:

    # Ruby program to demonstrate abort_on_exception
    
    Thread.abort_on_exception = true
    
    thread = Thread.new do
      puts "Starting new thread"
      raise "An error occurred in the thread"
    end
    
    sleep(0.5)
    puts "Execution complete"

    Output:

    Starting new thread
    RuntimeError: An error occurred in the thread

    3. critical: Returns the current “thread critical” status. This flag indicates whether Ruby’s thread scheduler is in a critical section.

    Thread.critical -> true or false

    4. critical=: This method sets the global “thread critical” condition. When set to true, it blocks scheduling of any thread but allows new threads to be created and run. Thread-critical sections are used mainly in threading libraries.

    Thread.critical= bool -> true or false

    5. current: This method returns the currently running thread.

    Thread.current -> thread

    6. exit: Terminates the current thread and schedules another thread to run. If the thread is marked to be killed, it will return the thread. If it’s the main thread or the last thread, the program will exit.

    Thread.exit

    7. fork: Similar to start, this method initiates a new thread.

    Thread.fork { block } -> thread

    8. kill: This method terminates a specified thread.

    Thread.kill(thread)

    Example:

    # Ruby program to demonstrate the kill method
    
    counter = 0
    
    # Create a new thread
    thread = Thread.new { loop { counter += 1 } }
    
    # Sleep for a short time
    sleep(0.4)
    
    # Kill the thread
    Thread.kill(thread)
    
    # Check if the thread is alive
    puts thread.alive?  # Output: false

    9. list: This method returns an array of all the thread objects, whether they are runnable or stopped.

    Thread.list -> array

    Example:

    # Ruby program to demonstrate list method
    
    # First thread
    Thread.new { sleep(100) }
    
    # Second thread
    Thread.new { 1000.times { |i| i*i } }
    
    # Third thread
    Thread.new { Thread.stop }
    
    # List all threads
    Thread.list.each { |thr| p thr }

    Output:

    #<Thread:0x00007fc6fa0a8b98 sleep>
    #<Thread:0x00007fc6fa0a8cf8 run>
    #<Thread:0x00007fc6fa0a8d88 sleep>

    10. main: This method returns the main thread of the process. Each run of the program will generate a unique thread ID.

    Thread.main -> thread

    Example:

    # Ruby program to print the main thread's ID
    
    puts Thread.main

    Output:

    #<Thread:0x00007fcd92834b50>

    11. new: Creates and runs a new thread. Any arguments passed are given to the block.

    Thread.new([arguments]*) { |arguments| block } -> thread

    12. pass: Attempts to pass execution to another thread. The actual switching depends on the operating system.

    Thread.pass

    13. start: Similar to new. If the Thread class is subclassed, calling start from the subclass will not invoke the subclass’s initialize method.

    Thread.start([arguments]*) { |arguments| block } -> thread

    14. stop: Stops the current thread, putting it to sleep and allowing another thread to be scheduled. It also resets the critical condition to false.

    Thread.stop

    Example:

    # Ruby program to demonstrate stop and pass methods
    
    thread = Thread.new { print "Start"; Thread.stop; print "End" }
    
    # Pass control to another thread
    Thread.pass
    
    print "Main"
    thread.run
    thread.join

    Output:

    StartMainEnd

    Ruby Thread Class-Public Class Methods

    In Ruby, threads are utilized to enable concurrent programming. Programs requiring multiple threads rely on the Thread class to create and manage threads. The Thread class offers a variety of methods to perform specific tasks.

    Public Class Methods

    1. abort_on_exception:
    This method checks the global “abort on exception” setting and returns its current status. By default, this setting is false. When set to true, it ensures that all threads are terminated if an exception occurs in any thread.

    Thread.abort_on_exception -> true or false

    2. Thread.abort_on_exception=: This method determines if threads will abort when an exception occurs. If set to true, any exception in a thread will terminate the program.

    Example:

    Thread.abort_on_exception = true
    
    x = Thread.new do
      puts "Hello from the thread!"
      raise "Error raised in the thread"
    end
    
    sleep(0.5)
    puts "This won't be printed"

    Output:

    Hello from the thread!
    test.rb:6: Error raised in the thread (RuntimeError)
            from test.rb:4:in `initialize'
            from test.rb:4:in `new'
            from test.rb:4

    3. Thread.critical: This method retrieves or sets the global “thread critical” condition. When set to true, thread scheduling is prohibited, but new threads can still be created and run.

    Example:

    Thread.critical = true
    puts Thread.critical # Output: true
    Thread.critical = false
    puts Thread.critical # Output: false

    Output:

    true
    false

    4. Thread.current: Returns the currently executing thread.

    Example:

    puts Thread.current

    Output:

    #<Thread:0x00005623 @status="run">

    5. Thread.exit

    Terminates the currently running thread and schedules another thread to run.

    Example:

    x = Thread.new do
      puts "Running thread"
      Thread.exit
      puts "This won't be executed"
    end
    
    x.join
    puts "Thread exited"

    Output:

    Running thread
    Thread exited

    6. Thread.kill: Terminates the specified thread.

    Example:

    counter = 0
    
    x = Thread.new { loop { counter += 1 } }
    
    sleep(0.4)
    Thread.kill(x)
    sleep(0.5)
    
    puts x.alive?

    Output:

    false

    7. Thread.list: Returns an array of all threads (either runnable or stopped).

    Example:

    Thread.new { sleep(100) }
    Thread.new { 10000.times {|i| i * i } }
    Thread.new { Thread.stop }
    
    Thread.list.each { |thr| p thr }

    Output:

    #<Thread:0x00005633 sleep>
    #<Thread:0x00005634 run>
    #<Thread:0x00005635 sleep>
    #<Thread:0x00005636 run>

    8. Thread.main: Returns the main thread of the process.

    Example:

    puts Thread.main

    Output:

    #<Thread:0x0000123 main>

    9. Thread.new: Creates and starts a new thread to execute a block of code.

    Example:

    t = Thread.new do
      puts "Executing in a new thread"
    end
    
    t.join

    Output:

    Executing in a new thread

    10. Thread.passAttempts to pass execution to another thread, depending on the operating system’s thread scheduler.

    Example:

    Thread.new { 3.times { puts "Running task"; Thread.pass } }
    
    puts "Main thread"

    Output:

    Main thread
    Running task
    Running task
    Running task

    11. Thread.stop: Stops the currently running thread and schedules another thread.

    Example:

    x = Thread.new { print "Start"; Thread.stop; print "End" }
    
    Thread.pass
    print "Running Main Thread"
    
    x.run
    x.join

    Output:

    StartRunning Main ThreadEnd

    Ruby Thread Life Cycle & Its States

    The thread life cycle explains the progression of a thread from its creation to termination. A new thread can be created using Thread.newThread.start, or Thread.fork. There’s no need to start a thread explicitly—it starts running automatically when CPU resources are available. The value returned by Thread.new is a Thread object. The Thread class provides several methods to query and control thread behavior.

    A thread executes the block of code passed to Thread.new and terminates once the block finishes execution. The result of the last expression in the block is the thread’s value, which can be accessed using the value method of the Thread object. The value method returns the result only if the thread has completed execution; otherwise, it does not return any value. If an exception is raised within a thread (other than the main thread), the thread terminates.

    Thread States

    Ruby provides five thread states, representing the thread’s current status. You can check a thread’s status using the alive? and status methods.

    StateReturn Value
    Runnable“run”
    Sleeping“sleep”
    Aborting“aborting”
    Terminated normallyfalse
    Terminated with exceptionnil

    Example: Checking Thread Status:

    counter = 0
    
    # Create a new thread
    x = Thread.new { loop { counter += 1 } }
    
    # Check if the thread is alive
    puts x.alive?

    Output:

    true
    Main Thread

    In Ruby, the main thread is the top-level thread under which all other threads are spawned. The Ruby interpreter runs until both the main thread and all child threads complete their execution. The Thread.main method returns the main thread object. If an exception occurs in the main thread and is not caught, the interpreter will print an error message and exit. If an exception occurs in a non-main thread, the interpreter will terminate that particular thread.

    Example:

    # Ruby program to demonstrate the main thread
    
    # Display the main thread
    puts Thread.main
    
    # Create a new thread
    thread1 = Thread.new { sleep 100 }
    
    # List all threads
    Thread.list.each { |t| p t }
    
    # Print the current thread
    puts "Current thread: " + Thread.current.to_s
    
    # Create another thread
    thread2 = Thread.new { sleep 100 }
    
    # List all threads again
    Thread.list.each { |t| p t }
    
    # Print the current thread
    puts "Current thread: " + Thread.current.to_s
    
    # Kill the first thread
    Thread.kill(thread1)
    
    # Pass execution to the next thread
    Thread.pass
    
    # Kill the second thread
    Thread.kill(thread2)
    
    # List all threads after killing
    Thread.list.each { |t| p t }
    
    # Exit the main thread
    Thread.exit

    Outputs:

    #<Thread:0x00007fcda2033e30 run>
    #<Thread:0x00007fcda2033e30 run>
    #<Thread:0x00007fcda2034d08 sleep>
    Current thread: #<Thread:0x00007fcda2033e30 run>
    #<Thread:0x00007fcda2033e30 run>
    #<Thread:0x00007fcda2034d08 sleep>
    #<Thread:0x00007fcda2035b48 sleep>
    Current thread: #<Thread:0x00007fcda2033e30 run>
    #<Thread:0x00007fcda2033e30 run>
    #<Thread:0x00007fcda2035b48 dead>
    Alternate Thread States: Pausing, Waking, and Killing

    Threads are created in the runnable state and are ready to execute. A thread can pause itself by entering the sleeping state using methods like Thread.stop or Kernel.sleep. If Kernel.sleep is called without an argument, the thread pauses indefinitely. When a time argument is provided, the thread resumes after the specified time expires and reenters the runnable state.

    Paused threads can be resumed using methods like wakeup and run. These methods change the thread’s state from sleeping to runnable. The run method also triggers the thread scheduler, potentially allocating CPU resources to the resumed thread. The wakeup method resumes a specific thread without invoking the thread scheduler.

    To terminate a thread, you can use the killterminate, or exit methods. These methods place the thread into the “terminated normally” state.

    Example: Pausing and Resuming Threads

    t = Thread.new do
      puts "Thread started"
      sleep(1)
      puts "Thread resumed"
    end
    
    puts "Pausing main thread"
    Kernel.sleep(2)
    
    t.run
    t.join

    Outputs:

    Thread started
    Pausing main thread
    Thread resumed
  • Collections

    Ruby Arrays

    An array is a collection of elements, which can be of the same or different types, stored in contiguous memory locations. The concept behind arrays is to group multiple elements together under a single variable name. In Ruby, arrays can store various types of objects like numbers, strings, hashes, symbols, or even other arrays. Arrays in Ruby are indexed, meaning each element is associated with an index number, starting from 0 for positive indices. Negative indices start from -1, representing elements from the end of the array.

    Example of an Array:

    ["Ruby", 42, 88.5, "Programming"]

    In this example, the array contains four elements: a string ("Ruby"), an integer (42), a float (88.5), and another string ("Programming").

    • Positive indices start from 0.
    • Negative indices start from -1 and represent elements from the end of the array.

    Ruby also supports multidimensional arrays (arrays within arrays), but here we will focus on one-dimensional arrays.

    Creating a 1-D Array in Ruby

    There are various ways to create an array in Ruby, but two of the most common methods are:

    1. Using the new method: The new method can be used to create arrays by specifying its size and optional default values. The Array.new method can take zero, one, or more arguments.

    Syntax:

    array_name = Array.new

    Example:

    arr = Array.new

    Here, arr is an empty array. To create an array with a specific size, pass a number to the new method.

    Example:

    arr = Array.new(10) # Creates an array of size 10
    puts arr.size       # Output: 10
    puts arr.length     # Output: 10

    You can also specify a default value for each element.

    Example:

    arr = Array.new(3, "Hello")
    puts "#{arr}"  # Output: ["Hello", "Hello", "Hello"]

    Program Example:

    # Ruby program demonstrating array creation using the new method
    arr1 = Array.new()         # Empty array
    arr2 = Array.new(5)        # Array with 5 nil elements
    arr3 = Array.new(4, "Hi")  # Array of size 4 with default element "Hi"
    
    puts arr1.size             # Output: 0
    puts arr2.length           # Output: 5
    puts arr3.size             # Output: 4
    puts "#{arr3}"             # Output: ["Hi", "Hi", "Hi", "Hi"]

    2. Using the literal constructor []The literal constructor [] can be used to quickly define an array with elements inside square brackets.

    Example:

    arr = ['x', 'y', 'z', 'a', 'b']
    puts "#{arr}"              # Output: ["x", "y", "z", "a", "b"]
    puts "Size of array: #{arr.size}"   # Output: 5

    3. Accessing Elements from an Array: In Ruby, you can access elements of an array using indices. The most common way is to use the element’s index number in square brackets []. If you try to access an element that doesn’t exist, Ruby will return nil.

    Example:

    # Ruby program to demonstrate element access
    arr = ["Hello", "World", "Ruby", "Array"]
    puts arr[1]     # Output: World
    puts arr[-1]    # Output: Array (last element)

    4. Accessing Multiple Elements: To retrieve multiple elements from an array, pass two arguments to the [] method, specifying the starting index and the number of elements to retrieve.

    Example:

    # Ruby program to demonstrate accessing multiple elements
    arr = ["Hello", "World", "Ruby", "Array"]
    puts arr[1, 2]  # Output: ["World", "Ruby"]

    Ruby String

    A string in Ruby is a sequence of characters that may include letters, numbers, or symbols. Ruby treats strings as objects, allowing them to be manipulated directly. Unlike some other languages, Ruby strings are mutable, meaning their content can be changed without creating new instances.

    Creating Strings:

    Strings in Ruby can be created by enclosing characters in either single or double quotes. You can also assign them to variables without specifying a type, as Ruby is dynamically typed.

    Example:

    # Creating strings using single and double quotes
    
    puts 'String created using single quotes'
    puts "String created using double quotes"
    
    # Storing strings in variables
    str1 = "Hello"
    str2 = 'World'
    
    # Displaying stored strings
    puts str1
    puts str2

    Output:

    String created using single quotes
    String created using double quotes
    Hello
    World

    Note: The main difference between using single and double quotes is that double quotes allow variable interpolation, while single quotes do not.

    Example:

    # Difference between single and double quotes
    name = "Ruby"
    
    puts 'This is #{name}'   # No interpolation
    puts "This is #{name}"   # Interpolation happens

    Output:

    This is #{name}
    This is Ruby
    Strings as Objects:

    Ruby is an object-oriented language, so strings are objects with associated data and methods.

    Example:

    # Ruby strings as objects
    
    str1 = "Programming"
    str2 = String.new("Language")
    
    puts str1  # Output: Programming
    puts str2  # Output: Language
    Accessing String Elements:

    You can access individual characters or substrings in a string using square brackets []. The index of the character or range can be specified.

    Example:

    # Accessing string elements
    
    str = "Learning Ruby"
    
    # Accessing substring using a string
    puts str["Learning"]   # Output: Learning
    
    # Accessing character using index
    puts str[1]            # Output: e
    
    # Accessing character using negative index
    puts str[-1]           # Output: y
    
    # Accessing substring using range of indices
    puts str[9, 4]         # Output: Ruby
    
    # Using range operators
    puts str[9..12]        # Output: Ruby
    Creating Multiline Strings:

    Ruby offers multiple ways to create multiline strings, which can be achieved using double quotes, %/ /, or heredoc syntax.

    Example:

    # Ruby program demonstrating multiline strings
    
    # Using double quotes with newline characters
    puts "This is a multiline string.\nIt spans multiple lines.\n"
    
    # Using %/ /
    puts %/This is another way to create a multiline string
    which spans multiple lines./
    
    # Using heredoc syntax
    puts <<TEXT
    This is created using heredoc syntax.
    It also spans multiple lines.
    TEXT
    String Replication:

    In Ruby, the * operator allows you to replicate strings multiple times.

    Example:

    # Replicating strings
    
    str = "Hello, Ruby!\n"
    
    # Replicating the string 5 times
    puts str * 5

    Output:

    Hello, Ruby!
    Hello, Ruby!
    Hello, Ruby!
    Hello, Ruby!
    Hello, Ruby!

    Ruby String Interpolation

    String interpolation in Ruby allows combining strings in a dynamic way without needing to concatenate them using the + operator. It works only with double quotes ("") and provides a straightforward way to include variables or expressions within strings. When using interpolation, Ruby evaluates the variables or expressions inside #{} and replaces them with their values in the string.

    Syntax:

    "#{variable}"

    In the syntax above, everything inside the curly braces {} is an executable expression or variable.

    Example 1:

    # Ruby program demonstrating string interpolation
    
    x = 10
    y = 25
    puts "The number #{x} is less than #{y}"

    Output:

    The number 10 is less than 25

    In this example, the values of x and y are substituted directly into the string at the locations where the interpolation occurs.

    Example 2:

    # Ruby program demonstrating string interpolation with variables
    
    name = 'Rocket'
    age = 5
    
    # Using interpolation
    puts "#{name} is #{age} years old"
    
    # Concatenation without interpolation
    puts name + " is " + age.to_s + " years old"

    Output:

    Rocket is 5 years old
    Rocket is 5 years old

    In this example:

    • The first puts statement uses string interpolation, automatically converting the variables into a string format.
    • The second puts statement uses the + operator, requiring age to be converted to a string using to_s to avoid a type error.

    Note: String interpolation is preferable because it’s cleaner, avoids explicit conversions, and can handle complex expressions inside the curly braces.

    How String Interpolation Works:

    When Ruby encounters #{} inside a string, it evaluates the expression or variable inside and inserts the resulting value into the string. This works for numbers, strings, or even more complex expressions.

    For example:

    # Ruby program demonstrating interpolation of an expression
    
    num = 20
    puts "In five years, the number will be #{num + 5}"

    Output:

    In five years, the number will be 25

    Ruby Hashes Basics

    hash in Ruby is a data structure that holds a collection of unique keys, each associated with a corresponding value. Hashes are also called associative arrays or maps because they map keys to values. Unlike arrays, which are indexed by integers, hashes can be indexed by objects (such as strings or symbols).

    Creating Hashes

    There are several ways to create a hash in Ruby:

    1. Using the new Method: The new method creates an empty hash with no default value unless specified.

    Syntax:

    hash_variable = Hash.new

    Example:

    my_hash = Hash.new

    This will create an empty hash called my_hash. You can also specify a default value for the hash, which will be returned when trying to access a key that does not exist.

    my_hash = Hash.new("default_value")

    Now, if a key is not found in my_hash, it will return "default_value".

    2. Using {} Braces: This is the most common way to create a hash, using curly braces {} to define key-value pairs.

    Syntax:

    hash_variable = { "key1" => value1, "key2" => value2 }

    Example:

    languages = { "Ruby" => 1, "Python" => 2 }

    This hash associates "Ruby" with 1 and "Python" with 2.

    Fetching Hash Values

    To retrieve a value from a hash, use square brackets [] and pass the key as an argument.

    Example:

    # Define a hash with some key-value pairs
    student_scores = {
      "Alice" => 85,
      "Bob" => 92,
      "Charlie" => 78
    }
    
    # Fetch values using their keys
    puts student_scores["Alice"]   # Output the score of Alice
    puts student_scores["Bob"]     # Output the score of Bob
    puts student_scores["Charlie"] # Output the score of Charlie

    Output:

    85
    92
    78

    2. Numbers

    Ruby supports different types of numbers, including integers and floating-point numbers. You can write numbers of any size, using underscores (_) for readability. Ruby allows various numerical formats, including decimal, hexadecimal, octal, and binary.

    Example:

    # Ruby program to demonstrate hash creation and fetching values
    
    my_hash = { "Apple" => 3, "Banana" => 5 }
    
    # Fetching values using keys
    puts my_hash["Apple"]    # Output: 3
    puts my_hash["Banana"]   # Output: 5
    Modifying Hashes in Ruby

    You can modify a hash by adding, removing, or changing key-value pairs. Modifying the value of an existing key is done by reassigning the value.

    Example:

    # Ruby program to demonstrate modifying a hash
    
    fruits = { "Apple" => 3, "Banana" => 5 }
    
    puts "Before Modification:"
    puts fruits["Apple"]    # Output: 3
    puts fruits["Banana"]   # Output: 5
    
    # Modifying the values
    fruits["Apple"] = 10
    fruits["Banana"] = 8
    
    puts "\nAfter Modification:"
    puts fruits["Apple"]    # Output: 10
    puts fruits["Banana"]   # Output: 8
    Overwriting Key Values

    If you assign multiple values to the same key in a hash, the last assignment overwrites the previous ones. This does not raise an error but may give a warning.

    Example:

    # Ruby program to demonstrate overwriting keys
    
    my_hash = { "Apple" => 3, "Apple" => 7, "Banana" => 5 }
    
    puts "Before Modifying:"
    puts my_hash["Apple"]    # Output: 7 (last assigned value)
    puts my_hash["Banana"]   # Output: 5
    
    # Modifying the hash
    my_hash["Apple"] = 12
    my_hash["Banana"] = 10
    
    puts "\nAfter Modifying:"
    puts my_hash["Apple"]    # Output: 12
    puts my_hash["Banana"]   # Output: 10

    Ruby Hash Class

    In Ruby, a Hash is a collection of unique keys and their associated values. Unlike arrays, hashes allow indexing with arbitrary object types as keys. Iterating through a hash may return key-value pairs in an arbitrary order, not necessarily in the insertion order. By default, hashes return nil when trying to access keys that do not exist.

    Class Methods

    1. []: Creates a new hash populated with the given key-value pairs.

    # Example:
    p Hash["a", 1, "b", 2]
    p Hash["a" => 1, "b" => 2]

    Output:

    {"a"=>1, "b"=>2}
    {"a"=>1, "b"=>2}

    2. new: Returns an empty hash. If a key that doesn’t exist is accessed, the return value depends on the form of new. By default, nil is returned, but a custom object or block can be specified for default values.

    # Example:
    h = Hash.new("default_value")
    p h["key1"] = 10
    p h["key2"] = 20
    p h["key3"]       # key3 doesn't exist, returns default

    Output:

    10
    20
    "default_value"

    3. try_convert: Converts an object into a hash, if possible, and returns the hash or nil if conversion fails.

    # Example:
    p Hash.try_convert({1 => 2})
    p Hash.try_convert("1 => 2")  # Not a hash

    Output:

    {1=>2}
    nil
    Instance Methods

    1. ==: Checks if two hashes are equal, meaning both contain the same keys and corresponding values.

    # Example:
    h1 = {"a" => 1, "b" => 2}
    h2 = {"b" => 2, "a" => 1}
    h3 = {"a" => 3, "b" => 2}
    p h1 == h2  # true
    p h1 == h3  # false

    2. [] (Element Reference): Retrieves the value associated with the specified key, or returns nil if the key is not found.

    # Example:
    h = {"a" => 10, "b" => 20}
    p h["a"]  # 10
    p h["c"]  # nil

    3. []= (Element Assignment): Assigns a value to a key in the hash.

    # Example:
    h = {"a" => 10, "b" => 20}
    h["a"] = 50
    h["c"] = 30
    p h  # {"a"=>50, "b"=>20, "c"=>30}

    4. clear: Removes all key-value pairs from the hash.

    # Example:
    h = {"a" => 10, "b" => 20}
    h.clear
    p h  # {}

    5. default: Returns the default value for missing keys.

    # Example:
    h = Hash.new("default_value")
    p h.default  # "default_value"

    6. delete: Deletes a key-value pair from the hash by key, returning the value or nil if the key is not found.

    # Example:
    h = {"a" => 10, "b" => 20}
    p h.delete("a")  # 10
    p h.delete("z")  # nil

    7. each: Iterates over each key-value pair in the hash.

    # Example:
    h = {"a" => 10, "b" => 20}
    h.each { |key, value| puts "#{key}: #{value}" }

    Output:

    a: 10
    b: 20

    8. has_key?: Returns true if the specified key is present in the hash, otherwise false.

    # Example:
    h = {"a" => 10, "b" => 20}
    p h.has_key?("a")  # true
    p h.has_key?("z")  # false

    9. invert: Swaps keys and values, returning a new hash.

    # Example:
    h = {"a" => 1, "b" => 2}
    p h.invert  # {1 => "a", 2 => "b"}

    10. merge: Combines two hashes, giving precedence to the second hash’s values for duplicate keys.

    # Example:
    h1 = {"a" => 1, "b" => 2}
    h2 = {"b" => 3, "c" => 4}
    p h1.merge(h2)  # {"a" => 1, "b" => 3, "c" => 4}

    11. reject: Returns a new hash with key-value pairs removed where the block evaluates to true.

    # Example:
    h = {"a" => 1, "b" => 2, "c" => 3}
    p h.reject { |k, v| v > 1 }  # {"a" => 1}
  • Ruby Classes

    Ruby Float Class

    In Ruby, the Float class is a subclass of the Numeric class. Objects of the Float class represent real numbers using the system’s native double-precision floating-point representation.

    Public Instance Methods

    Arithmetic Operations

    The Float class supports various arithmetic operations, including addition, subtraction, multiplication, division, modulo, and exponentiation.

    • Addition: Returns the sum of a float and a numeric value as a floating-point number.
    float + numeric
    • Subtraction: Returns the difference between a float and a numeric value as a floating-point number.
    float - numeric
    • Multiplication: Returns the product of a float and a numeric value as a floating-point number.
    float * numeric
    • Division: Returns the quotient of a float and a numeric value as a floating-point number.
    float / numeric
    • Modulo: Returns the remainder when a float is divided by a numeric value.
    float % numeric
    • Exponentiation: Raises the float to the power of a numeric value.
    float ** numeric
    • Unary Minus: Returns the negative of the float.
    -float

    Example:

    a = 5.5
    b = 2
    
    # Addition
    puts a + b  # Output: 7.5
    
    # Subtraction
    puts a - b  # Output: 3.5
    
    # Multiplication
    puts a * b  # Output: 11.0
    
    # Division
    puts a / b  # Output: 2.75
    
    # Modulo
    puts a % b  # Output: 1.5
    
    # Exponentiation
    puts a ** b  # Output: 30.25
    
    # Unary Minus
    puts -a  # Output: -5.5
    Comparison Operators
    • Spaceship operator (<=>): Returns -1 if the float is less than the numeric value, 0 if they are equal, and 1 if the float is greater.
    float <=> numeric

    Example:

    puts 3.2 <=> 5   # Output: -1
    puts 5.0 <=> 5   # Output: 0
    puts 7.4 <=> 5   # Output: 1
    • Equality (==): Returns true if the float is equal to another object.
    float == obj

    Example:

    p 45.5.divmod(6)  # Output: [7, 3.5]
    Other Useful Methods
    • abs: Returns the absolute value of the float.
    float.abs
    • eql?: Returns true if the float is equal to another object, with the same value and type.
    float.eql?(obj)
    • ceil: Returns the smallest integer greater than or equal to the float.
    float.ceil
    • divmod: Returns an array containing the quotient and remainder when divided by a numeric value.
    float.divmod(numeric)

    Example:

    p 45.5.divmod(6)  # Output: [7, 3.5]
    • eql?: Returns true if the float is equal to another object, with the same value and type.
    float.eql?(obj)

    Example:

    puts 5.5.eql?(5.5)  # Output: true

    Output:

    Title: Ruby Programming
    Author: John Doe
    Title: Learning Ruby
    Author: Jane Doe
    • finite?: Returns true if the float is a valid IEEE floating-point number.
    float.finite?

    Example:

    puts 10.0.finite?  # Output: true
    • floor: Returns the largest integer less than or equal to the float.
    float.floor

    Output:

    Global count in FirstClass: 5
    Global count in SecondClass: 5
    • floor: Returns the largest integer less than or equal to the float.
    float.floor
    • infinite?: Returns nil-1, or 1 depending on whether the float is finite, negative infinity, or positive infinity.
    float.infinite?

    Example:

    puts (1.0/0.0).infinite?  # Output: 1
    • round: Rounds the float to the nearest integer or to the specified number of decimal places.
    float.round(digits=0)

    Example:

    puts 5.67.round   # Output: 6
    puts 5.67.round(1) # Output: 5.7
    • to_f: Returns the float itself.
    float.to_f
    • to_i: Truncates the float to return its integer value.
    float.to_i
    • zero?: Returns true if the float is 0.0.
    float.zero?

    Example:

    puts 0.0.zero?  # Output: true
    Constants in the Float Class
    • EPSILON: Smallest floating-point number greater than 1 (2.2204460492503131e-16).
    • MANT_DIG: Number of mantissa digits (53 by default).
    • MAX: Largest double-precision floating-point number (1.7976931348623157e+308).
    • MIN: Smallest positive normalized number (2.2250738585072014e-308).
    • \INFINITY: Represents positive infinity.
    • NAN: Represents “Not a Number.”

    Ruby Integer Class

    The Integer class in Ruby is the foundation for two subclasses, Bignum and Fixnum, which store whole numbers. Fixnum holds integer values within the machine’s native word size, while Bignum handles values outside the range of Fixnum. The Integer class itself inherits from the Numeric class and offers a variety of methods for performing operations on integers.

    Methods in the Integer Class

    1. to_i: This method returns the integer value. Its synonym is to_int.

    int.to_i

    2. chr: This method returns the ASCII character that corresponds to the integer’s value as a string.

    int.chr

    Example:

    puts 97.chr  # Output: "a"
    puts 66.chr  # Output: "B"

    3. downto: This method passes decreasing integer values from the receiver down to (and including) the argument, yielding each value to the block.

    int.downto(integer) {|i| block}

    Example:

    5.downto(1) { |i| print "#{i} " }
    # Output: 5 4 3 2 1

    4. floor: This method returns the largest integer less than or equal to the receiver. It behaves similarly to to_i.

    int.floor

    Example:

    puts 3.floor  # Output: 3
    puts (-3.4).floor  # Output: -4

    5. integer?: This method checks if the object is an integer, returning true if it is and false otherwise.

    int.integer?

    Example:

    puts 5.integer?  # Output: true
    puts 3.7.integer?  # Output: false

    6. next and succ: These methods return the next integer, which is the current integer plus one. Both methods are synonymous.

    int.next
    int.succ

    Example:

    puts 7.next  # Output: 8
    puts (-3).succ  # Output: -2

    7. times: This method executes the block a specified number of times, passing values from 0 to int - 1.

    int.times { |i| block }

    8. upto: This method iterates over integers from the receiver up to (and including) the specified value, yielding each number to the block.

    4.times { |i| print "#{i} " }
    # Output: 0 1 2 3

    Examples:

    2.upto(5) { |i| print "#{i} " }
    # Output: 2 3 4 5

    9. round: This method rounds the integer or float to the nearest integer. If no argument is provided, it rounds to zero decimal places.

    puts 10.round  # Output: 10
    puts (15.67).round  # Output: 16
    Additional Methods
    • to_int: Same as to_i, returns the integer value.
    • truncate: Similar to floor, it truncates any decimal part and returns an integer.
    • zero?: Returns true if the integer is zero, otherwise false.
    • odd?: Returns true if the integer is odd, otherwise false.
    • even?: Returns true if the integer is even, otherwise false.

    Ruby Symbol Class

    The Struct class in Ruby provides a concise way to bundle multiple attributes together, using accessor methods, without the need to define an explicit class. Each structure creates a new class with accessor methods for a predefined set of variables. A subclass of Struct is Struct::Tms.

    Example:

    # Ruby program demonstrating Symbol objects
    
    # A symbol representing a class
    module ModuleA
      class MyClass
      end
      $sym1 = :MyClass
    end
    
    # A symbol representing a constant
    module ModuleB
      MyConstant = 1
      $sym2 = :MyClass
    end
    
    # A symbol representing a method
    def MyClassMethod
    end
    $sym3 = :MyClass
    
    puts $sym1.object_id
    puts $sym2.object_id
    puts $sym3.object_id

    Output:

    1675428
    1675428
    1675428

    In this example, the symbol :MyClass refers to the same object, regardless of whether it’s used as a class name, constant, or method.

    Class Method

    • all_symbols: Returns an array of all symbols currently available in Ruby’s symbol table.
    Symbol.all_symbols

    Example:

    # Ruby program demonstrating the all_symbols method
    puts Symbol.all_symbols.size
    puts Symbol.all_symbols[1, 10]

    Output:

    3250
    [:a_symbol, :another_symbol, ...]  # An example of some symbols
    Instance Methods
    • id2name: Returns the string representation of a symbol.
    sym.id2name

    Example:

    # Ruby program demonstrating the id2name method
    p :Ruby.id2name
    p :"Hello World".id2name

    Output:

    "Ruby"
    "Hello World"
    • inspect: Returns a string representation of the symbol, prefixed with a colon.
    sym.inspect

    Example:

    # Ruby program demonstrating the inspect method
    p :ruby.inspect
    p :"sample text".inspect

    Output:

    ":ruby"
    ":\"sample text\""
    • to_s: Converts the symbol to its string equivalent.
    sym.to_s

    Example:

    # Ruby program demonstrating the to_s method
    p :language.to_s
    p :"hello world".to_s

    Output:

    "language"
    "hello world"
    • <=>: Compares two symbols after converting them to strings. Returns -1 if the first symbol is less, 0 if they are equal, and 1 if it’s greater.
    sym <=> other_sym
    • ==: Returns true if two symbols are the same object.
    # Ruby program demonstrating the <=> method
    a = :ruby
    b = :"programming language"
    puts a <=> b  # Output: -1
    puts a <=> :ruby  # Output: 0
    puts b <=> a  # Output: 1
    • ==: Returns true if two symbols are the same object.
    # Ruby program demonstrating the == method
    a = :ruby
    b = :"programming language"
    puts a == b  # Output: false
    puts a == :ruby  # Output: true

    Example:

    # Ruby program demonstrating the == method
    a = :ruby
    b = :"programming language"
    puts a == b  # Output: false
    puts a == :ruby  # Output: true
    • downcase: Converts all uppercase letters in the symbol to lowercase.
    sym.downcase

    Example:

    # Ruby program demonstrating the downcase method
    puts :"RUBY LANGUAGE".downcase
    • The entries method lists all files and folders in a directory.

    Syntax:

    Dir.entries("directory")

    Output:

    :"ruby language"
    • length: Returns the number of characters in the symbol.
    sym.length

    Example:

    # Ruby program demonstrating the length method
    puts :RubySymbol.length

    Output:

    10
    • slice: Returns a substring or character at a given index from the symbol.
    sym.slice(index)
    sym.slice(start, length)

    Example:

    # Ruby program demonstrating the slice method
    p :Programming.slice(2)  # Output: "o"
    p :Programming.slice(0, 6)  # Output: "Progra"
    • swapcase: Swaps the case of the symbol’s characters, converting uppercase to lowercase and vice versa.
    swapcase: Swaps the case of the symbol’s characters, converting uppercase to lowercase and vice versa.

    Example:

    # Ruby program demonstrating the swapcase method
    p :"RubyLanguage".swapcase

    Output:

    :"rUBYlANGUAGE"
    • upcase: Converts all lowercase letters in the symbol to uppercase.
    sym.upcase

    Example:

    # Ruby program demonstrating the upcase method
    p :"ruby language".upcase

    Output:

    :"RUBY LANGUAGE"
    • to_proc: Converts the symbol into a Proc object that invokes the method represented by the symbol.
    sym.to_proc

    Example:

    # Example using an array of strings
    words = ["apple", "banana", "cherry"]
    
    # Using &:symbol to convert symbol to Proc
    capitalized_words = words.map(&:capitalize)
    
    puts capitalized_words

    Output:

    Apple
    Banana
    Cherry
    • to_sym: Returns the symbol itself (as it is already a symbol).
    sym.to_sym

    Example:

    # Example: String to Symbol
    str = "hello"
    symbol = str.to_sym
    puts symbol  # Output: :hello
    puts symbol.class  # Output: Symbol

    Output:

    :world
    Symbol

    Ruby Struct Class

    The Struct class in Ruby provides a concise way to bundle multiple attributes together, using accessor methods, without the need to define an explicit class. Each structure creates a new class with accessor methods for a predefined set of variables. A subclass of Struct is Struct::Tms.

    Example:

    # Ruby program demonstrating the use of Struct
    
    # Creating a Struct with custom behavior
    Course = Struct.new(:name, :category) do
      def details
        "This is a #{category} course on #{name}."
      end
    end
    
    # Creating an instance of the struct
    course = Course.new("Ruby", "Programming")
    puts course.details

    Output:

    This is a Programming course on Ruby.

    Class Method

    • new: This method creates a new class with accessor methods for the provided symbols. If the name string is omitted, an anonymous structure class is created. If a name is provided, it appears as a constant in the Struct class and must be unique, starting with a capital letter.
    Struct.new([name], symbol1, symbol2)
    Struct.new([name], symbol1, symbol2){block}

    Example:

    # Ruby program demonstrating Struct creation
    
    # Creating a structure with a name in Struct
    Struct.new("Course", :subject, :type)
    Struct::Course.new("Ruby", "Programming")
    
    # Creating a structure using a constant name
    Course = Struct.new(:subject, :type)
    p Course.new("Ruby", "Programming")

    Output:

    #<struct Course subject="Ruby", type="Programming">
    Instance Methods
    • ==: Checks for equality between two instances of the same Struct class, comparing the values of their instance variables.
    instance1 == instance2

    Example:

    • []=: Assigns a new value to an instance variable using a symbol or index.
    # Ruby program demonstrating equality in Structs
    
    Course = Struct.new(:subject, :type)
    course1 = Course.new("Ruby", "Programming")
    course2 = Course.new("Python", "Data Science")
    course3 = Course.new("Ruby", "Programming")
    
    puts course1 == course2  # Output: false
    puts course1 == course3  # Output: true
    • each: Iterates over each instance variable’s value, passing it to the given block.
    instance.each { |value| block }

    Example:

    # Ruby program demonstrating each method
    
    Course = Struct.new(:subject, :type)
    course = Course.new("Ruby", "Programming")
    
    course.each { |value| puts value }
    • each: Iterates over each instance variable’s value, passing it to the given block.
    # Ruby program demonstrating each method
    
    Course = Struct.new(:subject, :type)
    course = Course.new("Ruby", "Programming")
    
    course.each { |value| puts value }

    Output:

    Ruby
    Programming
    • each_pair: Iterates over each instance variable, passing both the name and the value to the given block.
    • each_pair: Iterates over each instance variable, passing both the name and the value to the given block.
    instance.each_pair { |name, value| block }

    Example:

    # Ruby program demonstrating each_pair method
    
    Course = Struct.new(:subject, :type)
    course = Course.new("Ruby", "Programming")
    
    course.each_pair { |name, value| puts "#{name} => #{value}" }

    Output:

    # Ruby program demonstrating attribute assignment
    
    Course = Struct.new(:subject, :type)
    course = Course.new("Ruby", "Programming")
    
    course[:subject] = "JavaScript"
    course[1] = "Web Development"
    
    puts course.subject  # Output: JavaScript
    puts course.type     # Output: Web Development
  • Ruby Regex

    Ruby Regular Expressions

    A regular expression, or regex, is a sequence of characters that forms a search pattern. It’s mainly used for pattern matching within strings. In Ruby, regular expressions (regex) allow us to find specific patterns within a string. Two common uses of Ruby regex are validation (such as checking email addresses) and parsing text. Regular expressions in Ruby are enclosed between two forward slashes (/).

    Syntax:

    # Finding the word 'hi'
    "Hi there, I am using Ruby" =~ /hi/

    This will return the index of the first occurrence of ‘hi’ in the string if found, otherwise, it will return nil.

    Checking if a String Contains a Pattern

    You can check if a string contains a regex pattern using the match method.

    Example:

    Match found

    Checking for Specific Characters in a String

    A character class lets you define a range of characters for matching. For instance, you can use [aeiou] to search for any vowel.

    Example:

    # Ruby program using regular expressions
    
    # Function to check if the string contains a vowel
    def contains_vowel(str)
      str =~ /[aeiou]/
    end
    
    # Driver code
    puts contains_vowel("Hello")  # 'Hello' has a vowel, so it returns index
    puts contains_vowel("bcd")    # 'bcd' has no vowels, returns nil, so nothing is printed

    Output:

    1
    Common Regular Expressions

    Here are some shorthand character classes for specifying ranges:

    • \w is equivalent to [0-9a-zA-Z_]
    • \d is the same as [0-9]
    • \s matches any whitespace
    • \W matches anything not in [0-9a-zA-Z_]
    • \D matches anything that’s not a number
    • \S matches anything that’s not a whitespace

    The dot character . matches any character except a newline. If you want to search for the literal . character, you need to escape it with a backslash (\.).

    Example:

    # Ruby program using regular expressions
    
    str1 = "2m3"
    str2 = "2.5"
    
    # . matches any character
    if str1.match(/\d.\d/)
      puts "Match found"
    else
      puts "Not found"
    end
    
    # Escaping the dot to match only a literal '.'
    if str1.match(/\d\.\d/)
      puts "Match found"
    else
      puts "Not found"
    end
    
    # This will match because str2 contains a dot between digits
    if str2.match(/\d\.\d/)
      puts "Match found"
    else
      puts "Not found"
    end

    Output:

    Match found
    Not found
    Match found

    Explanation:

    1. First condition: Checks if str1 contains a digit followed by any character and then another digit (\d.\d). Since str1 contains “2m3,” it matches.
    2. Second condition: Escapes the dot (\.) to match a literal .. Since str1 doesn’t contain a literal dot, it does not match.
    3. Third condition: Matches because str2 (“2.5”) contains a digit, followed by a literal dot, and then another digit.

    In summary, Ruby regular expressions provide a powerful way to search and match patterns within strings, allowing for complex validations and parsing tasks.

    Ruby Search and Replace

    sub and gsub are Ruby string methods that utilize regular expressions for searching and replacing content in a string. Their in-place variants, sub! and gsub!, modify the original string directly.

    • sub and sub!: Replace only the first occurrence of the pattern.
    • gsub and gsub!: Replace all occurrences of the pattern.

    The difference between sub/gsub and their in-place variants (sub!/gsub!) is that the in-place methods (sub! and gsub!) modify the string on which they are called, while sub and gsub return a new string, leaving the original one unchanged.

    Example:

    # Ruby program demonstrating sub and gsub methods in a string
    
    number = "1234-567-890 # This is a number"
    
    # Remove the comment section using sub (replaces the first occurrence)
    number = number.sub!(/#.*$/, "")
    puts "Number: #{number}"
    
    # Remove all non-digit characters using gsub (replaces all occurrences)
    number = number.gsub!(/\D/, "")
    puts "Number: #{number}"

    Output:

    Number: 1234-567-890
    Number: 1234567890
  • Exceptions

    Ruby Exceptions

    In programming, predicting errors and handling them effectively is crucial. Exceptions are errors that occur during runtime and disrupt the normal flow of a program. Handling exceptions properly ensures a program continues to operate even in unexpected situations.

    What is an Exception?

    An exception is an unexpected event that occurs during the execution of a program, disrupting its normal flow. It provides a way to handle error scenarios without stopping the entire program.

    Errors vs. Exceptions

    Errors:

    • Unexpected issues that arise during program execution.
    • Cannot be handled directly.
    • All errors are considered exceptions.

    Exceptions:

    • Unexpected events during runtime.
    • Can be managed using begin-rescue blocks.
    • Not all exceptions are errors.
    Traditional Exception Handling Approach

    In the traditional approach, errors were managed using return codes. Methods would return specific values indicating failure, which would be propagated through calling routines until a function took responsibility. This made error management complex and hard to maintain.

    Ruby addresses this issue by using an exception class that packages information about errors into an object. This object is passed through the calling stack until appropriate code is found to handle the error.

    Exception Class & Its Hierarchy

    Ruby has a built-in hierarchy of exception classes. Most exceptions are subclasses of StandardError, which represents general errors in Ruby programs. More serious exceptions fall under other classes. Users can also create their own exceptions by subclassing StandardError or its descendants. Every exception object contains a message and a stack trace.

    Example of an Exception

    # Ruby program to illustrate an exception
    
    # defining two integer values
    num1 = 14
    num2 = 0
    
    # attempting to divide by zero
    result = num1 / num2
    
    puts "The result is: #{result}"

    Runtime Error:

    source_file.rb:6:in `/': divided by 0 (ZeroDivisionError)
    from source_file.rb:6:in `<main>'

    Explanation

    In the example above, dividing 14 by 0 triggers a ZeroDivisionError.

    Creating User-Defined Exceptions

    Ruby uses the raise method to create exceptions, which become instances of the Exception class or its subclasses. The rescue clause is used to handle these exceptions.

    Example of User-Defined Exception

    # Ruby program to create a user-defined exception
    
    # defining a method
    def raise_exception
      puts 'This is before the exception is raised!'
    
      # using raise to create an exception
      raise 'Custom Exception Raised'
    
      puts 'This line will not be executed.'
    end
    
    # calling the method
    raise_exception

    Output:

    This is before the exception is raised!
    source_file.rb:6:in `raise_exception': Custom Exception Raised (RuntimeError)
        from source_file.rb:10:in `<main>'

    Handling Exceptions with rescue

    You can handle exceptions in Ruby using begin-rescue blocks. This structure allows the program to continue running after handling an exception.

    Example of Handling Exception

    # Ruby program to create and handle a user-defined exception
    
    # defining a method
    def raise_and_rescue
      begin
        puts 'This is before the exception is raised!'
    
        # using raise to create an exception
        raise 'Custom Exception Raised!'
    
        puts 'This line will not be executed.'
    
      # using rescue to handle the exception
      rescue
        puts 'Exception handled!'
      end
    
      puts 'Outside of the begin block!'
    end
    
    # calling the method
    raise_and_rescue

    Output:

    This is before the exception is raised!
    Exception handled!
    Outside of the begin block!

    Ruby Exception Handling

    Exception handling in Ruby provides a way to manage unexpected events or errors that occur during the execution of a program. These errors can disrupt the normal flow of a program and are managed using various statements like beginrescueraiseensure, and more. Ruby also provides an Exception class, which has different methods to handle these errors.

    Syntax of Exception Handling in Ruby

    The code block where an exception might be raised is enclosed in a begin and end block. The rescue clause is used to manage the exception.

    Basic Syntax:

    begin
      # Code where an exception might be raised
    rescue
      # Code to handle the exception
    end

    Example of Exception Handling

    # Ruby program to handle an exception
    
    # defining a method
    def raise_and_rescue
      begin
        puts 'This is before an exception occurs!'
    
        # raising an exception
        raise 'An Exception Occurred!'
    
        puts 'This will not be printed'
      rescue
        puts 'Exception handled successfully!'
      end
    
      puts 'Outside the begin block!'
    end
    
    # calling the method
    raise_and_rescue

    Output:

    This is before an exception occurs!
    Exception handled successfully!
    Outside the begin block!

    Explanation: In this example, an exception is raised in the begin block using raise, which interrupts the program’s flow. The rescue block then catches and handles the exception, allowing the program to continue executing.

    Note

    • You can use multiple rescue clauses to handle different exceptions. If an exception is not handled by the first rescue, the next one is tried. If no rescue matches, or if an exception occurs outside the begin block, Ruby searches up the call stack for an exception handler.
    Statements Used in Exception Handling

    1. retry Statement: The retry statement re-executes the code from the beginning of the begin block after catching an exception.

    Syntax:

    begin
      # Code where an exception might be raised
    rescue
      # Code to handle the exception
      retry
    end

    Note: Be cautious while using retry as it can lead to an infinite loop if not properly managed.

    2. raise Statement: The raise statement is used to raise an exception in Ruby.

    Syntax:

    • raise: Re-raises the current exception.
    • raise "Error Message": Creates a RuntimeError with the given message.
    • raise ExceptionType, "Error Message": Creates an exception of the specified type with the given message.

    Example:

    # Ruby program demonstrating the use of raise statement
    
    begin
      puts 'This is before an exception occurs!'
    
      # Raising an exception
      raise 'An Exception Occurred!'
    
      puts 'This will not be printed'
    end

    Output:

    This is before an exception occurs!
    An Exception Occurred!

    3. ensure Statement: The ensure block always executes regardless of whether an exception is raised or rescued. It is placed after the rescue block.

    Syntax:

    begin
      # Code where an exception might be raised
    rescue
      # Code to handle the exception
    ensure
      # Code that always executes
    end

    Output:

    # Ruby program demonstrating the use of the ensure statement
    
    begin
      # Raising an exception
      raise 'An Exception Occurred!'
      puts 'This will not be printed'
    rescue
      puts 'Exception handled!'
    ensure
      puts 'Ensure block executed'
    end

    Output:

    Exception handled!
    Ensure block executed

    4. else Statement: The else block executes only if no exception is raised in the begin block. It is placed between the rescue and ensure blocks.

    Syntax:

    begin
      # Code where an exception might be raised
    rescue
      # Code to handle the exception
    else
      # Code that executes if no exception is raised
    ensure
      # Code that always executes
    end

    Example:

    # Ruby program demonstrating the use of the else statement
    
    begin
      puts 'No exception is raised here'
    rescue
      puts 'Exception handled!'
    else
      puts 'Else block executed because no exception was raised'
    ensure
      puts 'Ensure block executed'
    end

    Output:

    No exception is raised here
    Else block executed because no exception was raised
    Ensure block executed

    5. catch and throw Statements:catch and throw provide a lightweight mechanism for managing errors and jumps in Ruby. The catch block works normally until it encounters a throw. When a throw is encountered, Ruby looks for the matching catch block with the same label.

    Syntax:

    catch :label_name do
      # Code block
      throw :label_name if condition
    end

    Example:

    # Ruby program to illustrate Class Variables
    
    class Library
      # Class variable
      @@book_count = 0
    
      def initialize(title)
        # Instance variable
        @title = title
      end
    
      def display_title
        puts "Book Title: #{@title}"
      end
    
      def add_book
        # Increment the class variable
        @@book_count += 1
        puts "Total books: #@@book_count"
      end
    end
    
    # Creating objects
    book1 = Library.new("The Art of Ruby")
    book2 = Library.new("Ruby on Rails Guide")
    
    # Calling methods
    book1.display_title
    book1.add_book
    
    book2.display_title
    book2.add_book

    Input:

    Enter a number: 1

    Output:

    1

    Input:

    Enter a number: !

    Output:

    nil

    Catch and Throw Exception In Ruby

    In Ruby, an exception is an object that belongs to the Exception class or one of its subclasses. An exception occurs when a program reaches an undefined or unexpected state. At this point, Ruby doesn’t know how to proceed, so it raises an exception. This can happen automatically or be done manually by the programmer.

    The catch and throw keywords in Ruby provide a way to handle exceptions, similar to how raise and rescue work. The throw keyword generates an exception, and when it is encountered, the program’s control flow jumps to the corresponding catch statement.

    Syntax

    catch :label_name do
      # Code block that runs until a throw is encountered
      throw :label_name if condition
      # This block will not execute if throw is encountered
    end

    The catch block is used to jump out of nested code, and it will continue executing normally until a throw statement is encountered.

    Example: Basic catch and throw

    # Ruby Program using Catch and Throw for Exception Handling
    result = catch(:divide) do
      # Code block similar to 'begin'
      number = rand(2)
      throw :divide if number == 0
      number # set result = number if no exception is thrown
    end
    
    puts result

    Output: If the random number is 0, the output will be empty since the throw :divide statement will execute and jump to the catch block. If the random number is 1, the output will be:

    1

    Explanation: If the random number is 0throw :divide is executed, and nothing is returned to the catch block, resulting in an empty output. If the number is 1, the exception is not thrown, and the result variable is set to 1.

    Example:

    In the previous example, when the exception was thrown, the variable result was set to nil. We can change this behavior by providing a default value to the throw keyword.

    # Ruby Program using Catch and Throw with a Default Value
    result = catch(:divide) do
      # Code block similar to 'begin'
      number = rand(2)
      throw :divide, 10 if number == 0
      number # set result = number if no exception is thrown
    end
    
    puts result

    Output:

    1

    Exception Handling in Ruby

    In Ruby, a literal is any constant value that can be assigned to a variable. We use literals whenever we type an object directly into the code. Ruby literals are similar to those in other programming languages, with some differences in syntax and functionality.

    Types of Ruby Literals

    Ruby supports various types of literals:

    1. Booleans and nil
    2. Numbers
    3. Strings
    4. Symbols
    5. Ranges
    6. Arrays
    7. Hashes
    8. Regular Expressions

    1. Booleans and nil: Booleans are constants that represent the truth values true and falsenil is another constant that represents an “unknown” or “empty” value, and it behaves similarly to false in conditional expressions.

    Example:

    raise exception_type, "exception message" if condition

    Output:

    true
    false
    false

    When a raise statement is executed, it invokes the rescue block. By default, raise raises a RuntimeError.

    Example 1: Using raise Statement

    # Ruby program to demonstrate the use of raise statement
    begin
      puts 'This is Before Exception Arises!'
    
      # Using raise to create an exception
      raise 'Exception Created!'
    
      puts 'After Exception'
    end

    Output:

    This is Before Exception Arises!
    Exception Created!

    Ruby Exception Handling in Threads

    Threads in Ruby can also encounter exceptions. By default, only exceptions that arise in the main thread are handled, while exceptions in other threads cause those threads to terminate. The behavior of threads when an exception arises is controlled by the abort_on_exception method, which is set to false by default.

    If abort_on_exception is false, an unhandled exception will abort the current thread but allow other threads to continue running. The setting can be changed by using Thread.abort_on_exception = true or by setting $DEBUG to true. Additionally, Ruby provides the ::handle_interrupt method for handling exceptions asynchronously within threads.

    Example 1: Exception in Threads

    # Ruby program to demonstrate exception handling in threads
    
    #!/usr/bin/ruby
    
    threads = []
    4.times do |value|
      threads << Thread.new(value) do |i|
        # Raise an error when i equals 2
        raise "An error occurred!" if i == 2
        print "#{i}\n"
      end
    end
    
    threads.each { |t| t.join }

    Output:

    0
    3
    1
    main.rb:11:in `block (2 levels) in <main>': An error occurred! (RuntimeError)

    Note: The Thread.join method waits for a specific thread to finish. When a Ruby program ends, all threads are terminated, regardless of their states.

    Saving Exceptions in Threads

    In the following example, we handle exceptions within threads to prevent program termination and continue processing other threads.

    Example 2: Handling Exceptions in Threads

    # Ruby program to handle exceptions in threads
    
    #!/usr/bin/ruby
    
    threads = []
    
    5.times do |value|
      threads << Thread.new(value) do |i|
        raise "An error occurred!" if i == 3
        print "#{i}\n"
      end
    end
    
    threads.each do |t|
      begin
        t.join
      rescue RuntimeError => e
        puts "Exception caught: #{e.message}"
      end
    end

    Output:

    0
    1
    4
    2
    Exception caught: An error occurred!
    Using abort_on_exception

    Setting abort_on_exception to true will terminate the entire program as soon as an exception occurs in any thread. Once the thread with the exception is terminated, no further output will be generated.

    Example 3: Using abort_on_exception

    # Ruby program to demonstrate thread termination on exception
    
    #!/usr/bin/ruby
    
    # Set abort_on_exception to true
    Thread.abort_on_exception = true
    
    threads = []
    
    5.times do |value|
      threads << Thread.new(value) do |i|
        raise "An error occurred!" if i == 3
        print "#{i}\n"
      end
    end
    
    # Use Thread.join to wait for threads to finish
    threads.each { |t| t.join }

    Output:

    0
    1
    2
    main.rb:12:in `block (2 levels) in <main>': An error occurred! (RuntimeError)

  • OOP Concepts in Ruby

    Object-Oriented Programming (OOP) is a way of organizing code around objects, which represent real-world entities. In Ruby, everything is an object—including numbers, strings, arrays, and even classes.

    In OOP, a class is a blueprint, and an object is an instance of that blueprint. For example, if Vehicle is a class, then car1 and car2 are objects created from it—each with its own attribute values.


    Classes and Objects in Ruby

    A class defines attributes (data) and methods (behavior). An object is created from a class using .new.

    Basic Class Syntax

    class ClassName
    end
    

    Creating a Class with a Constructor

    Ruby uses the initialize method as a constructor. It runs automatically when you create an object using .new.

    class Vehicle
      def initialize(make, model)
        @make = make
        @model = model
      end
    end
    

    Creating Objects

    car1 = Vehicle.new("Toyota", "Camry")
    car2 = Vehicle.new("Honda", "Accord")
    

    Instance Methods and Returning Values

    In Ruby, a method returns the last evaluated expression, so return is usually optional.

    class Vehicle
      def initialize(make, model)
        @make = make
        @model = model
      end
    
      def make
        @make
      end
    
      def model
        @model
      end
    end
    

    Modifying Attributes Safely

    Instead of accessing instance variables directly, you update values through methods.

    class Vehicle
      def initialize(make, model)
        @make = make
        @model = model
      end
    
      def model
        @model
      end
    
      def change_model(new_model)
        @model = new_model
      end
    end
    
    car = Vehicle.new("Toyota", "Camry")
    car.change_model("Corolla")
    puts car.model
    

    Using attr_reader, attr_writer, and attr_accessor

    Ruby provides built-in helpers to create getter and setter methods.

    • attr_reader → getter only
    • attr_writer → setter only
    • attr_accessor → getter + setter
    class Vehicle
      attr_accessor :make, :model
    
      def initialize(make, model)
        @make = make
        @model = model
      end
    end
    
    car = Vehicle.new("Toyota", "Camry")
    car.model = "Corolla"
    puts car.model
    

    Variable Scope in Ruby

    Ruby supports multiple variable types.

    Instance Variables (@var)

    Belong to a single object.

    Class Variables (@@var)

    Shared by all objects of a class.

    Global Variables ($var)

    Accessible everywhere (not recommended for most programs).


    Class Variables and Class Methods

    Class variables are shared across instances. Use class methods to access them.

    class Vehicle
      @@count = 0
    
      def initialize(make, model)
        @make = make
        @model = model
        @@count += 1
      end
    
      def self.count
        @@count
      end
    end
    
    Vehicle.new("Toyota", "Camry")
    Vehicle.new("Honda", "Accord")
    
    puts Vehicle.count
    

    Inheritance in Ruby

    Inheritance allows a child class to reuse and extend behavior from a parent class.

    class Device
      def description
        puts "This is a general device."
      end
    end
    
    class Laptop < Device
      def description
        puts "This is a laptop."
      end
    end
    
    Laptop.new.description
    

    Using super in Inheritance

    Use super to call the parent method.

    class Device
      def description
        puts "This is a general device."
      end
    end
    
    class Laptop < Device
      def description
        puts "This is a laptop."
        super
      end
    end
    
    Laptop.new.description
    

    Modules and Mixins

    Ruby doesn’t support multiple inheritance directly, but you can reuse behavior using modules.

    Using include (instance methods)

    module Features
      def feature_list
        puts "Supports Wi-Fi and Bluetooth."
      end
    end
    
    class Laptop
      include Features
    end
    
    Laptop.new.feature_list
    

    Using extend (class methods)

    module Features
      def feature_list
        puts "This class supports advanced features."
      end
    end
    
    class Laptop
      extend Features
    end
    
    Laptop.feature_list
    

    Access Control in Ruby

    Ruby controls access at the method level, using:

    • public (default)
    • private
    • protected

    Public and Private Example

    class Device
      def display_info
        greet
        puts "Device ready."
      end
    
      private
    
      def greet
        puts "Hello!"
      end
    end
    
    Device.new.display_info
    

    Polymorphism in Ruby

    Polymorphism means the same method can behave differently depending on the object.

    Polymorphism via Inheritance

    class Instrument
      def play
        puts "Playing an instrument"
      end
    end
    
    class Guitar < Instrument
      def play
        puts "Playing the guitar"
      end
    end
    
    class Piano < Instrument
      def play
        puts "Playing the piano"
      end
    end
    
    [Instrument.new, Guitar.new, Piano.new].each(&:play)
    

    Polymorphism via Duck Typing

    Ruby cares about what an object can do, not what it is.

    class Store
      def order(customer)
        customer.payment_type
        customer.discount
      end
    end
    
    class RegularCustomer
      def payment_type; puts "Paying with card"; end
      def discount; puts "No discount"; end
    end
    
    class PremiumCustomer
      def payment_type; puts "Paying with points"; end
      def discount; puts "10% discount"; end
    end
    
    store = Store.new
    store.order(RegularCustomer.new)
    store.order(PremiumCustomer.new)
    

    Freezing Objects in Ruby

    freeze makes an object immutable.

    item = "Laptop"
    item.freeze
    
    puts item.frozen?   # true
    

    Summary

    Ruby is a fully object-oriented language where classes, objects, inheritance, modules, and polymorphism are core tools for writing maintainable code. By combining:

    • classes and constructors (initialize)
    • accessors (attr_accessor)
    • inheritance (< and super)
    • modules (include and extend)
    • access control (public, private, protected)
    • polymorphism (inheritance + duck typing)

    …you can design clean, scalable Ruby applications.

  • Ruby Methods

    A method in Ruby is a reusable block of code that performs a specific task and optionally returns a value. Methods help reduce repetition by letting you define logic once and call it multiple times.

    In Ruby, methods are defined using the def keyword and closed with end. Method names are typically written in lowercase (snake_case).


    Defining and Calling a Method

    A method must be defined before it can be called.

    Basic Syntax

    def method_name
      # statements
    end
    

    Example:

    def welcome
      puts "Hello! Welcome to Ruby Programming."
    end
    
    welcome
    

    Output:

    Hello! Welcome to Ruby Programming.
    

    Passing Parameters to Methods

    You can pass values into a method using parameters. Ruby also supports default parameter values, which are used if the caller does not provide an argument.

    Example with Default Values

    def display_info(name = "Ruby", level = "Beginner")
      puts "Language: #{name}"
      puts "Level: #{level}"
    end
    
    display_info "Python", "Intermediate"
    puts ""
    display_info
    

    Output:

    Language: Python
    Level: Intermediate
    
    Language: Ruby
    Level: Beginner
    

    Accepting a Variable Number of Arguments

    When the number of arguments is unknown, Ruby lets you collect them using the splat operator *.

    Example-

    def show_items(*items)
      puts "Number of items: #{items.length}"
      items.each_with_index do |item, index|
        puts "Item #{index + 1}: #{item}"
      end
    end
    
    show_items "Apple", "Banana", "Cherry"
    show_items "Orange"
    

    Returning Values from Methods

    Ruby automatically returns the value of the last evaluated expression. You only need return when you want to exit early or return multiple values explicitly.

    Eg:

    def calculate_sum
      num1 = 25
      num2 = 75
      num1 + num2
    end
    
    puts "The sum is: #{calculate_sum}"
    

    Method Visibility in Ruby

    Method visibility determines where methods can be called from. Ruby provides three access levels:

    • public (default): callable from anywhere
    • protected: callable within the class and its subclasses, often for comparisons between instances
    • private: callable only inside the class, and not with an explicit receiver (even self)

    Public Methods Example

    class Example
      def public_method1
        puts "public_method1 called!"
      end
    
      public
    
      def public_method2
        puts "public_method2 called!"
      end
    end
    
    obj = Example.new
    obj.public_method1
    obj.public_method2
    

    Protected Methods Example

    class Parent
      protected
    
      def protected_method
        puts "protected_method called!"
      end
    end
    
    class Child < Parent
      def call_protected
        protected_method
      end
    end
    
    Child.new.call_protected
    

    Private Methods Example

    class Example
      private
    
      def private_method
        puts "private_method called!"
      end
    
      public
    
      def public_method
        private_method
      end
    end
    
    Example.new.public_method
    

    Recursion in Ruby

    Recursion happens when a method calls itself to solve a problem in smaller steps. It can make certain solutions elegant, but Ruby recursion can cause stack overflow for very large inputs.


    Iterative vs Recursive Array Sum

    Iterative:

    def iterative_sum(numbers)
      sum = 0
      numbers.each { |n| sum += n }
      sum
    end
    
    puts iterative_sum([1, 2, 3, 4, 5])
    

    Recursive:

    def recursive_sum(numbers)
      return 0 if numbers.empty?
      numbers[0] + recursive_sum(numbers[1..])
    end
    
    puts recursive_sum([1, 2, 3, 4, 5])
    

    Recursive Factorial Example

    def factorial(n)
      return 1 if n <= 1
      n * factorial(n - 1)
    end
    
    puts factorial(5)
    

    Recursive Fibonacci Example

    def fibonacci(n)
      return n if n < 2
      fibonacci(n - 1) + fibonacci(n - 2)
    end
    
    puts fibonacci(5)
    

    Tip: recursive Fibonacci is slow for large values—use iteration or memoization in real projects.


    Ruby Hook Methods

    Hook methods are special methods triggered by events such as including a module, inheriting from a class, or calling undefined methods.

    Common hook methods include:

    • included
    • prepended
    • extended
    • inherited
    • method_missing

    included Hook

    module WelcomeMessage
      def self.included(target)
        puts "The #{target} has been greeted with a warm welcome!"
      end
    end
    
    class User
      include WelcomeMessage
    end
    

    prepended Hook

    module Language
      def self.prepended(target)
        puts "#{self} has been prepended to #{target}"
      end
    
      def description
        "The language used is Ruby."
      end
    end
    
    class Programming
      prepend Language
    end
    
    puts Programming.new.description
    

    extended Hook

    module Framework
      def self.extended(target)
        puts "#{self} was extended by #{target}"
      end
    
      def description
        "This framework is based on Ruby."
      end
    end
    
    class Software
      extend Framework
    end
    
    puts Software.description
    

    inherited Hook

    class Vehicle
      def self.inherited(subclass)
        puts "#{subclass} is a subclass of Vehicle"
      end
    end
    
    class Car < Vehicle
    end
    

    method_missing

    class Language
      def method_missing(method_name, *args)
        "#{method_name} is not defined for #{self}"
      end
    
      def known_method
        "This method is defined."
      end
    end
    
    obj = Language.new
    puts obj.known_method
    puts obj.unknown_method
    

    Ruby Range Class Methods and Examples

    A Range represents a sequence between two values.

    • .. includes the end value
    • ... excludes the end value

    Creating Ranges

    (1..6).to_a    # => [1, 2, 3, 4, 5, 6]
    (1...6).to_a   # => [1, 2, 3, 4, 5]
    

    Useful Range Methods

    range = (3..10)
    
    puts range.begin      # 3
    puts range.end        # 10
    puts range.first      # 3
    puts range.last       # 10
    puts range.include?(5)  # true
    puts range.include?(15) # false
    

    Iteration with each and step

    (1..5).each { |i| print "#{i} " }
    puts ""
    
    (1..10).step(2) { |i| print "#{i} " }
    puts ""
    

    Summary

    Ruby methods make your code reusable and easier to maintain. In this guide, you learned how to:

    • define and call methods
    • pass parameters and defaults
    • accept variable-length arguments
    • return values naturally
    • control method visibility (public/protected/private)
    • use recursion safely
    • use hook methods for metaprogramming
    • work with Ruby ranges

  • Control Statement

    Ruby Decision Making (if, if-else, if-else-if, ternary)

    Decision-making in programming is much like making choices in real life. In a program, certain blocks of code are executed based on whether a specific condition is true or false. Programming languages use control statements to manage the flow of execution depending on these conditions. These control structures guide the direction of execution, causing the program to branch or continue based on the current state. In Ruby, the if-else structure helps in testing these conditions.

    Types of Decision-Making Statements in Ruby:

    1. if statement
    2. if-else statement
    3. if-elsif ladder
    4. Ternary statement

    1. if Statement: In Ruby, the if statement determines whether a block of code should be executed based on a condition. If the condition evaluates to true, the associated code is executed.

    Syntax:

    if (condition)
       # code to be executed
    end

    Example:

    # Ruby program to demonstrate the if statement
    
    temperature = 30
    
    # Check if temperature is above 25 degrees
    if temperature > 25
      puts "It's a warm day."
    end

    Output:

    It's a warm day.

    2. if-else Statement: The if-else statement is used when one block of code should be executed if the condition is true, and another block should run if the condition is false.

    Syntax:

    if (condition)
        # code if the condition is true
    else
        # code if the condition is false
    end

    Example:

    # Ruby program to demonstrate if-else statement
    
    time = 16
    
    # Check if the time is past noon
    if time > 12
      puts "Good afternoon!"
    else
      puts "Good morning!"
    end

    Output:

    Good afternoon!

    3. if-elsif-else Ladder: This structure allows multiple conditions to be evaluated one after another. Once a true condition is found, the corresponding block of code is executed, and the remaining conditions are skipped.

    Syntax:

    if (condition1)
        # code if condition1 is true
    elsif (condition2)
        # code if condition2 is true
    else
        # code if neither condition1 nor condition2 is true
    end

    Example:

    # Ruby program to demonstrate if-elsif-else ladder
    
    score = 85
    
    if score < 50
      puts "You failed."
    elsif score >= 50 && score < 65
      puts "You passed with a C grade."
    elsif score >= 65 && score < 80
      puts "You passed with a B grade."
    elsif score >= 80 && score < 90
      puts "You passed with an A grade."
    else
      puts "You passed with an A+ grade."
    end

    Output:

    You passed with an A grade.

    4. Ternary Operator: The ternary operator is a shorthand for simple if-else statements. It evaluates a condition and returns one of two values based on whether the condition is true or false.

    Syntax:

    condition ? true_value : false_value

    Example:

    # Ruby program to demonstrate the ternary operator
    
    age = 20
    
    # Ternary operator to check if the person is an adult
    status = (age >= 18) ? "Adult" : "Minor"
    puts status

    Output:

    Adult

    Ruby Loops (for, while, do..while, until)

    Looping is an essential concept in programming, enabling the repeated execution of a block of code based on a specific condition. Ruby, a dynamic and flexible language, offers various looping constructs to handle iterations effectively. These loops help automate tasks that require repetitive actions within a program.

    The primary types of loops in Ruby include:

    1. while Loop
    2. for Loop
    3. do..while Loop
    4. until Loop

    1. while Loop: In a while loop, the condition is evaluated at the beginning of the loop, and the code block is executed as long as the condition is true. Once the condition becomes false, the loop terminates. This is known as an Entry-Controlled Loop since the condition is checked before executing the loop body. It’s commonly used when the number of iterations is unknown.

    Syntax:

    while condition [do]
      # code to be executed
    end

    Example:

    # Ruby program demonstrating the 'while' loop
    
    counter = 3
    
    # Using the while loop to print a message 3 times
    while counter > 0
      puts "Welcome to Ruby programming!"
      counter -= 1
    end

    Output:

    Welcome to Ruby programming!
    Welcome to Ruby programming!
    Welcome to Ruby programming!

    2. for Loop: The for loop in Ruby functions similarly to the while loop but has a more concise syntax, especially useful when the number of iterations is known in advance. It is often used to iterate over a range, array, or collection. This loop is also an Entry-Controlled Loop since the condition is evaluated before the loop begins.

    Syntax:

    for variable_name[, variable...] in expression [do]
      # code to be executed
    end

    Example:

    # Ruby program demonstrating the 'for' loop using a range
    
    for num in 1..4 do
      puts "Number: #{num}"
    end

    Output:

    Number: 1
    Number: 2
    Number: 3
    Number: 4

    Example 2: Iterating Over an Array

    # Ruby program demonstrating the 'for' loop with an array
    
    fruits = ["Apple", "Banana", "Cherry"]
    
    for fruit in fruits do
      puts fruit
    end

    Output:

    Apple
    Banana
    Cherry

    3. do..while Loop: The do..while loop is similar to the while loop, but with one key difference: the condition is evaluated after the code block has been executed, ensuring that the loop runs at least once. This makes it an Exit-Controlled Loop.

    Syntax:

    loop do
      # code to be executed
      break if condition
    end

    Example:

    # Ruby program demonstrating the 'do..while' loop
    
    counter = 0
    
    # The loop will run at least once
    loop do
      puts "Iteration #{counter + 1}"
      counter += 1
      break if counter == 3
    end

    Output:

    Iteration 1
    Iteration 2
    Iteration 3

    4. until Loop: The until loop in Ruby is the opposite of the while loop. It continues executing as long as the given condition remains false, and stops once the condition becomes true. Like while, this is also an Entry-Controlled Loop.

    Syntax:

    until condition [do]
      # code to be executed
    end

    Example:

    # Ruby program demonstrating the 'until' loop
    
    counter = 5
    
    # Using the until loop to print values until counter reaches 10
    until counter == 10 do
      puts counter
      counter += 1
    end

    Output:

    5
    6
    7
    8
    9

    5. Class Variables: Class variables start with @@ and are shared across all instances of a class. They belong to the class itself rather than any individual instance. Class variables must be initialized before they are used. An uninitialized class variable will raise an error.

    Example:

    # Ruby program to illustrate Class Variables
    
    class Library
      # Class variable
      @@book_count = 0
    
      def initialize(title)
        # Instance variable
        @title = title
      end
    
      def display_title
        puts "Book Title: #{@title}"
      end
    
      def add_book
        # Increment the class variable
        @@book_count += 1
        puts "Total books: #@@book_count"
      end
    end
    
    # Creating objects
    book1 = Library.new("The Art of Ruby")
    book2 = Library.new("Ruby on Rails Guide")
    
    # Calling methods
    book1.display_title
    book1.add_book
    
    book2.display_title
    book2.add_book

    Output:

    Book Title: The Art of Ruby
    Total books: 1
    Book Title: Ruby on Rails Guide
    Total books: 2

    6. Global Variables: Global variables start with a $ sign and are accessible from anywhere in the Ruby program. They are not limited to a specific class or scope. By default, an uninitialized global variable has a nil value. However, excessive use of global variables can make code difficult to debug and maintain.

    Example:

    # Ruby program to illustrate Global Variables
    
    # Global variable
    $global_count = 5
    
    class FirstClass
      def display_count
        puts "Global count in FirstClass: #$global_count"
      end
    end
    
    class SecondClass
      def display_count
        puts "Global count in SecondClass: #$global_count"
      end
    end
    
    # Creating objects
    obj1 = FirstClass.new
    obj2 = SecondClass.new
    
    # Calling methods
    obj1.display_count
    obj2.display_count

    Output:

    Global count in FirstClass: 5
    Global count in SecondClass: 5

    Case Statement Without a Value

    In Ruby, you can use a case statement without specifying a value. Instead, you can use the when clauses to evaluate different conditions directly.

    Example: Case Statement Without a Value

    # Ruby program demonstrating case statement without a value
    
    str = "HelloWorld123"
    
    # Case statement evaluating different conditions
    case
    when str.match(/\d/)
      puts "The string contains numbers."
    when str.match(/[a-zA-Z]/)
      puts "The string contains letters."
    else
      puts "The string contains neither letters nor numbers."
    end

    Example:

    The string contains numbers.

    Output:

    Global variable in FirstClass is 20
    Global variable in SecondClass is 20
    Case Statement in Method Call

    You can use a case statement directly in a method call, where it will return a value just like any other method.

    Example: Case Statement in a Method Call

    # Ruby program demonstrating case statement in a method call
    
    str = "4567"
    
    # Case statement inside a method call to return a value
    puts case
    when str.match(/\d/)
      "The string contains numbers."
    when str.match(/[a-zA-Z]/)
      "The string contains letters."
    else
      "The string contains neither numbers nor letters."
    end

    Output:

    The string contains numbers.

    Control Flow Statements in Ruby

    Ruby provides several control flow statements in addition to loops, conditionals, and iterators. These statements allow altering the normal execution flow in a program, controlling how the code is executed based on conditions or specific cases.

    Here’s a breakdown of some important Ruby control flow statements:

    1. break Statement: The break statement is used to exit a loop prematurely when a certain condition is met. Typically used in loops such as whilefor, and case statements, it stops the execution of the loop as soon as the condition becomes true.

    Syntax:

    break

    Example:

    # Ruby program demonstrating the break statement
    
    i = 1
    
    # Using a while loop
    while true
      if i * 6 >= 30
        break  # Exit the loop if condition is met
      end
    
      puts i * 6
      i += 1
    end

    Output:

    6
    12
    18
    24

    2.next Statement: The next statement is used to skip the current iteration and move to the next one in a loop. It’s similar to the continue statement in languages like C and Java.

    Syntax:

    next

    Example:

    # Ruby program demonstrating the next statement
    
    # Using a for loop
    for t in 0...10
      if t == 5
        next  # Skip the iteration when t is 5
      end
    
      puts t
    end

    Output:

    0
    1
    2
    3
    4
    6
    7
    8
    9

    3. redo Statement: The redo statement restarts the current iteration of the loop without testing the loop’s condition again. Unlike next, which skips to the next iteration, redo causes the loop to restart the current one.

    Syntax:

    redo

    Example:

    # Ruby program demonstrating the redo statement
    
    val = 0
    
    while val < 4
      puts val
      val += 1
    
      redo if val == 4  # Restart the loop when val equals 4
    end

    Output:

    0
    1
    2
    3
    4

    4.retry Statement (Deprecated): The retry statement was used in earlier versions of Ruby (before 1.9) to restart a loop or an iterator from the beginning. It has been removed in later versions, as it was considered a deprecated feature.

    5.return Statement: The return statement is used to exit a method and optionally pass a value back to the caller. If no value is provided, it returns nil.

    Example:

    # Ruby program demonstrating the return statement
    
    def my_method
      val1 = 100
      val2 = 200
    
      return val1, val2  # Returning multiple values
    
      puts "This won't be executed"
    end
    
    result = my_method
    puts result

    Output:

    100
    200

    6. throw/catch Statement: The throw and catch statements are used to create an advanced control flow structure. It’s like a multi-level break that allows you to exit out of deeply nested loops or methods. throw is used to break out, while catch defines a label where control can be transferred.

    Example:

    # Ruby program demonstrating throw/catch control flow
    
    def check_number(num)
      throw :error if num < 10  # Exit the block if number is less than 10
      puts "Number is greater than or equal to 10!"
    end
    
    catch :error do
      check_number(15)
      check_number(25)
      check_number(5)  # This will cause the throw statement to exit the block
    end
    
    puts "Code after catch block"

    Output:

    Number is greater than or equal to 10!
    Number is greater than or equal to 10!
    Code after catch block

    Break and Next Statement

    Break statement: 

    In Ruby, the break statement is used to stop the execution of a loop prematurely. It is often applied in loops like while and for, where it helps to exit the loop when a specific condition is met. Once the condition triggers the break statement, the loop terminates, and the code continues after the loop.

    Syntax:

    break

    Example:

    # Ruby program demonstrating the break statement
    i = 1
    
    # Using a while loop
    while true
      puts i * 3  # Print multiples of 3
      i += 1
      if i * 3 >= 21
        break  # Exit the loop if the condition is met
      end
    end

    Output:

    3
    6
    9
    12
    15
    18

    Explanation: In this example, the loop prints multiples of 3. Once the value i * 3 becomes 21 or greater, the break statement stops the loop.

    Example:

    # Another example demonstrating break statement
    x = 0
    
    # Using a while loop
    while true
      puts x  # Print current value of x
      x += 1
      break if x > 3  # Stop the loop when x becomes greater than 3
    end

    Output:

    0
    1
    2
    3
    next Statement in Ruby

    The next statement is used to skip the rest of the current loop iteration and immediately proceed to the next iteration. It’s similar to the continue statement in other programming languages like C or Python. This is useful when certain conditions inside a loop should cause the loop to skip to the next step without executing further code for that iteration.

    Syntax:

    next

    Example:

    # Ruby program demonstrating the next statement
    for x in 0..6
      if x + 1 < 4
        next  # Skip the current iteration if the condition is met
      end
    
      puts "Value of x is: #{x}"  # Print the value of x for other cases
    end

    Output:

    Value of x is: 3
    Value of x is: 4
    Value of x is: 5
    Value of x is: 6

    Working with Directories in Ruby

    directory is a location where files can be stored. In Ruby, the Dir class and the FileUtils module provide various methods for managing directories, while the File class handles file operations. Double dot (..) refers to the parent directory, and single dot (.) refers to the current directory itself.

    The Dir Class

    The Dir class allows access to and manipulation of directory structures in Ruby. It includes methods for listing directory contents, creating directories, changing directories, and more.

    Features of the Dir Class

    1. Creating Directories: The mkdir method is used to create a new directory. It returns 0 if the directory is successfully created.

    Syntax:

    next

    Examples:

    # Ruby program demonstrating the next statement
    for x in 0..6
      if x + 1 < 4
        next  # Skip the current iteration if the condition is met
      end
    
      puts "Value of x is: #{x}"  # Print the value of x for other cases
    end

    Output:

    Value of x is: 3
    Value of x is: 4
    Value of x is: 5
    Value of x is: 6

    Ruby redo and retry Statement

    redo statement:

    The redo statement in Ruby is used to repeat the current iteration of a loop without re-evaluating the loop condition. It is useful when you want to retry the current iteration based on a specific condition. This statement can only be used inside loops.

    Syntax:

    redo

    Example:

    # Ruby program demonstrating redo statement
    restart = false
    
    # Using a for loop
    for x in 2..20
      if x == 15
        if restart == false
          # Print message when x is 15 for the first time
          puts "Re-doing when x = #{x}"
          restart = true
    
          # Using redo statement to repeat the current iteration
          redo
        end
      end
      puts x
    end

    Output:

    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Re-doing when x = 15
    15
    16
    17
    18
    19
    20
    retry Statement in Ruby

    The retry statement is used to restart the entire loop from the beginning. It is typically used within a block that includes begin-rescue error handling. The control jumps back to the start of the block or the loop, retrying the entire process. As of Ruby 1.9, the retry statement is considered deprecated for regular loops but is still available in begin-rescue blocks for exception handling.

    Example :

    # Ruby program demonstrating retry statement
    
    # Using a do loop
    10.times do |i|
      begin
        puts "Iteration #{i}"
        raise if i > 2  # Raise an exception if i is greater than 2
      rescue
        # Retry the iteration if an exception is raised
        retry
      end
    end

    Output:

    Iteration 0
    Iteration 1
    Iteration 2
    Iteration 3
    Iteration 3
    Iteration 3
    ...
  • Basic Concepts

    Ruby Keywords

    Keywords or reserved words are special words in a programming language that have predefined meanings and are used for certain internal processes. These words cannot be used as identifiers such as variable names, object names, or constants. Attempting to use these reserved words as identifiers will result in a compile-time error.

    Example of Invalid Use of Keywords

    # Ruby program to illustrate Keywords
    
    # This is an incorrect use of the keyword 'if'
    # It cannot be used as a variable name
    if = 30
    
    # Here 'if' and 'end' are keywords used incorrectly
    # Using them will result in a syntax error
    if if >= 18
      puts "You are eligible to drive."
    end

    Compile-Time Error:

    Error(s), warning(s):
    
    example.rb:4: syntax error, unexpected '='
    
    if = 30
    
        ^
    
    example.rb:9: syntax error, unexpected '>='
    
    if if >= 18
    
            ^
    
    example.rb:11: syntax error, unexpected keyword_end, expecting end-of-input
    Common Ruby Keywords

    Ruby has a total of 41 reserved keywords. Here are some of the most common ones and their uses:

    KeywordDescription
    __ENCODING__The script encoding of the current file.
    __LINE__The line number in the current file.
    __FILE__The path to the current file.
    BEGINRuns code before any other in the current file.
    ENDRuns code after all other code in the current file.
    aliasCreates an alias for a method.
    andLogical AND with lower precedence than &&.
    classDefines a new class.
    defDefines a method.
    doBegins a block of code.
    endEnds a syntax block such as a class, method, or loop.
    ifConditional statement.
    moduleDefines a module.
    nextSkips to the next iteration of a loop.
    nilRepresents “no value” or “undefined”.
    returnExits from a method and optionally returns a value.
    selfRefers to the current object.
    trueBoolean true value.
    whileCreates a loop that executes while a condition is true.

    Example of Using Keywords Correctly

    Here’s a simple example demonstrating the correct use of some Ruby keywords:

    # Ruby program to illustrate the use of Keywords
    
    #!/usr/bin/ruby
    
    # Defining a class named 'Person'
    class Person
    
      # Defining a method using 'def' keyword
      def introduce
        # Printing a statement using 'puts'
        puts "Hello! I'm learning Ruby."
      end
    
    # End of the method
    end
    
    # End of the class
    end
    
    # Creating an object of the class 'Person'
    student = Person.new
    
    # Calling the method using the object
    student.introduce

    Output:

    Hello! I'm learning Ruby.

    Ruby Data Types

    In Ruby, data types represent various kinds of data such as text, numbers, symbols, arrays, etc. Since Ruby is a pure Object-Oriented Language, all data types are based on classes. Here are the primary data types in Ruby:

    1. Numbers
    2. Boolean
    3. Strings
    4. Hashes
    5. Arrays
    6. Symbols

    1. Numbers: A number is generally defined as a sequence of digits, which may include a dot for decimal places. Ruby supports both integer and floating-point numbers. Depending on their size, numbers can be categorized into Fixnum and Bignum. However, in modern Ruby versions, they have been unified under the Integer class.

    Example:

    # Ruby program to illustrate Numbers Data Type
    
    # Float type
    distance = 0.2
    
    # Both integer and float type
    time = 15.0 / 3600
    speed = distance / time
    puts "The average speed of the cyclist is #{speed} km/h"

    Output:

    The average speed of the cyclist is 48.0 km/h

    2. Boolean: The Boolean data type represents two values: true or false. This data type is used for simple true/false conditions.

    Example:

    # Ruby program to illustrate Boolean Data Type
    
    if false
      puts "This won't be printed!"
    else
      puts "This is False!"
    end
    
    if nil
      puts "nil is True!"
    else
      puts "nil is False!"
    end
    
    if 1
      puts "1 is True!"
    else
      puts "1 is False!"
    end

    Output:

    This is False!
    nil is False!
    1 is True!

    3. Strings: A string is a collection of characters enclosed within either single (') or double (") quotes. Double-quoted strings allow for string interpolation and special character sequences, while single-quoted strings only support limited escape sequences.

    Example:

    # Ruby program to illustrate Strings Data Type
    
    puts "Ruby String Data Type"
    puts 'Escape using "\\"'
    puts 'It\'s a beautiful day!'

    Output:

    Ruby String Data Type
    Escape using "\"
    It's a beautiful day!

    4. Hashes: A hash is a collection of key-value pairs, similar to a dictionary in Python or an associative array in PHP. Keys and values are separated by => and each pair is enclosed within curly braces {}.

    Example:

    # Ruby program to illustrate Hashes Data Type
    
    colors = { "red" => "#FF0000", "green" => "#00FF00", "blue" => "#0000FF" }
    colors.each do |color, hex|
      puts "#{color} has hex value #{hex}"
    end

    Output:

    red has hex value #FF0000
    green has hex value #00FF00
    blue has hex value #0000FF

    5. Arrays: An array is an ordered collection of elements, which can contain data of any type. Elements are separated by commas and enclosed within square brackets []. Arrays in Ruby can hold mixed data types.

    Example

    # Ruby program to illustrate Arrays Data Type
    
    data = ["Alice", 25, 5.5, "Hello, world!", "last item"]
    data.each do |element|
      puts element
    end

    Output:

    Alice
    25
    5.5
    Hello, world!
    last item

    6. Symbols: Symbols are lightweight, immutable strings. They are used instead of strings in situations where memory optimization is crucial. A symbol is prefixed with a colon (:).

    Example:

    # Ruby program to illustrate Symbols Data Type
    
    countries = { :us => "United States", :ca => "Canada", :fr => "France" }
    
    puts countries[:us]
    puts countries[:ca]
    puts countries[:fr]

    Output:

    United States
    Canada
    France

    Types of Variables in Ruby

    In Ruby, there are four main types of variables, each serving different purposes and having different scopes. Each type is distinguished by a special character at the beginning of the variable name.

    SymbolType of Variable
    [a-z] or _Local Variable
    @Instance Variable
    @@Class Variable
    $Global Variable

    1. Local Variables: Local variables in Ruby start with a lowercase letter (a-z) or an underscore (_). They are restricted to the scope in which they are defined (e.g., inside a method or a block). Local variables do not need to be initialized before use.

    Example:

    # Ruby program to illustrate Local Variables
    
    # Local variables
    name = "Alice"
    _age = 30
    
    puts "Name: #{name}"
    puts "Age: #{_age}"

    Output:

    Name: Alice
    Age: 30

    2. Instance Variables: Instance variables start with an @ sign. They belong to a specific instance of a class and are accessible across different methods within that instance. Each instance of the class can have different values for its instance variables. Uninitialized instance variables have a nil value by default.

    Example:

    # Ruby program to illustrate Instance Variables
    
    class Book
      def initialize(title, author)
        # Instance variables
        @title = title
        @auth_name = author
      end
    
      def display_info
        puts "Title: #{@title}"
        puts "Author: #{@auth_name}"
      end
    end
    
    # Creating objects
    book1 = Book.new("Ruby Programming", "John Doe")
    book2 = Book.new("Learning Ruby", "Jane Doe")
    
    # Calling methods
    book1.display_info
    book2.display_info

    Output:

    Title: Ruby Programming
    Author: John Doe
    Title: Learning Ruby
    Author: Jane Doe

    3. Class Varia: Class variables start with @@ and are shared across all instances of a class. They belong to the class itself rather than any individual instance. Class variables must be initialized before they are used. An uninitialized class variable will raise an error.

    Example:

    # Ruby program to illustrate Class Variables
    
    class Library
      # Class variable
      @@book_count = 0
    
      def initialize(title)
        # Instance variable
        @title = title
      end
    
      def display_title
        puts "Book Title: #{@title}"
      end
    
      def add_book
        # Increment the class variable
        @@book_count += 1
        puts "Total books: #@@book_count"
      end
    end
    
    # Creating objects
    book1 = Library.new("The Art of Ruby")
    book2 = Library.new("Ruby on Rails Guide")
    
    # Calling methods
    book1.display_title
    book1.add_book
    
    book2.display_title
    book2.add_book

    Output:

    Book Title: The Art of Ruby
    Total books: 1
    Book Title: Ruby on Rails Guide
    Total books: 2

    4. Global Variables: Global variables start with a $ sign and are accessible from anywhere in the Ruby program. They are not limited to a specific class or scope. By default, an uninitialized global variable has a nil value. However, excessive use of global variables can make code difficult to debug and maintain.

    Example:

    # Ruby program to illustrate Global Variables
    
    # Global variable
    $global_count = 5
    
    class FirstClass
      def display_count
        puts "Global count in FirstClass: #$global_count"
      end
    end
    
    class SecondClass
      def display_count
        puts "Global count in SecondClass: #$global_count"
      end
    end
    
    # Creating objects
    obj1 = FirstClass.new
    obj2 = SecondClass.new
    
    # Calling methods
    obj1.display_count
    obj2.display_count

    Output:

    Global count in FirstClass: 5
    Global count in SecondClass: 5

    Global Variable in Ruby

    Global variables have a global scope, meaning they are accessible from anywhere in a program. Modifying global variables from any point in the code has implications across the entire program. Global variables are always denoted with a dollar sign ($). If a variable needs to be shared across multiple classes, defining it as a global variable ensures it can be accessed from all classes. By default, an uninitialized global variable has a nil value, which can lead to confusing and complex code. Global variables can be modified from any part of the program.

    Syntax:

    $global_variable = 5

    Example:

    # Ruby program demonstrating global variables
    
    # Defining a global variable
    $global_variable = 20
    
    # Defining first class
    class FirstClass
      def display_global
        puts "Global variable in FirstClass is #$global_variable"
      end
    end
    
    # Defining second class
    class SecondClass
      def display_global
        puts "Global variable in SecondClass is #$global_variable"
      end
    end
    
    # Creating instances of both classes
    first_instance = FirstClass.new
    first_instance.display_global
    
    second_instance = SecondClass.new
    second_instance.display_global

    Output:

    Global variable in FirstClass is 20
    Global variable in SecondClass is 20

    In the above example, a global variable is defined with the value 20, and it can be accessed in both classes.

    Another Example:

    # Ruby program to demonstrate global variables across methods and classes
    
    $global_variable1 = "Hello"
    
    class SampleClass
      def show_global_in_instance_method
        puts "Global variables are accessible here: #{$global_variable1}, #{$another_global_var}"
      end
    
      def self.set_global_in_class_method
        $another_global_var = "World"
        puts "Global variables are accessible here: #{$global_variable1}"
      end
    end
    
    # Using the class method
    SampleClass.set_global_in_class_method
    
    # Creating an object and using the instance method
    sample_object = SampleClass.new
    sample_object.show_global_in_instance_method

    Output:

    Global variables are accessible here: Hello
    Global variables are accessible here: Hello, World

    Literal

    In Ruby, a literal is any constant value that can be assigned to a variable. We use literals whenever we type an object directly into the code. Ruby literals are similar to those in other programming languages, with some differences in syntax and functionality.

    Types of Ruby Literals

    Ruby supports various types of literals:

    1. Booleans and nil
    2. Numbers
    3. Strings
    4. Symbols
    5. Ranges
    6. Arrays
    7. Hashes
    8. Regular Expressions

    1. Booleans and nil: Booleans are constants that represent the truth values true and falsenil is another constant that represents an “unknown” or “empty” value, and it behaves similarly to false in conditional expressions.

    Example:

    # Boolean literals demonstration
    puts(5 + 2 == 7)  # true
    puts(5 - 3 != 2)  # false
    puts(nil == false)  # false

    Output:

    true
    false
    false

    2. Numbers: Ruby supports different types of numbers, including integers and floating-point numbers. You can write numbers of any size, using underscores (_) for readability. Ruby allows various numerical formats, including decimal, hexadecimal, octal, and binary.

    Example:

    # Number literals demonstration
    puts("Sum: ", 100 + 2_00 + 300)  # underscores for readability
    puts("Hexadecimal:", 0x1A)  # hexadecimal
    puts("Octal:", 0o56)  # octal
    puts("Decimal:", 123)  # decimal
    puts("Binary:", 0b1101)  # binary
    puts("Float:", 1.234E2)  # scientific notation

    Output:

    Sum: 600
    Hexadecimal: 26
    Octal: 46
    Decimal: 123
    Binary: 13
    Float: 123.4

    3. Strings: Strings in Ruby can be enclosed in either double quotes (" ") or single quotes (' '). Double-quoted strings support interpolation and special characters, while single-quoted strings only support limited escape sequences.

    Example:

    # String literals demonstration
    puts("The result of 3 + 4 is: #{3 + 4}")  # string interpolation
    puts("Hello\nWorld")  # new line in double quotes
    puts('Hello\nWorld')  # no new line in single quotes

    Output:

    The result of 3 + 4 is: 7
    Hello
    World
    Hello\nWorld

    4. Symbols: symbol in Ruby is a lightweight, immutable string used as an identifier. Symbols are created using a colon (:) and are never garbage-collected, making them memory-efficient for repeated use.

    Example:

    # Symbol demonstration
    puts(:user_id)  # simple symbol
    puts(:"user#{42}")  # symbol with interpolation

    Output:

    user_id
    user42

    5. Ranges: Ranges represent a set of values between a starting and an ending point. They can include numbers, characters, or other objects. Ranges can be constructed using the .. (inclusive) or ... (exclusive) operators.

    Example:

    # Range literals demonstration
    for i in 1..4 do
      puts(i)
    end
    
    puts("Exclusive range:", (1...4).to_a)

    Output:

    1
    2
    3
    4
    Exclusive range: [1, 2, 3]

    6. Arrays: An array is a collection of objects in Ruby, enclosed within square brackets ([ ]). Arrays can contain objects of different data types, and elements are separated by commas.

    Example:

    # Array demonstration
    fruits = ['apple', 'banana', 'cherry']
    puts(fruits[0])  # accessing the first element
    puts(fruits[1..2])  # accessing a range of elements
    puts("Negative index:", fruits[-1])  # accessing the last element

    Output:

    apple
    banana
    cherry
    Negative index: cherry

    7. Hashes: hash is a collection of key-value pairs, similar to dictionaries in other languages. Hashes are defined using curly braces ({ }), and symbols can be used as keys for efficiency.

    Example:

    # Hash demonstration
    person = { name: "Alice", age: 30, city: "Wonderland" }
    
    # Accessing hash elements
    puts(person[:name])
    puts(person[:age])
    
    # Iterating over the hash
    person.each do |key, value|
      puts("#{key} => #{value}")
    end

    Output:

    Alice
    30
    name => Alice
    age => 30
    city => Wonderland

    8. Regular Expressions: Ruby supports regular expressions (regex) for pattern matching. Regular expressions are defined using forward slashes (/pattern/) or %r{pattern}.

    Example:

    # Regular expression demonstration
    line1 = "The quick brown fox"
    line2 = "Jumps over the lazy dog"
    
    # Checks if 'quick' is in line1
    if (line1 =~ /quick/)
      puts line1
    end
    
    # Checks if 'lazy' is in line2 using %r{} format
    if (line2 =~ %r{lazy})
      puts line2
    end
    
    # Checks if 'rabbit' is in line2
    if (line2 =~ /rabbit/)
      puts line2
    else
      puts "No match found"
    end

    Output:

    The quick brown fox
    Jumps over the lazy dog
    No match found

    Ruby Directories

    Working with Directories in Ruby

    directory is a location where files can be stored. In Ruby, the Dir class and the FileUtils module provide various methods for managing directories, while the File class handles file operations. Double dot (..) refers to the parent directory, and single dot (.) refers to the current directory itself.

    The Dir Class

    The Dir class allows access to and manipulation of directory structures in Ruby. It includes methods for listing directory contents, creating directories, changing directories, and more.

    Features of the Dir Class

    1. Creating Directories: The mkdir method is used to create a new directory. It returns 0 if the directory is successfully created.

    Syntax:

    Dir.mkdir "directory_name"

    Examples:

    # Creating a directory named "my_folder"
    result = Dir.mkdir "my_folder"
    
    # Output the result
    puts result

    Output:

    2. Checking Directories: The exist? method checks if a directory exists.

    Syntax:

    Dir.exist? "directory_name"

    Example:

    # Create a directory named "my_folder"
    Dir.mkdir("my_folder")
    
    # Check if the directory exists
    puts Dir.exist?("my_folder")

    Output:

    true
    • The empty? method checks if a directory is empty.

    Syntax:

    Dir.empty? "directory_name"

    3. Working with Directories

    • The new method creates a new directory object (the directory should already exist).

    Syntax:

    obj = Dir.new("directory_name")
    • The pwd method returns the current working directory.

    Syntax:

    Dir.pwd

    Example:

    # Create a directory named "example_folder"
    Dir.mkdir("example_folder")
    
    # Print the current working directory
    puts Dir.pwd

    Output:

    /path/to/current/directory
    • The home method returns the home directory of the current user.

    Syntax:

    Dir.home

    Example:

    # Print the home directory
    puts Dir.home

    Output:

    /Users/username
    • The path method returns the path of a directory object.

    Syntax:

    d = Dir.new("directory_name")
    d.path

    Output:

    Odd

    Example:

    # Create a directory and get its path
    Dir.mkdir("example_folder")
    obj = Dir.new("example_folder")
    puts obj.path

    Output:

    example_folder
    • The getwd method returns the path of the current directory.

    Syntax:

    Dir.getwd

    Example:

    /path/to/current/directory
    • The chdir method changes the current working directory.

    Syntax:

    Dir.chdir("directory_name")

    Example:

    # Create directories
    Dir.mkdir("/workspace/folder1")
    Dir.mkdir("/workspace/folder2")
    
    # Change to folder2
    Dir.chdir("/workspace/folder2")
    puts Dir.pwd

    Output:

    /workspace/folder2
    • The entries method lists all files and folders in a directory.

    Syntax:

    Dir.entries("directory")

    Example:

    # Create a directory and subdirectories
    Dir.mkdir("main_folder")
    Dir.chdir("main_folder")
    Dir.mkdir("subfolder1")
    Dir.mkdir("subfolder2")
    
    # List all files and folders
    puts Dir.entries(".")

    Output:

    .
    ..
    subfolder1
    subfolder2
    • The glob method lists files matching a certain pattern.

    Syntax:

    Dir.glob("pattern")

    Example:

    # Create files and folders
    Dir.mkdir("sample_folder")
    Dir.chdir("sample_folder")
    Dir.mkdir("test")
    Dir.mkdir("demo.rb")
    Dir.mkdir("script.rb")
    
    # List files matching patterns
    puts Dir.glob("*.rb")

    Output:

    demo.rb
    script.rb

    4. Removing Directories: Methods like rmdirdelete, and unlink are used to remove directories.

    Syntax:

    Dir.delete "directory_name"
    Dir.rmdir "directory_name"
    Dir.unlink "directory_name"

    Example:

    # Create a directory
    Dir.mkdir("temp_folder")
    puts Dir.exist?("temp_folder")
    
    # Remove the directory
    Dir.rmdir("temp_folder")
    puts Dir.exist?("temp_folder")

    Output:

    true
    false

    5. Creating Nested Directories: The mkdir_p method from the FileUtils module creates a directory and all its parent directories.

    Syntax:

    FileUtils.mkdir_p 'directory_path'

    Example:

    require "fileutils"
    
    # Create nested directories
    FileUtils.mkdir_p "parent/child/grandchild"
    
    # Check existence
    Dir.chdir("parent/child")
    puts Dir.exist?("grandchild")

    Output:

    true

    6. Moving Files and Folders: The mv method from the FileUtils module is used to move files or directories.

    Syntax:

    FileUtils.mv("source", "destination")

    Example:

    require "fileutils"
    
    # Create directories
    Dir.mkdir "source_folder"
    Dir.mkdir "destination_folder"
    
    # Move source_folder into destination_folder
    FileUtils.mv("source_folder", "destination_folder")
    
    # Check if the move was successful
    Dir.chdir("destination_folder")
    puts Dir.exist?("source_folder")

    Output:

    true

    7. Copying Files: The cp method from the FileUtils module copies files from one location to another.

    Syntax:

    FileUtils.cp("source", "destination")

    Example:

    require "fileutils"
    
    # Copy file.txt from source_folder to destination_folder
    FileUtils.cp("source_folder/file.txt", "destination_folder")
    
    # Check if the file exists in the destination
    Dir.chdir("destination_folder")
    puts File.exist?("file.txt")

    Output:

    true