The latest 2023 intermediate-level Ruby interview questions, including answers. A must for brushing up on questions! Record it.

A good memory is not as good as a bad pen

The content comes from the Interview Guide – a collection of intermediate difficulty Ruby interview questions

Question: Please explain what method visibility modifiers are in Ruby and what do they mean?

In Ruby, method visibility modifiers are used to define the visibility and accessibility of a method or variable in classes and subclasses. These modifiers control other code's access to them. The following are the main method visibility modifiers in Ruby:

  1. public: By default, methods will be considered public if no visibility modifier is explicitly specified. This means that the method can be called from anywhere, including outside the class, subclasses, and within the same class.

  2. private: Methods declared using the private modifier can only be accessed within the class in which they are defined. This applies to instance methods of the current class and cannot be accessed from subclasses or external classes.

  3. protected: A method declared using the protected modifier can be accessed within the class in which it is defined and its subclasses, but cannot be accessed from external classes. This means that even if you are not an instance of the class in which the method is defined, you can access it as long as you are an instance of its subclass.

  4. module_function: In a module (Module), you can use module_function to create private methods that can only be accessed within the module scope. Additionally, this method can be called as a “class method” in the module itself.

  5. private_class_method: This modifier is used to make the class method private so that it cannot be accessed directly from outside the class.

  6. singleton (singleton method): Although this is not a true visibility modifier, it can be achieved by defining a method outside the class definition and using self.method_name Similar functionality. This singleton method can only be called through the class itself, not through an instance of the class.

For example, here are examples of how to use these modifiers:

class MyClass
  def public_method
    puts "Public method"
  end

  private
  def private_method
    puts "Private method"
  end

  protected
  def protected_method
    puts "Protected method"
  end
end

my_instance = MyClass.new

# Have access to public methods
my_instance.public_method

# Attempting to access a private method will throwNoMethodErrormistake
my_instance.private_method # throw error

# Protected methods can only be accessed in subclasses or in the same class
class MySubClass < MyClass
  def call_protected
    protected_method
  end
end

MySubClass.new.call_protected # output: Protected method 

Understanding and correctly using these visibility modifiers is crucial to writing clear, easy-to-maintain Ruby code.

Q: What are modules in Ruby and what do they do? Please give an example of how to use the module.

Modules in Ruby are a way of organizing related code together. They are mainly used to encapsulate and reuse functionality. Modules can be included (also called “mixed in” or “mixed in”) within the definition of a class, thereby introducing methods and constants from the module into the class. In addition, modules provide a namespace mechanism that can help avoid name conflicts at the global scope.

The main functions of the module include:

  1. Code Reuse: Modules can encapsulate some commonly used functions and then reuse them in multiple classes.
  2. Namespace: Through modules, we can create methods and constants with specific context to avoid conflicts with methods and constants of the same name defined elsewhere.
  3. Metaprogramming: Modules can also be used to implement some metaprogramming features of Ruby, such as defining methods, modifying class behavior, etc.

Here is a simple example to illustrate how to use modules:

# define a module
module Utility
  # define a method
  def hello
    puts "Hello from the Utility module!"
  end

  # define a constant
  PI = 3.14
end

# Create a class and mix modules
class MyClass
  include Utility
end

# Use methods and constants from mixed-in modules
my_object = MyClass.new
my_object.hello # output: Hello from the Utility module!
puts MyClass::PI # output: 3.14 

In this example, we first define a module named Utility, and define a method hello and a constant PI in it. We then created a class called MyClass and mixed it with the Utility module using the include keyword. In this way, MyClass has all the methods and constants in the Utility module. We can verify this by instantiating MyClass and calling the hello method, or by accessing MyClass::PI directly.

Question: Please explain what blocks in Ruby are and what do they do? Please give an example of how to use blocks.

In Ruby, a block is a piece of code that can be used with methods or iterators. Blocks are usually defined by {} or do...end syntax, and they can accept parameters, perform operations, and return values. The main purpose of a block is to provide a concise way to pass code to a method so that the method can call that code as many times as needed.

