Back to blog overview

January 10, 2024

Debugging Complex Ruby on Rails Applications

Jesse House

&

&

Senior Software Engineer
Prescott, AZ

# **Debugging Complex Ruby on Rails Applications: Tracing Data Creation**

Debugging complex Ruby on Rails applications can be a daunting task, especially when dealing with intricate data models and their interactions. One common challenge is determining how and where data is being created in your application. This blog post will explore strategies to trace data creation, highlight common issues you might encounter, and discuss an effective technique using `after_initialize` callbacks in ActiveRecord models for debugging.

## **Understanding Data Flow in Rails**

Rails' opinionated framework streamlines many processes, for example, when a user submits a form, how does that data get saved to the database? In most cases you can easily follow the flow of data from the route to the controller to the models being manipulated in the controller to the database, but in more complex applications, it can be difficult to determine how and where data is being created. It is non uncommmon to encounter callbacks, complex nested attributes, has many through associations, third-party gem mixins in your models, implicit invocation of service objects and so on.

## **Tracing Data Creation with `after_initialize`**

A useful technique for uncovering the origins of data creation is to employ `after_initialize` callbacks in your ActiveRecord models. This callback is triggered whenever a new object is instantiated, either from the database or newly created in memory.

### **Helpful Tools**

- **Debuggers:** A good technique to use is the `after_initialize` in conjunction with a debugger. For example, I might add a `binding.break` in the `after_initialize` callback to pause execution and inspect the object in the debugger while running system or request specs. On older versions of rails you might use `byebug` or `binding.pry` instead of `binding.break`.
- **Formatting:** I like to use the [amazing_print](https://github.com/amazing-print/amazing_print) gem or its predecessor [awesome_print](https://github.com/awesome-print/awesome_print) to format the output of `caller(0)` and other ruby objects. This makes it easier to read and analyze the output. Use the `ap` function to format Array, Hash and ActiveRecord objects.

### **Step-by-Step Guide**

1. **Add an `after_initialize` Callback:**
   
   In your ActiveRecord model, add an `after_initialize` callback. This should be a temporary change in development to help you debug the issue. Not recommended for production code.
   

```ruby
class YourModel < ApplicationRecord
 after_initialize :trace_creation

 private

 def trace_creation
   # binding.break
   Rails.logger.debug "#{self.class.name} initialized:"
   ap caller(0) # or just caller(0)
 end
end

```

2. **Inspect the Call Stack:**
   
   The `caller(0)` method returns the current execution stack trace. When the model is instantiated, this will log the trace, helping you to pinpoint where in your application the object is being created. The `caller` method returns an array of strings, each string representing a line in the stack trace. See [Kernel#caller](https://rubydoc.info/stdlib/core/Kernel:caller) for more information.
   
3. **Analyze the Output:**
   
   `caller(0)` will display a list of files and methods that were called leading up to the instantiation of your model. This can provide invaluable insights into the flow of data and pinpoint exactly where objects are being created.
   

```ruby
ap caller(0)
[
   [  0] "/example/app/models/category_widget_log.rb:7:in `block in <class:CategoryWidgetLog>'",
   ...
   [ 18] "/example/app/models/category_widget.rb:12:in `create_category_widget_log'",
   ...
   [ 60] "/example/app/services/widget_manager.rb:6:in `create_widget'",
   [ 61] "/example/app/controllers/widgets_controller.rb:4:in `create'",
   ...
   [129] "/example/spec/requests/widgets_request_spec.rb:12:in `block (4 levels) in <top (required)>'",
   ...
]

```

The stacktrace can be quite long and verbose, so you may want to filter it to relevant lines. For example, you can filter to lines that include `app/` to see only your application code, but be aware that this may filter out important information coming from other gems or libraries.

```ruby
ap caller(0).select { |line| line.include?('app/') }
[
   [0] "/example/app/models/category_widget_log.rb:7:in `block in <class:CategoryWidgetLog>'",
   [1] "/example/app/models/category_widget.rb:12:in `create_category_widget_log'",
   [2] "/example/app/services/widget_manager.rb:6:in `create_widget'",
   [3] "/example/app/controllers/widgets_controller.rb:4:in `create'",
   [4] "/example/spec/requests/widgets_request_spec.rb:12:in `block (4 levels) in <top (required)>'",
]

```

## **Conclusion**

Understanding data flow in a complex Ruby on Rails application is crucial for effective debugging and maintenance. Using `after_initialize` callbacks and inspecting the stack trace can be a powerful method to trace back the origins of data creation.

Let's Chat

Are you ready to build something brilliant? We're ready to help.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
RedwoodJS Logo
RedwoodJS
Conference

conference
for builders

Grants Pass, Oregon • September 26 - 29, 2023
View All