undefined method `as_name' in rubykoan - ruby

I'm doing RubyKoan about_iteration part.
But, I encounter an error on the test_each_is_a_method_on_arrays test that I don't know how to solve ;
undefined method `as_name' for #<AboutIteration:0x00007fcbc6031dc8 #name=:test_each_is_a_method_on_arrays,
#failure=#<NoMethodError: undefined method `as_name' for #<AboutIteration:0x00007fcbc6031dc8 ...>>, #koan_cou
nt=17, #step_count=158, #koan_file="AboutIteration">
This is the code ;
require File.expand_path(File.dirname(__FILE__) + '/neo')
class AboutIteration < Neo::Koan
# -- An Aside ------------------------------------------------------
# Ruby 1.8 stores names as strings. Ruby 1.9 stores names as
# symbols. So we use a version dependent method "as_name" to convert
# to the right format in the koans. We will use "as_name" whenever
# comparing to lists of methods.
in_ruby_version("1.8") do
def as_name(name)
name.to_s
end
end
in_ruby_version("1.9", "2.0") do
def as_name(name)
name.to_sym
end
end
# Ok, now back to the Koans.
# -------------------------------------------------------------------
def test_each_is_a_method_on_arrays
assert_equal true, [].methods.include?(as_name(:each))
end
This is the full error ;
➜ koans git:(main) ✗ ruby path_to_enlightenment.rb
AboutIteration#test_each_is_a_method_on_arrays has damaged your karma.
The Master says:
You have not yet reached enlightenment.
Do not lose hope.
The answers you seek...
undefined method `as_name' for #<AboutIteration:0x00007fcbc6031dc8 #name=:test_each_is_a_method_on_arrays,
#failure=#<NoMethodError: undefined method `as_name' for #<AboutIteration:0x00007fcbc6031dc8 ...>>, #koan_cou
nt=17, #step_count=158, #koan_file="AboutIteration">
Please meditate on the following code:
/Users/amirulasyraf/Documents/koans/about_iteration.rb:27:in `test_each_is_a_method_on_arrays'
when you lose, don't lose the lesson
your path thus far [............................X_____________________] 157/278 (56%)

You are using a version of RubyKoans that is over seven years out-of-date and only supports Ruby 1.8, 1.9, and 2.0. The current version of RubyKoans supports Ruby up to 2.7 (but not 3.0).

#as_name is defined in the in_ruby_version block. Adding Ruby 3 to the list of versions did it for me.
in_ruby_version("1.9", "2", "3") do
def as_name(name)
name.to_sym
end
end

Related

Upgade to Ruby 3.1 breaks code when using CSV class from standard library

I'm upgrading a Project written for JRuby 1.7 (corresponding on the language level to Ruby 1.9) to JRuby 9.4 (corresponding to Ruby 3.1.0). In this code, we have
require 'csv'
....
CSV.parse(string, csv_options) { .... }
where string is of class String and csv_optionsis of class Hash. This statement produces, when run under the new Ruby version, the error
ArgumentError:
wrong number of arguments (given 2, expected 1)
I found in the Ruby docs the following difference in the definition of parse:
Old version:
def parse(str, options={}, &block)
New version
def parse(str, **options, &block)
I understand that in the new Ruby, I would have to invoke parse as
CSV.parse(string, **csv_options) {....}
However, I would like to keep the code compatible for both versions, at least for some transition period, but the old JRuby does not understand **variable (I would get a syntax error, unexpected tPOW).
Is there a way to write the invocation of CSV.parse in such a way, that it preserves the original semantics and can run under Ruby 1.9 and Ruby 3.1? Currently the best solution for this problem which I can think of, is to write something like turning the block argument into a proc and writing
if RUBY_VERSION < '2'
CSV.parse(string, csv_options, &myproc)
else
# Without the eval, the compiler would complain about
# the ** when compiled with Ruby 1.9
eval "CSV.parse(string, **csv_options, &myproc)"
end
which looks pretty awful.
Not sure exactly what you are passing as csv_options but all versions can handle this using an a combination of implicit Hash/kwargs. e.g.
CSV.parse(string, col_sep: '|', write_headers: false, quote_empty: true) { ... }
If this is not an option then you going to need to patch the CSV class to make it backwards compatible e.g.
csv_shim.rb
# Shim CSV::parse to mimic old method signature
# while supporting Ruby 3 kwargs argument passing
module CSVShim
def parse(string, options={}, &block)
super(string, **options, &block)
end
end
CSV.singleton_class.prepend(CSVShim)
Then you can modify as:
require 'csv'
require 'csv_shim.rb' if RUBY_VERSION > '2.6.0'
#...
CSV.parse(string, csv_options) { .... }

Display all constants in 'Digest'

I would like to know how to see every constant available on the Digest module in advance programmatically. The behaviour seen below is due to const_missing used here:
require 'digest'
Digest.constants
#=> [:Class, :REQUIRE_MUTEX, :Instance, :Base]
Digest::MD5
Digest.constants
#=> [:Class, :REQUIRE_MUTEX, :Instance, :Base, :MD5]
Digest::SHA1
Digest.constants
#=> [:Class, :REQUIRE_MUTEX, :Instance, :Base, :MD5, :SHA1]
Given that they are metaprogramming in possible digests, how can I know all possible available digests?
For knowledge, the ones that appear to be available in Ruby 2.4.1 should be [:SHA256, :SHA384, :SHA512, :SHA1, :SHA2, :MD5, :RMD160]
Here's a code snipped from the current master branch of ruby:
module Digest
def self.const_missing(name) # :nodoc:
case name
when :SHA256, :SHA384, :SHA512
lib = 'digest/sha2.so'
else
lib = File.join('digest', name.to_s.downcase)
end
begin
require lib
rescue LoadError
raise LoadError, "library not found for class Digest::#{name} -- #{lib}", caller(1)
end
unless Digest.const_defined?(name)
raise NameError, "uninitialized constant Digest::#{name}", caller(1)
end
Digest.const_get(name)
end
# ...
end
...So, you can't really list all constants without knowing them already! You need to either require the necessary file, or reference the constant directly (which will load the file dynamically, as seen above).
Any workaround solution I could give would only be guaranteed to work for a specific version of ruby. You'd be better off to just read the documentation and load each library explicitly, unfortunately!

Builder's XmlMarkup object loses constants?

I try to upgrade a legacy application from Ruby 1.8.7 to 2.2.3. Afterwards the rendering of builder templates raises errors about unknown classes.
uninitialized constant Builder::XmlMarkup::BigDecimal (NameError)
It seem that within the Builder::XmlMarkup constants like classes disappear.
### example.xml.builder (template) ###
BigDecimal.new('23') # no error
class << xml
def some
data(BigDecimal.new('23')) # raises an error in 2.2.3
end
end
xml.test { xml.some }
### main script ###
require 'rubygems'
require 'builder'
require 'bigdecimal'
def eval_script(file)
xml = Builder::XmlMarkup.new
binding.eval(File.read(file), file)
xml.target!
end
template = File.join(File.dirname(__FILE__), 'example.xml.builder')
puts eval_script(template)
# Ruby 1.8.7 / builder 3.2.0 => <test><data>0.23E2</data></test>
# Ruby 2.2.3 / builder 3.2.2 => ./eval_script.rb:5:in `some': uninitialized constant Builder::XmlMarkup::BigDecimal (NameError)
I found no reason for the behavior. How can I fix the problem?
BTW: I have the same problem with the method lookup, e.g format('%d', 42) which returns the full XML document but doesn't call Kernel.format in Ruby 2.2.3.
I found a workaround overriding const_missing which has to be applied to every template file. It works so far for the legacy application.
### example.xml.builder (template) ###
class << xml
def self.const_missing(name)
super rescue ::Object.const_get(name)
end
def some
data(BigDecimal.new('23'))
end
end
xml.test { xml.some }
But every time the constant BigDecimal is used, it triggers const_missing and then raises a NameError and calls the Object method.