The functions of blocks include:

  1. Control flow: Blocks can be used to implement loop structures and conditional statements.
  2. Callback mechanism: Blocks can be used as callback functions, which can be called to perform specific operations when an event occurs.
  3. Data processing: Blocks can be used to traverse a collection (such as an array or hash) and operate on the elements in it.

Here is a simple example to illustrate how to use blocks:

# Ruby of `each` Method can receive a block as parameter
[1, 2, 3].each do |num|
  puts num * 2 # output: 2, 4, 6
end

# Define blocks using curly brace syntax
(1..5).select { |i| i % 2 == 0 } # Return a new array [2, 4]

# Blocks can also have parameters
def print_twice(*args)
  args.each { |arg| puts arg; puts arg }
end

print_twice "Hello", "world" # output:
# Hello
# Hello
# world
# world 

In this example, we show how to use blocks to process each element in an array, and how to pass blocks as arguments to methods. This way we can dynamically change the behavior of our code as needed without explicitly creating new functions or classes.

Question: In Ruby, how do you tell whether an object is an instance of a specific class or its subclass?

In Ruby, you can use the is_a? and kind_of? methods to determine whether an object is an instance of a specific class or its subclass. Both methods return a Boolean value (true or false) indicating whether the given object belongs to the specified class or inherits from that class.

Here's how to use both methods:

  1. Use the is_a? method:
object.is_a?(SomeClass) 

For example:

str = "Hello, World!"
str.is_a?(String)  # return true,because str yes String an instance of class 
  1. Use the kind_of? method:
object.kind_of?(SomeClass) 

This method has the same function as the is_a? method, but has a different name.

str = "Hello, World!"
str.kind_of?(String)  # return true,because str yes String an instance of class 

If you want to check whether an object belongs to a module, you can use the include? method:

object.include?(SomeModule) 

This checks whether the object's class contains the specified module.

Note that none of these methods triggers a method lookup, but instead looks directly into the object's class hierarchy. Therefore, they are often faster and more efficient than calling #class and comparing class names.

Question: Please explain what reflections in Ruby are and what they do? Please give an example of how to use reflection.

Reflection in Ruby is the ability to allow a program to access and manipulate its own metadata at runtime. It mainly includes the following functions:

  1. Get class information: such as class name, ancestor chain, module mix, etc.
  2. Access the properties and methods of the object: including private variables and methods.
  3. Dynamic creation of classes and modules: New classes or modules can be created at runtime.
  4. Modify existing classes and objects: You can dynamically add methods to classes, or modify the behavior of existing methods.

The functions of reflection include:

  1. Metaprogramming: Through reflection, we can write code to generate other code, or change the behavior of the code at runtime.
  2. Debugging and Testing: Reflection can help us gain in-depth understanding of the running program, such as viewing the status of objects, inspecting method call stacks, etc.
  3. Frameworks and libraries: Many frameworks and libraries use reflection to provide more flexible functions, such as route mapping, dependency injection, etc.

Here is a simple example to illustrate how to use reflection:

class MyClass
  def initialize(name)
    @name = name
  end

  private

  def say_hello
    puts "Hello, #{@name}!"
  end
end

# Create an instance
my_object = MyClass.new("Alice")

# use `Object` Category `instance_variable_get` Method to access private variables
puts my_object.instance_variable_get(:@name) # output: Alice

# use `Class` Category `new` Method to dynamically create a new class
MyNewClass = Class.new do
  def say_goodbye
    puts "Goodbye!"
  end
end

# use `Method` Category `new` Method dynamically creates a method,and add it to the existing class
method = Method.new { |name| puts "Hello, #{name}!" }
MyClass.send(:define_method, :say_hello_to, method)

# Call private methods using reflection
my_object.send(:say_hello) # output: Hello, Alice! 

In this example, we show how to use reflection to access and manipulate classes, objects, and their methods and properties. This way we can write code more flexibly and change the behavior of the program at runtime.

Question: How do you handle file I/O operations in Ruby? Please show a piece of code for reading and writing files.

