Code360 powered by Coding Ninjas X Naukri.com. Code360 powered by Coding Ninjas X Naukri.com
Table of contents
1.
Introduction
2.
Thread Safety With Synchronised Blocks
3.
Tracing
4.
Tracing Files Loaded and Classes Defined
5.
Example for Tracing Files Loaded
6.
Frequently Asked Questions
6.1.
Define Ruby’s Reflection API.
6.2.
What is metaprogramming?
6.3.
What are domain-specific languages?
6.4.
Where are states stored?
6.5.
Which option loads the specified library before running the program?
7.
Conclusion
Last Updated: Mar 27, 2024
Medium

Tracing Files Loaded and Classes Defined in Ruby

Author Prachi Singh
0 upvote

Introduction

Ruby’s reflection API—its generally dynamic nature, blocks-and-iterators control structures, and parentheses-optional syntax—make it an ideal language for metaprogramming. Loosely defined, metaprogramming is writing programs (or frameworks) that help you write programs. 

To put it another way, metaprogramming is a set of techniques for extending Ruby’s syntax in ways that make programming easier. Metaprogramming is closely tied to the idea of writing domain-specific languages, or DSLs. DSLs in Ruby typically use method invocations and blocks as if they were keywords in a task-specific extension to the language.

Thread Safety With Synchronised Blocks

When writing programs that use multiple threads, two lines mustn't attempt to modify the same object simultaneously. One way to do this is to place the code that must be made thread-safe in a block associated with a call to the synchronize method of a Mutex object. We take this further and emulate Java's synchronized keyword with a global method named synchronized. This synchronized method expects a single object argument and a block. It obtains a Mutex associated with the object and uses Mutex. Synchronize to invoke the block. The tricky part is that Ruby's things, unlike Java's objects, do not have a Mutex.

Tracing

Ruby defines several features for tracing the execution of a program. These are mainly useful for debugging code and printing informative error messages. The simplest features are exact language keywords: __FILE__ and __LINE__. 

These keyword expressions always evaluate the name of the file and the line number within that file on which they appear, and they allow an error message to specify the exact location at which it was generated: STDERR.puts "#{__FILE__}:#{__LINE__): invalid data" As an aside, note that the methods Kernel.eval, Object.instance_eval, and Module.class_eval all accept a filename (or other string) and a line number as their final two arguments. If you are evaluating code you have extracted from a file, you can use these arguments to specify the values of __FILE__ and __LINE__ for the evaluation. 

You have undoubtedly noticed that when an exception is raised and not handled, the error message printed to the console contains the filename and line number information. This information is based on __FILE__ and __LINE__, of course. Every Exception object has a backtrace that shows where it was raised, where the method that raised the Exception was invoked, where that method was invoked, and so on. The backtrace method returns an array of strings containing this information. The first element of this array is the location at which the Exception occurred, and each subsequent element is one stack frame higher.

Tracing Files Loaded and Classes Defined

When the program exits, it prints a report. You can use this code to “instrument” an existing program to understand better what it is doing. One way to use this code is to insert this line at the beginning of the program: 

require 'class trace.'


A more straightforward solution, however, is to use the -r option to your Ruby interpreter: ruby -rclasstrace my_program.rb --trace out /tmp/trace.The -r option loads the specified library before running the program.

Example for Tracing Files Loaded

# We define this module to hold the global state we require so that
 # we don't alter the global namespace more than necessary.
 module ClassTrace
 # This array holds our list of files loaded and classes defined.
 # Each element is a subarray holding the class defined or the 
# file loaded and the stack frame where it was defined or loaded.
 T = [] # Array to hold the files loaded
 # Now define the constant OUT to specify where tracing output goes. 
# This defaults to STDERR, but can also come from command-line arguments
 if x = ARGV.index("--traceout") # If argument exists 
OUT = File.open(ARGV[x+1], "w") # Open the specified file 
ARGV[x,2] = nil # And remove the arguments
 else 
OUT = STDERR # Otherwise default to STDERR 
end 

# Alias chaining step 1: define aliases for the original methods 
alias original_require require 
alias original_load load 
# Alias chaining step 2: define new versions of the methods 
def require(file) 
ClassTrace::T << [file, caller[0]] # Remember what was loaded where 
original_require(file) # Invoke the original method 
end 
You can also try this code with Online Ruby Compiler
Run Code


The above example uses static alias chaining to trace all invocations of the Kernel.require and Kernel.load methods. It defines an Object. Inherited hook to track definitions of new classes. And it uses Kernel.at_exit to execute a block of code when the program terminates.  Besides, alias chaining requires and load and defining Object. Inherited, the only modification to the global namespace made by this code is the definition of a module named ClassTrace.

All state-required for tracing are stored in constants within this module so that we don’t pollute the namespace with global variables.

Check out this article - Balanced Parentheses

Frequently Asked Questions

Define Ruby’s Reflection API.

Ruby’s reflection API—its generally dynamic nature, blocks-and-iterators control structures, and parentheses-optional syntax—makes it an ideal language for metaprogramming. 

What is metaprogramming?

Metaprogramming is a set of techniques for extending Ruby’s syntax in ways that make programming easier. 

What are domain-specific languages?

DSLs in Ruby typically use method invocations and blocks as if they were keywords in a task-specific extension to the language.

Where are states stored?

All state-required for tracing are stored in constants within this module so that users don’t pollute the namespace with global variables.

Which option loads the specified library before running the program?

-r option loads the specified library before running the program.

Conclusion

After reading about loaded and classes defined tracing files, are you not feeling excited to read/explore more articles on metaprogramming? Don't worry; Coding Ninjas has you covered.

However, you may want to pursue our premium courses to give your job an advantage over the competition!

With our Coding Ninjas Studio Guided Path, you may learn about Data Structures and Algorithms, Competitive Programming, JavaScript, System Design, and more! Check out the mock test series and participate in the contests on Coding Ninjas Studio if you want to put your coding talents to the test! However, if you've just started school and are looking for answers to concerns raised by digital behemoths such as Amazon, Microsoft, Uber, and others. As part of your placement preparations, you must evaluate the obstaclesinterview experiences, and interview package in this case. Please vote for our blogs if you find them valuable and exciting.

Happy studying!!

Live masterclass