Rails magic is a widespread term among developers working with Ruby on Rails application. Is it an overstatement? Well, it depends.
During day-to-day work, there isn’t usually enough time to study deeply Ruby on Rail source code. Even though it is an effective way to develop programming skills and understand the framework in-depth, sooner or later everybody finds mysterious places in a codebase that may cause frustration.
I bet that you have already seen if Rails.env.production?
condition several times. Have you ever thought where it comes from? Today I learned that ActiveSupport::StringInquirer
1 class is a part of Ruby on Rails codebase and adds some magic powers to String
objects 🙂
Wrapping a string in this class gives you a prettier way to test for equality.
In Rails codebase, we can see that the env
method uses the class:
def env @_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"].presence || ENV["RACK_ENV"].presence || "development") end
Surprisingly, the method does not return String
object, but ActiveSupport::StringInquirer
:
[22] pry(main)> Rails.env => "development" [23] pry(main)> Rails.env.class => ActiveSupport::StringInquirer
So now it’s time to go even deeper and check the source code of ActiveSupport::StringInquirer
2 class:
class StringInquirer < String private def respond_to_missing?(method_name, include_private = false) (method_name[-1] == "?") || super end def method_missing(method_name, *arguments) if method_name[-1] == "?" self == method_name[0..-2] else super end end end
Let’s break its magic down using the Rails.env
example:
Rails.env
returns"development"
, but its class isn’tString
, butActiveSupport::StringInquirer
.ActiveSupport::StringInquirer
inherits fromString
, so all its methods are available (Rails.env.titleize
returns"Development"
🙂 ).development?
method is not defined inString
class norActiveSupport::StringInquirer
, somethod_missing
one is executed as a fallback.method_missing
checks if a method name ends with?
.- In our case it ends with
?
, soself == method_name[0..-2]
comparison is made. "development"
string is equal to"development?[0..-2]"
, so the method returnstrue
.
String
class defines inquiry
method which wraps the current string in the ActiveSupport::StringInquirer
class.When to use ActiveSupport::StringInquirer
?
First of all, you need to be brave enough to consciously use Rails magic 😉 Being serious, whenever you do strings comparison the class can be used instead. Please do some benchmarking first, though.
[28] pry(main)> name = "Johny" => "Johny" [29] pry(main)> name.Johny? NoMethodError: undefined method `Johny?' for "Johny":String [30] pry(main)> name.inquiry.Johny? => true [31] pry(main)> name.inquiry.class => ActiveSupport::StringInquirer [32] pry(main)> ActiveSupport::StringInquirer.new("Johny").Johny? => true
Summary
Whenever you find a fragment of code you do not understand fully, it’s a good idea to go deeper and try to understand it fully. Thanks to such an approach I explored and understood new fragments of Ruby on Rails source code. As a result, Rails is a bit less magical to me ?.
Footnotes
- https://api.rubyonrails.org/classes/ActiveSupport/StringInquirer.html
- https://github.com/rails/rails/blob/master/activesupport/lib/active_support/string_inquirer.rb