I was building a Rails utility class to handle file uploads when I hit a wall. My goal was simple: format file sizes into human-readable strings (like “2 KB” instead of “2048”) using Ruby Rails’ built-in number_to_human_size
helper. But when I tried to call it inside a class method, I got slapped with a NoMethodError
. Here’s how I unraveled the mystery and how you can avoid the same trap.
The Problem: “Why Can’t My Class Method See This Helper?”
I started with this code:
class UploadHelper
include ActionView::Helpers::NumberHelper
def instance_upload
puts "Uploading #{number_to_human_size(123)}" # Works!
end
def self.class_upload
puts "Uploading #{number_to_human_size(123)}" # NoMethodError!
end
end
What happened?
- In
instance_upload
, the helper worked perfectly. - In
self.class_upload
, it crashed becausenumber_to_human_size
was “missing.”
It turns out, this is all about how Ruby shares methods between instances and classes. The include
keyword adds methods to the instances of the class, not to the class itself.
The Fix: Two Workarounds (and Why They Work)
Use an Instance Inside the Class Method
def self.class_upload
# Create an instance to access the helper
puts "Uploading #{new.number_to_human_size(123)}" #
end
Why this works:
include
adds methods to instances, not the class.- By creating
new
, I tap into the instance’s methods, making the helper accessible.
Extend the Module
class UploadHelper
# Add methods to both instances AND the class
include ActionView::Helpers::NumberHelper
extend ActionView::Helpers::NumberHelper # Now available in class methods!
def self.class_upload
puts "Uploading #{number_to_human_size(123)}" #
end
end
Why this works:
extend
adds the module’s methods as class methods.- Now both instances and the class itself can call
number_to_human_size
.
Leveling Up Adding Extra Functionality
To practice further and add versatility to my utility class, I expanded it to handle more scenarios.
Format File Sizes in Class Methods
def self.upload_info(size_in_bytes)
human_size = new.number_to_human_size(size_in_bytes) # Uses Workaround 1
"File size: #{human_size}"
end
# Example:
UploadHelper.upload_info(2048) # => "File size: 2 KB"
Toggle Between Human-Readable and Raw Sizes
def self.upload_info(size, human: true)
size_str = human ? new.number_to_human_size(size) : size.to_s
"Size: #{size_str}"
end
# Example:
UploadHelper.upload_info(2048, human: false) # => "Size: 2048"
Log All Uploads
def self.log_upload(size)
Rails.logger.info "[Upload] #{upload_info(size)}"
end
Final Thoughts
Working with Rails helper methods inside class methods can be tricky if you’re not aware of how Ruby handles module inclusion. Here are the key takeaways from my experience:
- include vs. extend Matters:
- If your helper is for instances, use
include
. - If you need it in class methods, either extend the module or instantiate an object.
- If your helper is for instances, use
- Avoid Over-Extending:
Don’t extend modules globally unless necessary; it can clutter your class methods. - When in Doubt, Instantiate:
Usingnew
inside a class method is a safe, explicit way to access instance helpers. - Rails Helpers Are Everywhere:
Modules likeActionView::Helpers
are powerful not just for views. They can be leveraged in models, utility classes, or anywhere in your Rails application.
The next time you’re stuck with a NoMethodError
in a class method, ask yourself: “Is this method living in instances or the class?” The answer might save you hours of debugging.