My question builds upon this question: Ruby Koan: Constants become symbols. I have the following code:
in_ruby_version("mri") do
RubyConstant = "What is the sound of one hand clapping?"
def test_constants_become_symbols
all_symbols = Symbol.all_symbols
assert_equal __, all_symbols.include?(__)
end
end
Is the correct answer supposed to be the following?
assert_equal true, all_symbols.include?("RubyConstant".to_sym)
I know I shouldn't just do this:
assert_equal true, all_symbols.include?(:RubyConstant)
because then I could put anything in there and it would still be true
assert_equal true, all_symbols.include?(:DoesNotMatter)
Apologies in advance for asking simple a "yes or no" question. I was curious as to know what the "right" answer is. I would have preferred to just ask this question in the comments in the previous post I mentioned above but I couldn't without making a separate post.
NOTE: the following answer only applies to environments like irb, where Ruby code is being executed line by line. When executing code in a file, Ruby scans the entire file for symbols before executing anything, so the following details are not accurate. I've not deleted this answer because it exposes an interesting edge case, but see @GlichMr's answer for a better explanation of the problem.
You can safely do the following, because Symbol.all_symbols
returns a copy of the array of symbols, not a reference.
assert_equal true, all_symbols.include?(:RubyConstant)
I think that is the intended answer to the koan, and it's why all_symbols
is defined rather than calling Symbol.all_symbols
directly. For some evidence, see the following:
>> X = 1
=> 1
>> all_symbols = Symbol.all_symbols; nil
=> nil
>> Y = 2
=> 2
>> all_symbols.include?(:X)
=> true
>> all_symbols.include?(:Y)
=> false
Using String#to_sym
would make it possible to make these calls against Symbol.all_symbols
directly, but is not necessary for solving this koan.
Here's what I got:
in_ruby_version("mri") do
RubyConstant = "What is the sound of one hand clapping?"
def test_constants_become_symbols
all_symbols_as_strings = Symbol.all_symbols.map { |x| x.to_s }
assert_equal true, all_symbols_as_strings.include?("RubyConstant")
end
end
Symbol.all_symbols
contains every symbol that was referenced - variable names, class names, constant names, actual symbols. What actually this variable contains is implementation defined, but in Ruby MRI, many symbols are already on this list.
irb(main):001:0> Constant = 42
=> 42
irb(main):002:0> Symbol.all_symbols
=> [:"", :"<IFUNC>", :"<CFUNC>", :respond_to?, ..., :irb_exit_org, :Constant]
But now, there is a catch.
Symbol.all_symbols.include?(:DoesNotMatter)
Before you will run this code, :DoesNotMatter
doesn't exist in all_symbols
, but it somehow still exists. Well, actually when you use symbol literal, it's inserted to Symbol.all_symbols
(unless it's there already). So, the symbol is already here before you even call .include?
.
EDIT: Gregory Brown suggested following workaround. It works because in Ruby assignment of Symbol.all_symbols
copies variable for some reason instead of copying the reference to variable.
irb(main):001:0> symbols = Symbol.all_symbols; 1
=> 1
irb(main):002:0> symbols.include? :something
=> false
irb(main):003:0>