In Ruby, handling file I/O operations mainly involves opening, reading, writing, and closing files. The following is a sample code that uses basic File class methods to read and write files:

# file reading
def read_file(file_path)
  # open a file,Throws an exception if it does not exist
  file = File.open(file_path, "r")

  # Read the file contents line by line using a loop
  content = ""
  while (line = file.gets)
    content += line.chomp + "\n" # chompRemove trailing newline character,Then add back the newlines to keep the formatting
  end

  # close file
  file.close

  # Return file content
  return content
end

# file writing
def write_file(file_path, content)
  # open a file,Clear the content if it exists,Otherwise create new file
  file = File.open(file_path, "w")

  # Write content to file
  file.write(content)

  # close file
  file.close
end

# Example usage:
content = read_file("input.txt")
puts "Read Content:"
puts content

write_file("output.txt", "This is the new content of the output file.")
puts "Content written to 'output.txt'." 

In this code, the read_file function receives a file path as a parameter, opens the file and reads its contents line by line. When the reading is complete, it closes the file and returns the contents of the file.

The write_file function also receives a file path as a parameter, but also requires a string content. This function will open the specified file (clearing the original content if it exists), write the given content to the file, and finally close the file.

Please note that in order to ensure the robustness of your program, you should always ensure that any exceptions that may occur are properly handled in actual programming, such as when a file cannot be opened or written.

Q: How do you use regular expressions to match and replace in Ruby? Please give an example.

In Ruby, regular expressions (Regexp) are a powerful tool for matching, finding, and replacing text strings. Here are some examples using regular expressions:

Matching operation

  1. Use the =~ operator to match and return the matching position index (starting from 0). If no match is found, nil is returned.
string = "Hello, world!"
match_data = /world/ =~ string
puts match_data # output: 7 
  1. Use the String#match method to match and return a MatchData object. If there is no match, return nil.
string = "Hello, world!"
match_data = string.match(/world/)
puts match_data.inspect # output: #<MatchData "world" 1:nil> 
  1. Use the Regexp#match method, which is similar to String#match, but you need to explicitly pass in the string to be matched.
regex = /world/
match_data = regex.match("Hello, world!")
puts match_data.inspect # output: #<MatchData "world" 1:nil> 

Replacement operation

  1. Use the String#gsub method to globally replace matching substrings.
string = "Hello, world!"
new_string = string.gsub(/world/, 'Dolly')
puts new_string # output: Hello, Dolly! 
  1. Use the String#sub method to replace the first matching substring.
string = "Hello, world! World is beautiful."
new_string = string.sub(/world/, 'Dolly')
puts new_string # output: Hello, Dolly! World is beautiful. 
  1. You can use grouping and backreference when replacing.
string = "I love Ruby and Rails"
new_string = string.gsub(/(Ruby)&(\w+)/, '\1 & \2')
puts new_string # output: I love Ruby & Rails 

In this example, we first capture two groups: (Ruby) and (\w+), and then reference them in the replacement string via \1 and \2.

  1. Use named grouping and dynamically generated replacement strings.
string = "My name is John Doe"
new_string = string.gsub(/(?<first_name>\w+)\s+(?<last_name>\w+)/) do |match|
  "#{match[:first_name].capitalize} #{match[:last_name].capitalize}"
end
puts new_string # output: My name is John Doe 

In this example, we define two named groups: (?<first_name> \w+) and (?<last_name> \w+) and then generate a new replacement string based on these grouped values ​​in the block.

The above are some basic examples of matching and replacing using regular expressions in Ruby. In actual applications, these operations can be combined according to needs to implement more complex text processing logic.

Question: Please explain what metaprogramming in Ruby is and what do they do? Please give an example of how to use metaprogramming.

Metaprogramming in Ruby refers to the ability to dynamically create, modify, or delete program structures at runtime. It allows programmers to write code that can change its own behavior, making the code more flexible, scalable, and reducing duplication.

