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:
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.-
private
: Methods declared using theprivate
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. -
protected
: A method declared using theprotected
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. -
module_function
: In a module (Module), you can usemodule_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. -
private_class_method
: This modifier is used to make the class method private so that it cannot be accessed directly from outside the class. -
singleton
(singleton method): Although this is not a true visibility modifier, it can be achieved by defining a method outside the class definition and usingself.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:
- Code Reuse: Modules can encapsulate some commonly used functions and then reuse them in multiple classes.
- 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.
- 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:
- Control flow: Blocks can be used to implement loop structures and conditional statements.
- Callback mechanism: Blocks can be used as callback functions, which can be called to perform specific operations when an event occurs.
- 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:
- 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
- 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:
- Get class information: such as class name, ancestor chain, module mix, etc.
- Access the properties and methods of the object: including private variables and methods.
- Dynamic creation of classes and modules: New classes or modules can be created at runtime.
- Modify existing classes and objects: You can dynamically add methods to classes, or modify the behavior of existing methods.
The functions of reflection include:
- Metaprogramming: Through reflection, we can write code to generate other code, or change the behavior of the code at runtime.
- 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.
- 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
- 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
- Use the
String#match
method to match and return aMatchData
object. If there is no match, returnnil
.
string = "Hello, world!"
match_data = string.match(/world/)
puts match_data.inspect # output: #<MatchData "world" 1:nil>
- Use the
Regexp#match
method, which is similar toString#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
- 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!
- 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.
- 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
.
- 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:
- Dynamic code generation: New classes, methods, properties, etc. can be generated as needed during runtime.
- Simplify the code: Metaprogramming can eliminate repetitive code patterns and improve the simplicity and maintainability of the code.
- Enhanced flexibility: Metaprogramming techniques can make the code more adaptable to changing requirements, thereby providing greater flexibility.
- 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.