100 Core Ruby Interview Questions

Ruby is a dynamic, open-source programming language that focuses on simplicity and productivity. Exceptionally user-friendly, it is widely used to develop web applications and systems scripting. This blog post comprises various Ruby interview questions and answers, focusing on the language’s concepts, capabilities, and data structures. Practical for potential job applicants, it provides useful insight into how well a candidate understands Ruby and its applications, including object-oriented programming, error handling, and testing techniques. In technical interviews, proficiency in Ruby allows for a broad examination of an applicant’s programming acumen.

Content updated: January 1, 2024

Ruby Fundamentals


  • 1.

    What is Ruby, and why is it popular for web development?

    Answer:

    Ruby is a dynamic, object-oriented programming language known for its simplicity and focus on developer productivity. Its main claim to fame in web development is the web application framework, Ruby on Rails (RoR), which transformed the way web applications are built by promoting convention over configuration.

    Key Features & Contributions to Web Development

    • Language Syntax: Ruby’s syntax has an appeasing natural language style. This, paired with its dynamic typing, powerful metaprogramming features, and absence of semicolons, results in clean and expressive code.

    • Gems: Ruby’s package manager, RubyGems, simplifies library management, making it easy to integrate numerous third-party extensions.

    • Database Integration: ActiveRecord, a popular object-relational mapping system, aids in managing database records via a natural, object-oriented interface.

    • MVC Pattern: Rails, in particular, is famed for its adherence to the Model-View-Controller pattern, offering a clear separation of concerns.

    • Code Rearrangement: The auto-loading mechanism allows for seamless navigation between related files and classes while coding.

    • Ecosystem Consistency: RoR brought about a many-to-many relationship with databases, streamlining and simplifying existing development patterns.

    • Strong Community: The language’s supportive community and its commitment to clean, readable code are evident in guiding principles like “Mediterranean” quality and “Matz’s kindness.”

    • Test-Driven Development: RoR promotes best-testing practices from the project’s inception, ensuring reliability.

    • Giant Corporations’ Indulgence: Prominent organizations such as GitHub, Shopify, and Airbnb have successfully tapped into the potential of Ruby on Rails.

    Code Example: Ruby on Rails (RoR) Routing

    Here is the Ruby code:

    # config/routes.rb
    Rails.application.routes.draw do
      root 'welcome#index'
      get 'products/:id', to: 'products#show'
      resources :articles
    end
    

    This file configures routes for different URLs, specifying which controllers and actions to invoke. For instance, upon receiving a GET request for products/5, RoR would route it to the show action in the ProductsController with the ID parameter set to 5. Such straightforward setups contribute to RoR’s appeal.

  • 2.

    How do you create a Ruby script file and execute it on a command line?

    Answer:

    First, you create a Ruby script file with a .rb extension that contains your Ruby code. You can then execute this script using the ruby command in your command line.

    Basic Steps for Creating and Running a Ruby Script in a File

    1. Create a File: Use any text editor to write your Ruby code, and save the file with a .rb extension, e.g., my_ruby_script.rb.

    2. Write Ruby Code: Here is a simple example.

      # Filename: my_ruby_script.rb
      puts "Hello, Ruby Script!"
      
    3. Run the Ruby Script: Go to your command line and navigate to the folder where the Ruby file is saved. Then, type the following command:

      ruby my_ruby_script.rb
      

      After pressing enter, you will see the output:

      Hello, Ruby Script!
      

    Getting More Advanced

    Command-Line Arguments

    You can access command-line arguments using special variables called ARGV.

    Here is the code:

    # Filename: script_with_args.rb
    puts "Arguments: #{ARGV.join(', ')}"
    

    In the command line, you would run this script as:

    ruby script_with_args.rb arg1 arg2 arg3
    

    The output would be:

    Arguments: arg1, arg2, arg3
    

    Interactive Scripts

    Ruby scripts can engage with users using the gets method.

    Here is an example:

    # Filename: interactive_script.rb
    puts "What is your name?"
    name = gets.chomp
    puts "Hello, #{name}!"
    

    When you run this script using ruby interactive_script.rb, it will prompt you for your name, and after you provide it, it will greet you.

    Background Processing

    If you want a script to run in the background without blocking your command line, you can use the & character.

    For instance, to run a script called background_script.rb in the background, you can use:

    ruby background_script.rb &
    

    Ruby Shell

    For more complex shell operations, Ruby offers the shell library.

    Here is the sample code:

    require 'shell'
    
    # Use 'open' to open a URL in your default browser.
    sh = Shell.new
    sh.open "https://example.com"
    
  • 3.

    What are the basic data types in Ruby?

    Answer:

    Ruby is claimed to treat “everything as an object”. But like many languages, Ruby has both primitive and abstract data types.

    Primitive Types

    • Numbers:
      • Integers can be of any size (limited by system memory).
      • Floating-Point numbers follow the IEEE 754 standard.
    • Booleans: Represented by true and false.
    • Symbols: Unique, immutable identifiers represented with a : followed by a name.

    Abstract Types

    • Strings: Unicode with multiple encodings.
    • Arrays: Ordered, indexed collections.
    • Hashes: Key-value pairs, also known as dictionaries or maps in other languages.

    Others assimilated Primitive Types

    Ruby, despite its philosophy of being completely object-oriented, has some underlying primitive paradigms due to its performance concerns and efficiency considerations.

    • nil: Represents ‘nothing’ or ‘empty’. It’s the only instance of NilClass.

    • Booleans: While true and false are themselves keywords, any other value in Ruby is considered truthy in a conditional context.

    Advanced Types

    • Rational Numbers: Represented as a fraction (e.g., 1/3r).
    • Complex Numbers: Have real and imaginary parts (e.g., 2 + 3i).
    • Dates and Times: Provide various built-in classes like Time for dealing with date and time values.

    Ruby Uniqueness

    Ruby shuns a “strictly-typed” system. Variables need not be declared upfront and can be reassigned to different types during execution. This freedom, although liberating, can lead to unexpected behavior, especially in larger codebases.

  • 4.

    Explain the difference between symbols and strings in Ruby.

    Answer:

    Ruby features both strings and symbols, each with distinct use cases.

    Key Distinctions

    • Type: Strings are of class String, while symbols are instances of Symbol.
    • Mutability: Strings are mutable, symbols are not.
    • Memory: Symbols are stored as a single, unique object in memory, while each string is unique.
    • Performance: As symbols are immutable, lookups are faster than for equivalent strings.

    Primary Usages

    • Strings: For text and dynamic data that may change or be unique across different objects or occurrences.
    • Symbols: Typically used as keys for hashes or unique identifiers in the program. They’re advantageous for lookup efficiency and when the actual content of the identifier is less relevant than its unique identity.

    Memory Considerations

    • As symbols are stored only once in memory, they are memory-efficient in certain scenarios, like using the same symbol across different objects or operations. Be cautious, though, as unnecessarily creating a large number of symbols can lead to memory bloat.
    • Strings may be more memory-intensive, especially when there are numerous unique strings. However, they are the right choice when dealing with data that genuinely varies or where mutability is required.

    Code Example: String vs Symbol

    Here is the Ruby code:

    # Strings
    str_1 = "Hello"
    str_2 = "Hello"
    puts str_1.object_id == str_2.object_id  # Output: false
    
    # Symbols
    sym_1 = :hello
    sym_2 = :hello
    puts sym_1.object_id == sym_2.object_id  # Output: true
    
  • 5.

    How are constants declared and what is their scope in Ruby?

    Answer:

    In Ruby, you declare a constant by using all uppercase letters. Constants are subject to lexical scoping. While reassignment is technically possible (spawning a warning), it should be avoided as a practice.

    Constant Declaration

    You can declare a Ruby constant using Object::CONSTANT notation or by assigning a value directly to an identifier.

    Code Example: Constant Declaration

    # Using Object::CONSTANT notation
    Math::PI 
    
    # Direct assignment
    RADIUS = 5.0
    

    Constant Scope

    Constants have a global scope, but their visibility can be restricted within classes and modules.

    Global VS. Local Scope

    • Global Scope: Constants are accessible throughout the entire application.

      A = 1     # Top level
      module M
        puts A  # Outputs: 1
      end
      
    • Local Scope: Constants are defined within a module or a class.

      module M
        A = 2
        A = 3
        puts A  # Outputs: 3
      end
      

    Best Practices

    • Avoid re-assigning constants. Although this generates a warning, the reassignment can still take place, which can lead to unintended behavior.
    • For areas where you want to have a constant’s value remain unchanged, use .freeze on the constant or variable storing the constant’s value.

    Code Example: Avoiding Reassignment

    require "warning"
    
    # Generates a warning: already initialized constant
    A = 1
    A = 2 
    
    warning 'constant reassignment'
    
    puts A  # Outputs: 2
    

    Object#freeze

    CIRCLE_AREA = Math::PI * (RADIUS ** 2)
    CIRCLE_AREA.freeze
    
    # Any reassignment will generate an error
    # CIRCLE_AREA = 100 
    
    puts CIRCLE_AREA
    
  • 6.

    Explain the use of ‘require’ and ‘include’ in Ruby.

    Answer:

    Ruby uses both Require and Include to manage dependencies and to mix modules into classes.

    Require

    • Purpose: Loads external libraries, enabling access to their defined classes and modules.

    • Execution: Done at the top of the file or script.

    • Trigger: A LoadError is raised if the required file is not found.

    • State Management: Tracks loaded libraries, subsequently ignoring further require calls for the same library.

    Example: Using Require

    Here is the Ruby code:

    # In file application.rb
    require 'my_library'
    
    # In file my_library.rb
    class MyLibrary
      # ...
    end
    

    Include

    • Purpose: Integrates a module’s methods within a class, giving the class access to those methods.

    • Execution: On the specific class that necessitates the module’s functionality.

    • State: Not applicable for classes, as they can include multiple modules.

    Why is it Used?

    • Require: Ensures the presence of the external library before continuing, a basic necessity for external code.
    • Include: Mixes in module functionality only when needed, aligning with Rails’ convention of using it in the classes contextually.
  • 7.

    What are Ruby iterators and how do they work?

    Answer:

    When it comes to Ruby, iterators allow for easy, streamlined data manipulation. Whether you’re working with arrays, ranges, or other data structures, iterators help you efficiently apply operations to each element without needing to manage loop counters.

    Most Common Ruby Iterators

    • Each: The most basic iterator, it goes through each element.
    • Each with index: Similar to each, but it also gives the index of the current element.

    Code Example: Each & Each with Index

    Here is the Ruby code:

    arr = [5, 4, 3, 2, 1]
    
    # Iterating with Each
    arr.each { |num| puts num }
    
    # Output:
    # 5
    # 4
    # 3
    # 2
    # 1
    
    # Iterating with Each with Index
    arr.each_with_index { |num, index| puts "#{index}: #{num}" }
    
    # Output:
    # 0: 5
    # 1: 4
    # 2: 3
    # 3: 2
    # 4: 1
    

    Common Usage

    • Each Char: Often used with strings, this iterator loops through each character.
    • Each Line: Handy for reading files, it processes lines one at a time.

    Code Example: Each Char & Each Line

    Here is the Ruby code:

    str = "Hello, World!"
    
    # Iterating Each Character
    str.each_char { |char| puts char }
    
    # Output:
    # H
    # e
    # l
    # l
    # o
    # ,
    # ...
    
    File.open('example.txt').each_line do |line|
      puts line
    end
    

    Predicative Iterators

    These iterators select elements from a collection that match specific conditions. They are typically used in combination with blocks.

    Examples include select, reject, and grep. Each is designed to achieve specific selection goals:

    • select returns elements that yield true in the block.
    • reject returns elements that yield false in the block.
    • grep returns elements that match a specified pattern.

    Code Example: select, reject, and grep

    Here is the Ruby code:

    # Select even numbers
    numbers.select { |num| num.even? }
    
    # Reject short names
    names.reject { |name| name.length < 5 }
    
    # Grep to find email addresses
    text = "Email me at user@example.com"
    text.grep(/\b\w+@\w+\.\w+\b/)
    

    Chase & Transform

    These iterators process the elements and return a result. They include map, collect, and partition.

    • map: Transforms each input and returns a new array.
    • collect: Identical to map, but ops include the return value.
    • partition: Separates elements into two groups based on whether the block returns true or false.

    Code Example: map, collect, and partition

    Here is the Ruby code:

    # Double each number
    numbers.map { |num| num * 2 }
    
    # Names all uppercase
    names.collect { |name| name.upcase }
    
    # Split numbers based on even or odd
    numbers.partition { |num| num.even? }
    

    Execute Operations

    These iterators modify their elements or perform side effects. Examples include each and each_with_index.

    Often used for their simplicity, do exercise caution as these functions can have unexpected results, especially when combined with unintended side effects.

    • each: Processes each element but does not return anything.
    • each_with_index: Similar to each, but also gives the index of the current element.

    Sort-Related Operations

    When working with ordered collections like arrays or ranges, Ruby provides various sorting options. Common sorting iterators include sort, sort_by, and reverse_each. They all work with blocks to customize the sorting or iteration behavior.

    Repetition-Based Iterators

    These Ruby constructs are particularly useful in the context of text manipulation, allowing you to repeat characters (such as hyphens for formatting headers) for a specified number of times.

    • each_line: Useful when processing multi-line strings or files.
    • each_char: Ideal for character-level processing in strings or enumerations.
    • downto: Iterates downwards to a specified value.
    • upto: Iterates upwards to a specified value.
    • times: Repeats the associated block a predetermined number of times.
    • step: Indents a set number of times, confined by a range.
    • cycle: Used primarily with blocks, it repeatedly moves through the specified range.

    Code Example: Repetition-Based Iterators

    Here is the Ruby code:

    # Print a line of stars
    '*'.upto('*****') { |s| puts s }
    
    # Output:
    # *
    # **
    # ***
    # ****
    # *****
    
    # Print numbers from 5 to 10, then their squares
    5.upto(10) { |num| puts num }
    5.upto(10).each { |num| puts num**2 }
    
  • 8.

    How are errors handled in Ruby?

    Answer:

    Ruby’s exception hierarchy enables developers to manage different kinds of errors. The two main exception types cater to a multitude of issues:

    • StandardError: For generic problems that occur during code execution.
    • SystemCallError: Specifically deals with errors originating from system calls.

    Ways to Handle Exceptions in Ruby

    Top-Level Exception Handling

    Ruby leverages the at_exit method for centralized error handling. This approach is mainly useful for logging errors before program exit or for cleaning up resources.

    at_exit do
      puts $!.message if $!
    end
    

    Single Statement Unwind

    Utilize inline rescue, marked by the begin and end block. If an exception arises during the evaluation of the enclosed expression, it’s caught.

    result = begin
      raise StandardError, "This is an error"
    rescue StandardError => e
      "Rescued: #{e.message}"
    end
    
    puts result  # Output: Rescued: This is an error
    

    Custom Exception Handling

    Developers benefit from creating their custom exception classes or identifying specific exception types to tailor their error management strategies.

    Defining Custom Exception Classes

    The Exception class or, more preferably, its subclass, StandardError, are parents to all user-defined exceptions. This inheritance ensures that all custom exceptions are catchable via rescue.

    class MyCustomError < StandardError
      # Additional behavior or settings
    end
    
    raise MyCustomError, "Something went wrong!"
    

    Identifying the Right Exception

    An error’s distinct nature often demands a corresponding exception. For instance, consider a method handling file operations:

    def read_file(file_path)
      raise ArgumentError, "File path is empty" if file_path.to_s.empty?
      raise IOError, "File not found" unless File.exist?(file_path)
    
      File.read(file_path)
    end
    

    Upon calling read_file, any exception correlated to an invalid file path can be reliably caught and addressed with a targeted rescue block.

    Error Handling Best Practices

    • Keep it Precise: Make use of granular rescue blocks or case statements to align the corrective measures with the specific error.

    • Maintain a Balance: Overuse of exceptions can convolute code and hinder its readability. Carefully select the exceptions likely to surface and necessitate special attention.

    • Locale Transparency: Choose either a local exception resolver that terminates in the current method or a global one that percolates up the call stack, but aim for consistency.

    Performance Considerations

    While exceptions can be invaluable for isolated and unexpected mishaps, triggering and managing them incurs a performance cost. It’s typically wiser to leverage them predominantly in such scenarios and not as part of conventional program flow.

  • 9.

    Describe the difference between local, instance, and class variables.

    Answer:

    Let’s set the record straight on the differences between local, instance, and class variables in Ruby.

    Common Features

    All three variable types support:

    • naming: ![A-Za-z0-9_](2, 50)
    • assignment: variable_name = value
    • access control: public, private, and protected
    • immediacy: their scope begins from where they are initialized and exists until the scope ends.

    Local Variables

    • Scope: Limited to the block where they are defined.
    • Life Cycle: Created when the program reaches their definition and destroyed when the block is exited.

    Example: Local Variable

    Here is the Ruby code:

    def hello
      name = "Ruby"
      puts "Hello, #{name}!"  # Output: Hello, Ruby!
    end
    
    # Accessing name outside its defined block will cause an error.
    # puts name  # Will raise an error
    

    Instance Variables

    Naming Convention

    An instance variable is prefixed with a single ‘@’ symbol.

    • Scope: Primarily within the class, but is accessible from outside the class if the class is instantiated.
    • Life Cycle: Created when an object is instantiated and remains available until that particular object is destroyed.

    Example: Instance Variable

    Here is the Ruby code:

    class Greeting
      def set_name(name)
        @name = name
      end
    
      def display_greeting
        puts "Hello, #{@name}!"  # Output: Hello, Ruby!
      end
    end
    
    greeting_instance = Greeting.new
    greeting_instance.set_name("Ruby")
    greeting_instance.display_greeting
    

    Class Variables

    Naming Convention

    A class variable is prefixed with two ‘@’ symbols.

    • Scope: Within the class and its inheritors but not accessible from outside.
    • Life Cycle: Created when assigned within the class or its inheritors and accessible as long as the class or one of its inheritors is in memory.

    Example: Class Variable

    Here is the Ruby code:

    class Employee
      @@company_name = "ABC Corporation"
    
      def self.company_name=(name)
        @@company_name = name
      end
    
      def display_company_name
        puts @@company_name
      end
    end
    
    employee1 = Employee.new
    employee2 = Employee.new
    
    # Output: "ABC Corporation" for both employee1 and employee2.
    employee1.display_company_name
    employee2.display_company_name
    
    Employee.company_name = "New Company"  # changes the class variable
    
    # After changing, outputs for both employee1 and employee2 will be "New Company".
    employee1.display_company_name
    employee2.display_company_name
    
  • 10.

    What are Ruby’s accessor methods?

    Answer:

    In Ruby, accessor methods allow you to manipulate object attributes. There are three types of accessor methods: attr_reader, attr_writer, and attr_accessor, each serving a specific role in the attribute’s lifecycle

    Attribute Methods

    • attr_reader: Generates a simple getter method for an attribute. It can be accessed but not modified externally.
    • attr_writer: Generates a basic setter method. The attribute can be modified but not read externally.
    • attr_accessor: Combines both getter and writer methods in one. This creates a full-fledged getter and setter for the attribute.

    Code Example: Accessor Methods

    Here is the Ruby code:

    
    
    
    class Person
      attr_reader :name, :age
      attr_writer :name, :age
      def initialize(name, age)
        @name = name
        @age = age
      end
    end
    
    person = Person.new("Alice", 30)
    
    person.name # Returns "Alice"
    person.name = "Bob" # Error: undefined method 'name='
    
    person.age # Returns 30
    person.age = 35 # Error: undefined method 'age='
    
    person.instance_variables # Returns [:@name, :@age]
    
    
  • 11.

    How does garbage collection work in Ruby?

    Answer:

    Ruby employs automatic memory management, which is primarily influenced by garbage collection techniques. Let’s understand the specifics.

    Mark-and-Sweep Algorithm

    • Step 1 - Mark: The process starts from the root of object references. The GC traverses memory, marking referenced objects.

    • Step 2 - Sweep: It scans for unmarked objects and reclaims their memory, making it available for future use.

    Generational Garbage Collection

    To optimize the Mark-and-Sweep approach, Ruby introduces generational garbage collection.

    • Focused on Object Age: Objects are classified based on their age.

    • Young vs. Old Objects:

      • New objects start in the Young Generation.
      • Objects that persist multiple GC cycles move to the Old Generation.
    • Collection Frequency: The Young Generation is collected more frequently.

    • Short- and Long-Lived Object Management: It’s easier to collect younger objects, reducing the scope and overhead of a complete garbage collection cycle.

    Reference-Counting and ObjectSpace

    Although CPython uses reference-counting to track object lifespans, Ruby typically does not.

    • ObjectSpace: It’s a module that allows retrieval of all objects.

      However, note that modern Ruby versions represent a hybrid system, sensitive to object types and context.

    Code Example: Garbage Collection in Ruby

    Here is the Ruby code:

    # Enable trashcan (Ruby 2.6 onwards)
    ObjectSpace::count_objects[:FREE] > 100_000 && GC.start
    
    # Ruby versions before 2.6
    GC.start
    
  • 12.

    Explain the difference between ‘gets.chomp’ and ‘gets.strip’.

    Answer:

    Let me go through the major difference.

    Key Distinctions

    • Input Requirement:

      • gets.chomp removes all trailing whitespace and the newline character.
      • gets.strip eliminates all leading and trailing whitespace, including the newline character.
    • Use Cases:

      • gets.chomp: Suited when you anticipate or require specific trailing characters to be preserved.
      • gets.strip: Ideal for scenarios where you need to sanitize or validate user input by removing any extra leading or trailing spaces.

    Code Sample: gets.chomp & gets.strip

    Here is the Ruby code:

    # Using the gets.chomp method
    puts "Enter your name (including a trailing space): "
    name_chomp = gets.chomp
    puts "Name with trailing space: #{name_chomp}"
    
    # Using the gets.strip method
    puts "Enter your name: "
    name_strip = gets.strip
    puts "Name without trailing space: #{name_strip}"
    
  • 13.

    What is the role of ‘self’ in Ruby?

    Answer:

    In Ruby, self serves as a “mirror” that reflects the current context. Depending on where it’s used, self can represent different objects.

    Here’s the breakdown:

    Self in Different Contexts

    1. Instance Methods

    In this context, self refers to the instance of the object on which the method is called.

    Consider the following code:

    class MyClass
      def instance_method
        puts self
      end
    end
    
    object = MyClass.new
    object.instance_method
    

    The output would be the object reference #<MyClass:0x007fb4fa869358>.

    2. Class Methods

    Within a class definition, self refers to the class itself. This is why you use self.method_name to define class methods.

    For instance:

    class MyClass
      def self.class_method
        puts self
      end
    end
    
    MyClass.class_method
    

    The output will be the class MyClass.

    3. Method Invocation

    When a method is missing due to a typo or other reason, Ruby executes method_missing which can help handle such cases.

    Consider this example:

    class MyClass
      def method_missing(m, *args)
        puts "There's no method called #{m}"
      end
    
      def test_method
        method_thaat_doesnt_exist
      end
    end
    

    Calling test_method will invoke method_missing with the method name "method_thaat_doesnt_exist".

  • 14.

    Explain the principle of ‘Convention over Configuration’ in the context of Ruby.

    Answer:

    Convention over Configuration (CoC) is a software design principle that simplifies development by reducing the number of decisions developers need to make.

    In its essence, CoC means that frameworks come with best practice defaults or “conventions” that are automatically applied unless explicitly configured to behave differently.

    Practical Application

    1. Code-Base Structures: Many Ruby web frameworks, like Ruby on Rails or Sinatra, expect a certain directory structure that groups related files.
    2. Naming Conventions: Specially designed rules for naming classes, methods, and databases to help in identification and automatic linking.
    3. API Endpoints: Through consistent naming, it’s possible to infer routing information in web applications.
    4. Database Schemas: Named fields and tables allow the ORM to deduce relationships and configurations.

    Example: CRUD Actions in RoR

    In Ruby on Rails, the “conventions” for a resourceful route automatically map HTTP verbs to CRUD actions:

    # config/routes.rb
    resources :articles
    
    # Routes:
    # HTTP   Path                Controller#Action    Used For
    # ------------------------------------------------------------
    # GET    /articles           articles#index       Display a list
    # GET    /articles/:id       articles#show        Display a specific article
    # GET    /articles/new       articles#new         Display a form to create a new article
    # POST   /articles           articles#create      Add a new article to the database
    # GET    /articles/:id/edit  articles#edit        Display a form to edit an existing article
    # PATCH  /articles/:id       articles#update      Update an existing article in the database
    # PUT    /articles/:id       articles#update      (Alternate for update)
    # DELETE /articles/:id       articles#destroy     Remove a specific article from the database
    

    Here, the convention to map action names to routes frees the developer from configuring each route manually.

    Benefits

    • Speed: It streamlines development and reduces boilerplate.
    • Interoperability: CoC enables consistency across different projects and teams.

    Risks and Challenges

    • Over-optimization: While it’s efficient for simple, well-understood requirements, it can make advanced configurations and customizations cumbersome.
    • Learning Curve: Newcomers might find it challenging to adapt to these standard conventions.
    • Magic: Over-reliance on CoC can make the system seem like it has hidden, unexplained behaviors.
  • 15.

    How does Ruby support metaprogramming?

    Answer:

    Ruby offers powerful metaprogramming capabilities, enabling developers to write flexible, dynamic code. Key to Ruby’s metaprogramming are class methods such as define_method and language features like Open Classes leading to advanced techniques like Dynamic Dispatch.

    Dynamic Dispatch Mechanism

    • Dynamic Dispatch: Methods can be called at runtime, based on the object’s context, using send. This makes it easier to manage method invocations in metaprogrammed code.
    class MathOperations
      def do_operation(operator, x, y)
        send(operator, x, y) # Dynamic dispatch
      end
    
      private
    
      def add(x, y)
        x + y
      end
    
      def subtract(x, y)
        x - y
      end
    end
    
    result = MathOperations.new.do_operation(:add, 10, 5) # 15
    

    Class Modifications with Open Classes

    • Open Classes: Ruby allows changing a class’s definition dynamically, even after its initial declaration.

      This example adds a reverse method to the String class.

      class String
        def reverse
          chars.reverse.join
        end
      end
      

    Code Evaluation and Execution

    • Code Evaluation: Code strings can be executed within a bound context, enabling runtime code execution and evaluation.

      This is an example using eval to define a method at runtime, equivalent to def double(x) x * 2; end, but the method signature is constructed dynamically.

      method_signature = 'double(x)'
      method_body = 'x * 2'
      eval("def #{method_signature}; #{method_body}; end")
      
    • Binding Tasks: proc objects capture both the method (or block) and its associated context. They can be transferred across lexical scopes, allowing delayed execution of code.

      context = binding
      task = Proc.new { eval 'some_method', context }
      
    • Context Toggling: By toggling a method’s visibility, you can control its access scope.

      class MyClass
        def some_method
          "Public method"
        end
      
      private
      
        def toggle_method_visibility(visibility)
          # `send` here is being used for dynamic dispatch
          send(visibility, :some_method)
        end
      end
      
      instance = MyClass.new
      instance.toggle_method_visibility(:private)
      

    Internationalization: Advanced Use of send and eval

    • Localizing Method Calls: In internationalization tasks where method calls need to be localized, send, public_send, or even the more general eval can be suitable.

      def greeting(language)
        eval("#{language}_greeting")
      end
      
      def spanish_greeting
        "Hola Mundo"
      end
      

    Method Missing and Missing Method Feature

    • Method Missing: This feature is the heart of Ruby’s duck typing. It allows classes and objects to respond to method calls even when their definitions are absent, rather than resorting to method-not-found errors.

      This example cleans up a method call, removing spaces or underscores.

      def method_missing(name, *args, &block)
        cleaned_name = name.to_s.delete(' ').delete('_')
        send(cleaned_name, *args, &block)
      end
      
    • respond_to_missing?: This method is often used in conjunction with method_missing, providing a way for a class to communicate whether it handles a method call beyond what is statically defined.

folder icon

Unlock interview insights

Get the inside track on what to expect in your next interview. Access a collection of high quality technical interview questions with detailed answers to help you prepare for your next coding interview.

graph icon

Track progress

Simple interface helps to track your learning progress. Easily navigate through the wide range of questions and focus on key topics you need for your interview success.

clock icon

Save time

Save countless hours searching for information on hundreds of low-quality sites designed to drive traffic and make money from advertising.

Land a six-figure job at one of the top tech companies

amazon logometa logogoogle logomicrosoft logoopenai logo
Ready to nail your next interview?

Stand out and get your dream job

scroll up button

Go up