The main functions of metaprogramming include:

  1. Dynamic code generation: New classes, methods, properties, etc. can be generated as needed during runtime.
  2. Simplify the code: Metaprogramming can eliminate repetitive code patterns and improve the simplicity and maintainability of the code.
  3. Enhanced flexibility: Metaprogramming techniques can make the code more adaptable to changing requirements, thereby providing greater flexibility.
  4. Implement DSL (Domain Specific Language): Domain-specific grammar can be constructed through metaprogramming, making the code closer to human natural language and easier to understand and use.

Here are some examples of using Ruby metaprogramming:

Use class_eval to dynamically define methods

class MyClass
  # Dynamically define a method `hello`
  class_eval "def hello; puts 'Hello, World!'; end"
end

obj = MyClass.new
obj.hello  # output "Hello, World!" 

In this example, we use the class_eval method to execute code as a string in the context of the MyClass class, which dynamically adds a method named hello at runtime.

Use define_method to dynamically define methods

class MyClass
  def self.create_method(name)
    define_method(name) do |arg|
      puts "Called #{name} with argument: #{arg}"
    end
  end

  create_method :greet
end

obj = MyClass.new
obj.greet("World")  # output "Called greet with argument: World" 

Here, we use the define_method method to dynamically define a method named greet for the MyClass class, which accepts a parameter and outputs a message when called.

Use attr_accessor to create getters and setters

class Person
  attr_accessor :name
end

person = Person.new
person.name = "Alice"  # dynamically generatedsettermethod
puts person.name       # dynamically generatedgettermethod,output "Alice" 

attr_accessor is a common application of metaprogramming. It actually generates a pair of getter and setter methods for accessing instance variables of an object.

These are just a few basic examples of Ruby metaprogramming. In fact, Ruby provides many other meta-programming mechanisms, such as method_missing, singleton methods, module_eval, etc., allowing developers to customize and extend Ruby's syntax and behavior as needed.

Q: How do you handle exceptions in Ruby? Please show a piece of code with exception handling.

In Ruby, exception handling is mainly implemented through the begin...rescue...end statement. Here's a simple example:

def divide(x, y)
  begin
    result = x / y
  rescue ZeroDivisionError => e
    puts "An error occurred: #{e.message}"
    return nil
  end

  result
end

puts divide(10, 2) # output: 5.0
puts divide(10, 0) # output: An error occurred: divided by 0 

In this example, we define a method called divide which takes two parameters: x and y and attempts to divide them. If a ZeroDivisionError occurs (divide by zero), we will catch this exception, print out the error message, and return nil.

Note that the rescue keyword can be followed by one or more exception class names to catch specific types of exceptions. In this case, we can use rescue ZeroDivisionError to catch divide-by-zero errors. In addition, we can also use rescue Exception to catch all types of exceptions.

When we use =>e in rescue block, e will be assigned as the object that raised the exception. This allows us to access the properties of the exception object, such as message, which contains descriptive text about the exception.

Finally, the code in the begin...rescue...end block will be executed in sequence until an exception is encountered or execution is completed. If an exception occurs and is caught, the code in the rescue block will be executed, otherwise the program will continue executing the code after the begin...rescue...end block.

Q: How do you create classes and objects in Ruby? Please show a piece of code.

In Ruby, we can create a blueprint of an object by defining a class. We can then use this blueprint to create concrete object instances. Here is a simple example:

# Define a file named Person the type
class Person
  # Initialization method,Used to set the initial state
  def initialize(name, age)
    @name = name
    @age = age
  end

  # class methods(instance method)
  def say_hello
    puts "Hello, my name is #{@name} and I am #{@age} years old."
  end
end

# Create Person Object instance of class
person1 = Person.new("Alice", 25)

# Call an instance method of an object
person1.say_hello # output: Hello, my name is Alice and I am 25 years old. 

In this example, we first define a class named Person, and define an initialization method initialize and an instance method say_hello in it. Initialization methods are used to set the initial state of the object, while instance methods define the operations that the object can perform.

Next, we use the Person.new method to create a new Person object instance and assign it to the variable person1. Finally, we execute the object's method by calling person1.say_hello and output the corresponding message.

This is the basic process of creating classes and objects in Ruby. As per the requirement, we can define more methods in the class and create any number of object instances.