Ruby 1.8.7 method parameter defaults

In ruby 1.8.7 I notice that
def some_method(arg1,arg2='default',arg3,arg4)
will return
syntax error, unexpected ',', expecting '='
It works fine in Ruby 1.9
However, this works in Ruby 1.8.7:
def some_method(arg1,arg2='default',arg3='default',arg4='default')
Is this normal, or did I do something wrong here?
Ruby 1.8.7 only supports optional arguments at the end of the argument list.
# works in all versions of ruby
def foo(a, b=2)
puts "a:#{a} b:#{b}"
end
foo(1) # a:1 b:2
foo(2, 3) # a:2 b:3
However ruby 1.9+ supports optional arguments in any spot.
# works only in ruby 1.9+
def foo(a=1, b)
puts "a:#{a} b:#{b}"
end
foo(5) # a:1 b:5
foo(5, 6) # a:5 b:6
You're doing it right. Optional arguments that come before required arguments was a language feature introduced in ruby 1.9, and is unavailable in ruby 1.8.x versions.

Ruby 1.9 const_defined?("Timeout") returns true when Timeout not in the list of constants

I'm trying to upgrade Puppet to use Ruby 1.9 and running into trouble with constants. const_defined?("Timeout") is returning true even though :Timeout isn't in the list of constants. This doesn't happen on Ruby 1.8.7. Any ideas why?
[128, 137] in /Users/matthewrobinson/work/puppet/lib/puppet/util/classgen.rb
128 def handleclassconst(klass, name, options)
129 const = genconst_string(name, options)
130
131 require 'ruby-debug';
132 debugger if const == "Timeout"=>
133 if const_defined?(const)
134 if options[:overwrite]
135 Puppet.info "Redefining #{name} in #{self}"
136 remove_const(const)
137 else
(rdb:1) const
=> "Timeout"
(rdb:1) const_defined?(const)
=> true
(rdb:1) constants.grep /Timeout/
=> []
(rdb:1) constants
=> [:Ensure, :ParameterName, :Auth_type, :Allow_root, :Authenticate_user, :Auth_class, :Comment, :Group, :K_of_n, :Mechanisms, :Rule, :Session_owner, :Shared, :MetaParamNoop, :MetaParamSchedule, :MetaParamAudit, :MetaParamCheck, :MetaParamLoglevel, :MetaParamAlias, :MetaParamTag, :RelationshipMetaparam, :MetaParamRequire, :MetaParamSubscribe, :MetaParamBefore, :MetaParamNotify, :MetaParamStage, :Component, :Macauthorization, :Expirer, :ClassMethods, :InstanceMethods, :ExecutionStub, :POSIX, :Errors, :MethodHelper, :ClassGen, :Docs, :Execution, :Tagging, :Log, :Logging, :Package, :Warnings, :Cacher, :Autoload, :LoadedFile, :Settings, :Feature, :SUIDManager, :RunMode, :CommandLine, :InstanceLoader, :Pson, :Metric, :LogPaths, :ProviderFeatures, :InlineDocs, :FileLocking, :Storage, :Checksums]
(rdb:1) constants.grep /Path/
=> [:LogPaths]
(rdb:1) self
=> Puppet::Type::Macauthorization
The behavior of const_defined? in Ruby 1.9 can be made to be the same as in Ruby 1.8 by setting the new inherit parameter to false.
mod.const_defined?(sym, inherit=true)
Here's a example to illustrate the different behavior.
module Foo
def self.bar
puts "The constant I got was #{const_get("Timeout")}"
if const_defined?("Timeout")
puts "I found #{Timeout}!"
remove_const("Timeout")
puts "Timeout is now #{Timeout}"
end
end
end
class Timeout
end
puts Foo.bar
Under Ruby 1.9.2 the output from running this is:
The constant I got was Timeout
I found Timeout!
19_test.rb:6:in `remove_const': constant Foo::Timeout not defined (NameError)
from 19_test.rb:6:in `bar'
from 19_test.rb:13:in `<main>'
So even though const_defined? recognizes that Timeout is defined at top scope as a class, remove_const is only allowed to remove constants in Foo's scope.
In Ruby 1.8.7 the output is:
The constant I got was Timeout
nil
So const_get looks at ancestor scopes just like in Ruby 1.9.2, but const_defined? does not, which prevents remove_const from getting called.
Ruby 1.9.2 can be made to behave like 1.8.7 like so:
module Foo
def self.bar
puts "The constant I got was #{const_get("Timeout")}"
if const_defined?("Timeout", false)
puts "I found #{Timeout}!"
remove_const("Timeout")
puts "Timeout is now #{Timeout}"
end
end
end
class Timeout
end
puts Foo.bar
However, this is now not backwards compatible with Ruby 1.8 since const_defined? doesn't have a second parameter in 1.8. To get around this I made the following method that can be called instead of const_defined? and used in either version of Ruby.
def is_constant_defined?(const)
if ::RUBY_VERSION =~ /1.9/
const_defined?(const, false)
else
const_defined?(const)
end
end
This solved this particular Ruby 1.9 upgrade issue. It may not be the best long term solution, and the real issue is that there's a class called Timeout at topscope AND sometimes a constant called Timeout in other classes that needs to be checked for, but this change gets the code much closer to running on Ruby 1.9.
I can't say for sure what's going on.
However, the RDoc for const_defined? and constants is different in 1.8.7, whereas it's fairly similar in 1.9.
In 1.8.7, const_defined? says:
Returns true if a constant with the given name is defined by mod.
and constants says
Returns an array of the names of the constants accessible in mod. This includes the names of constants in any included modules (example at start of section).
However, in 1.9, const_defined? says
Returns true if a constant with the given name is defined by mod, or its ancestors if inherit is not false. [by default, inherit is true]
and constants says
Returns an array of the names of the constants accessible in mod. This includes the names of constants in any included modules (example at start of section), unless the all parameter is set to false. [by default, all is true]
So it seems like the behaviour of the two methods is consistent in 1.9, but not consistent in 1.8.7. But I could be wrong.
That being said, I'd suggest the following:
Create a toy example of using const_defined? and constants, preferably not involving Timeout, and play around with it until you are confident you understand what the two methods do, under both 1.8 and 1.9.
Work out where the Timeout constant belongs to. Also check whether IRB or the debugger may cause Timeout to become defined when it previously was undefined, and whether it gets loaded by default by one version of Ruby but not the other.
I also came across http://redmine.ruby-lang.org/issues/1915 when googling for const_defined? 1.8 1.9. I'm not sure if it's relevant or not.
I hope this helps - I'm not sure though!

Resources